chore: baseline usable version

This commit is contained in:
dela
2026-06-10 11:17:51 +08:00
commit a2f82a86b6
932 changed files with 172551 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
# References:
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
# https://code.qt.io/cgit/qt/qt5.git/tree/_clang-format
BasedOnStyle: LLVM
Standard: c++17
# 指针和引用的对齐方式。
# 可能的值有:
# PAS_Left (在配置中: Left) 指针左对齐。
# PAS_Right (在配置中: Right) 指针右对齐。
# PAS_Middle (在配置中: Middle) 指针中间对齐。
PointerAlignment: Right
# public/protected/private 等访问修饰符偏移量
AccessModifierOffset: -4
# 缩进长度
IndentWidth: 4
# 连续空行的最大数
MaxEmptyLinesToKeep: 999
# 在OC中的@property后面添加一个空格。例如使用“@property (readonly)”而不是“@property(readonly)”
ObjCSpaceAfterProperty: true
# OC块中所拍的字符数
ObjCBlockIndentWidth: 4
# 取决于值, 语句“int f() { return 0; }”可以被放到一个单行。
# 可能的值有:
# SFS_None (在配置中: None) 从不合并方法或函数到单独的一行。
# SFS_Empty (在配置中: Empty) 仅合并空的函数。
# SFS_Inline (在配置中: Inline) 仅合并类中定义的方法或函数. 意味着 “empty”.
# SFS_All (在配置中: All) 合并所有的方法适应单行.
AllowShortFunctionsOnASingleLine: None
# 如果为真true, 语句“if (a) return;” 能被放到单行。
AllowShortIfStatementsOnASingleLine: false
# 如果为真true, 对齐注释。
AlignTrailingComments: true
# 如果为真,对齐连续的宏定义
AlignConsecutiveMacros: true
# 如果为真true,将会在“[”之后和“]”之前插入空格。
SpacesInSquareBrackets: false
# 如果为真true, 将会在“(”之后和“)”之前插入空格。
SpacesInParentheses : false
# 如果为真true, 校准连续的声明。
# 这将会校准连续多行的声明的名字。这将会导致像下面这样的格式:
# int aaaa = 12;
# float b = 23;
# std::string ccc = 23;
AlignConsecutiveDeclarations: false
# 如果为真true连续调整多行
# 这将会调整连续行中的分配操作符。这将会导致像下面这样的格式:
# int aaaa = 12;
# int b = 23;
# int ccc = 23;
AlignConsecutiveAssignments: false
# 如果为假false移除分配操作符=)前空格。
SpaceBeforeAssignmentOperators: true
# 如果为真true, 将会在字面量容器中插入空格(例如 OC和Javascript的数组和字典字面量)。
SpacesInContainerLiterals: false
# 缩进case标签
IndentCaseLabels: true
# 如果表达式中包含函数调用,并且函数调用因为表达式太长被放到了下一行,是否缩进
IndentWrappedFunctionNames: true
# 如果为真true, 保持块的起始空行。
# true: false:
# if (foo) { vs. if (foo) {
# bar();
# bar(); }
# }
KeepEmptyLinesAtTheStartOfBlocks: true
# 允许所有参数都被放在下一行
AllowAllParametersOfDeclarationOnNextLine: false
# 使用C风格强制类型转换后是否在中间添加一个空格
SpaceAfterCStyleCast: true
# 在模板定义后换行
AlwaysBreakTemplateDeclarations: Yes
# Tab长度
TabWidth: 4
# 是否使用Tab
UseTab: Never
# 在括号后对齐参数
# someLongFunction(argument1,
# argument2);
AlignAfterOpenBracket: Align
# 名字空间内部缩进
NamespaceIndentation: All
# 一行最长列数
ColumnLimit: 100
# 按层次缩进宏定义
IndentPPDirectives: AfterHash
# 预处理语句缩进为 2
PPIndentWidth: 2
# 数组元素对齐
AlignArrayOfStructures: Left
# 不对头文件排序
SortIncludes: Never
FixNamespaceComments: false
StatementMacros: ['__qas_attr__', '__qas_exclude__', '__qas_include__']
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE ]

98
libs/qwindowkit/.gitignore vendored Normal file
View File

@@ -0,0 +1,98 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.log
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
# *.res
# *.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
__pycache__
*.pyc
# Binaries
# --------
*.dll
*.exe
.DS_Store
*/.DS_Store
build-*
build/
cmake-build*
*.user
*.lnk
_workingDir*
.vscode
.idea
.cache
cache
.vs
out/
CMakeSettings.json
# /vcpkg
/data
/*.natvis
*.sublime-*
setup-vcpkg.json
setup-vcpkg-temp*

4
libs/qwindowkit/.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "qmsetup"]
path = qmsetup
url = ../../stdware/qmsetup.git
branch = main

View File

@@ -0,0 +1,144 @@
cmake_minimum_required(VERSION 3.19)
project(QWindowKit
VERSION 1.5.1.0
LANGUAGES CXX
HOMEPAGE_URL "https://github.com/stdware/qwindowkit"
DESCRIPTION "Cross-platform window customization framework"
)
# ----------------------------------
# Build Options
# ----------------------------------
option(QWINDOWKIT_BUILD_STATIC "Build static libraries" OFF)
option(QWINDOWKIT_BUILD_WIDGETS "Build widgets module" ON)
option(QWINDOWKIT_BUILD_QUICK "Build quick module" OFF)
option(QWINDOWKIT_BUILD_EXAMPLES "Build examples" OFF)
option(QWINDOWKIT_BUILD_DOCUMENTATIONS "Build documentations" OFF)
option(QWINDOWKIT_INSTALL "Install library" ON)
option(QWINDOWKIT_FORCE_QT_WINDOW_CONTEXT "Force use Qt Window Context" OFF)
option(QWINDOWKIT_ENABLE_WINDOWS_SYSTEM_BORDERS "Enable system borders on Windows" ON)
option(QWINDOWKIT_ENABLE_STYLE_AGENT "Enable building style agent" ON)
#[[
Detailed Introcuction to Configure Options:
`QWINDOWKIT_BUILD_DOCUMENTATIONS`
- If you have installed `Doxygen`, you can ENABLE this option so that the documentations
will also be built and installed.
- If not, you can read the comments in `qdoc` style in `cpp` files to get detailed usages
of the public APIs.
`QWINDOWKIT_ENABLE_WINDOWS_SYSTEM_BORDERS`
- If you don't want the system borders on Windows 10/11, you can DISABLE this option.
- If so, the Windows 10 top border issue will disappear. However, part of the client edge
area will be occupied as the resizing margins.
`QWINDOWKIT_FORCE_QT_WINDOW_CONTEXT`
- If you want to use pure Qt emulated frameless implementation, you can ENABLE this option.
- If so, all system native features will be lost.
- The Qt Window Context is supported on all platforms.
`QWINDOWKIT_ENABLE_STYLE_AGENT`
- Select whether to exclude the style component by DISABLING this option according to your
requirements and your Qt version.
#]]
# ----------------------------------
# CMake Settings
# ----------------------------------
if(MSVC)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /manifest:no")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /manifest:no")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /manifest:no")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8")
if(NOT DEFINED CMAKE_DEBUG_POSTFIX)
set(CMAKE_DEBUG_POSTFIX "d")
endif()
elseif(MINGW)
if(NOT DEFINED CMAKE_STATIC_LIBRARY_PREFIX)
set(CMAKE_STATIC_LIBRARY_PREFIX "")
endif()
if(NOT DEFINED CMAKE_SHARED_LIBRARY_PREFIX)
set(CMAKE_SHARED_LIBRARY_PREFIX "")
endif()
if(NOT DEFINED CMAKE_IMPORT_LIBRARY_PREFIX)
set(CMAKE_IMPORT_LIBRARY_PREFIX "")
endif()
endif()
if(NOT DEFINED CMAKE_BUILD_TYPE AND NOT DEFINED CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release")
endif()
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux" AND NOT CMAKE_CROSSCOMPILING)
set(CMAKE_INSTALL_RPATH "\$ORIGIN:\$ORIGIN/../lib")
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH OFF)
endif()
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_VISIBILITY_INLINES_HIDDEN ON)
set(CMAKE_C_VISIBILITY_PRESET "hidden")
set(CMAKE_CXX_VISIBILITY_PRESET "hidden")
if(QWINDOWKIT_INSTALL)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
endif()
# ----------------------------------
# Project Variables
# ----------------------------------
set(QWINDOWKIT_VERSION ${PROJECT_VERSION})
set(QWINDOWKIT_INSTALL_NAME ${PROJECT_NAME})
string(TIMESTAMP _QWINDOWKIT_CURRENT_YEAR "%Y")
set(QWINDOWKIT_COPYRIGHT "Copyright 2023-${_QWINDOWKIT_CURRENT_YEAR} Stdware Collections")
set(QWINDOWKIT_DESCRIPTION ${PROJECT_DESCRIPTION})
# ----------------------------------
# Find basic dependencies
# ----------------------------------
find_package(qmsetup QUIET)
if(NOT TARGET qmsetup::library)
# Modify this variable according to your project structure
set(_source_dir ${CMAKE_CURRENT_SOURCE_DIR}/qmsetup)
# Import install function
include("${_source_dir}/cmake/modules/private/InstallPackage.cmake")
# Install package in place
set(_package_path)
qm_install_package(qmsetup
SOURCE_DIR ${_source_dir}
BUILD_TYPE Release
RESULT_PATH _package_path
)
# Find package again
find_package(qmsetup REQUIRED PATHS ${_package_path})
# Update import path
set(qmsetup_DIR ${_package_path} CACHE PATH "" FORCE)
endif()
qm_import(Filesystem)
qm_init_directories()
set(QT_NO_PRIVATE_MODULE_WARNING ON)
# ----------------------------------
# Add source modules
# ----------------------------------
add_subdirectory(src)
if(QWINDOWKIT_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()

202
libs/qwindowkit/LICENSE Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (C) 2023-2025 Stdware Collections (https://www.github.com/stdware)
Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
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.

316
libs/qwindowkit/README.md Normal file
View File

@@ -0,0 +1,316 @@
# QWindowKit
Cross-platform window customization framework for Qt Widgets and Qt Quick.
This project inherited the major implementations from [wangwenx190 FramelessHelper](https://github.com/wangwenx190/framelesshelper), with a complete refactoring and upgrading of the architecture.
Feature requests are welcome.
## Stay In Touch :triangular_flag_on_post:
You can share your findings, thoughts and ideas on improving / implementing QWindowKit functionalities on more platforms and apps!
- Chat with us on [Discord](https://discord.gg/grrM4Tmesy)
- 中文用户可加入 QQ 群 876419693
## Supported Platforms
- Microsoft Windows
- Apple macOS (11+)
- GNU/Linux
## Features
- Full support of Windows 11 Snap Layout
- Better workaround to handle Windows 10 top border issue
- Support Mac system buttons geometry customization
- Simpler APIs, more detailed documentations and comments
## Gallery
### Windows 11 (With Snap Layout)
![image](./docs/images/win11.png)
### Windows 10 (And 7, Vista)
![image](./docs/images/win10.png)
### macOS & Linux
| macOS | Linux (Ubuntu 20.04) |
|:-------------------------------:|:---------------------------------:|
| ![image](./docs/images/mac.png) | ![image](./docs/images/linux.png) |
## Requirements
| Component | Requirement | Details |
|:---------:|:-----------:|:-------------------------:|
| Qt | \>=5.12 | Core, Gui, Widgets, Quick |
| Compiler | \>=C++17 | MSVC 2019, GCC, Clang |
| CMake | \>=3.19 | >=3.20 is recommended |
Please read [Vulnerabilities](#Vulnerabilities) carefully to acquire detailed requirements.
### Tested Compilers
- Windows
- MSVC: 2019, 2022
- MinGW (GCC): 13.2.0
- macOS
- Clang 14.0.3
- Ubuntu
- GCC: 9.4.0
## Dependencies
- Qt 5.12 or higher
- [qmsetup](https://github.com/stdware/qmsetup)
## Integrate
### Build & Install
```sh
git clone --recursive https://github.com/stdware/qwindowkit
cd qwindowkit
cmake -B build -S . \
-DCMAKE_PREFIX_PATH=<QT_DIR> \
-Dqmsetup_DIR=<dir> \ # Optional
-DQWINDOWKIT_BUILD_QUICK=TRUE \ # Optional
-DCMAKE_INSTALL_PREFIX=/path/install \
-G "Ninja Multi-Config"
cmake --build build --target install --config Debug
cmake --build build --target install --config Release
```
Read the root `CMakeLists.txt` for more build options.
You can also include this directory as a subproject if you choose CMake as your build system.
For other build systems, you need to install with CMake first and include the corresponding configuration files in your project.
### Import
#### CMake Project
```sh
cmake -B build -DQWindowKit_DIR=/path/install/lib/cmake/QWindowKit
```
```cmake
find_package(QWindowKit COMPONENTS Core Widgets Quick REQUIRED)
target_link_libraries(widgets_app PUBLIC QWindowKit::Widgets)
target_link_libraries(quick_app PUBLIC QWindowKit::Quick)
```
#### QMake Project
```cmake
# WidgetsApp.pro
include("/path/install/share/QWindowKit/qmake/QWKWidgets.pri")
# QuickApp.pro
include("/path/install/share/QWindowKit/qmake/QWKQuick.pri")
```
#### Visual Studio Project
See [Visual Studio Guide](./docs/visual-studio-guide.md) for detailed usages.
## Quick Start
### Qt Widgets Application
#### Initialization
The following initialization should be done before any widget constructs.
```cpp
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QGuiApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings)
// ...
}
```
#### Setup Window Agent
First, setup `WidgetWindowAgent` for your top `QWidget` instance. (Each window needs its own agent.)
```c++
#include <QWKWidgets/widgetwindowagent.h>
MyWidget::MyWidget(QWidget *parent) {
// ...
auto agent = new QWK::WidgetWindowAgent(this);
agent->setup(this);
// ...
}
```
If you don't want to derive a new widget class or change the constructor, you can initialize the agent after the window
constructs.
```c++
auto w = new MyWidget();
auto agent = new QWK::WidgetWindowAgent(w);
agent->setup(w);
```
You should call `QWK::WidgetWindowAgent::setup()` as early as possible, especially when you need to set the size constrains. QWindowKit will change some Qt internal data which will affect how Qt calculates the window size, and thus you need to let QWindowKit initialize at the very beginning.
#### Construct Title bar
Then, construct your title bar widget, without which the window lacks the basic interaction feature, and it's better to
put it into the window's layout.
You can use the [`WindowBar`](examples/shared/widgetframe/windowbar.h) provided by `WidgetFrame` in the examples as the
container of your title bar components.
Let `WidgetWindowAgent` know which widget the title bar is.
```c++
agent->setTitleBar(myTitleBar);
```
Next, set system button hints to let `WidgetWindowAgent` know the role of the child widgets, which is important for the
Snap Layout to work.
```c++
agent->setSystemButton(QWK::WindowAgentBase::WindowIcon, myTitleBar->iconButton());
agent->setSystemButton(QWK::WindowAgentBase::Minimize, myTitleBar->minButton());
agent->setSystemButton(QWK::WindowAgentBase::Maximize, myTitleBar->maxButton());
agent->setSystemButton(QWK::WindowAgentBase::Close, myTitleBar->closeButton());
```
Doing this does not mean that these buttons' click events are automatically associated with window actions, you still need to manually connect the signals and slots to emulate the native window behaviors.
On macOS, this step can be skipped because it is better to use the buttons provided by the system.
Last but not least, set hit-test visible hint to let `WidgetWindowAgent` know which widgets are willing to receive mouse events.
```c++
agent->setHitTestVisible(myTitleBar->menuBar(), true);
```
The rest region within the title bar will be regarded as the draggable area for the user to move the window, and thus any QWidgets inside it will not receive any user interaction events such as mouse events/focus events/etc anymore, but you can still send/post such events to these widgets manually, either through Qt API or system API.
- If you want to disable window maximization, you can remove the `Qt::WindowMaximizeButtonHint` flag from the window.
<!-- #### Window Attributes (Experimental)
On Windows 11, you can use this API to enable system effects.
```c++
agent->setWindowAttribute("mica", true);
```
Available keys: `mica`, `mica-alt`, `acrylic`, `dark-mode`. -->
### Qt Quick Application
#### Initialization
Make sure you have registered `QWindowKit` into QtQuick:
```cpp
#include <QWKQuick/qwkquickglobal.h>
int main(int argc, char *argv[])
{
// ...
QQmlApplicationEngine engine;
// ...
QWK::registerTypes(&engine);
// ...
}
```
#### Setup Window Components
Then you can use `QWindowKit` data types and classes by importing its URI:
```qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QWindowKit 1.0
Window {
id: window
visible: false // We hide it first, so we can move the window to our desired position silently.
Component.onCompleted: {
windowAgent.setup(window)
window.visible = true
}
WindowAgent {
id: windowAgent
// ...
}
}
```
You can omit the version number or use "auto" instead of "1.0" for the module URI if you are using Qt6.
As we just mentioned above, if you are going to set the size constrains, please do it after `windowAgent.setup()` is called.
### Learn More
See [examples](examples) for more demo use cases. The examples have no High DPI support.
- QWindowKit Internals [TODO]
- [FramelessHelper Related](docs/framelesshelper-related.md)
### Vulnerabilities
#### Qt Version
- To achieve better frameless functionality, QWindowKit depends heavily on Qt's internal implementation. However, there are many differences in different versions of Qt, and earlier versions of Qt5 and Qt6 have many bugs which make it extremely difficult for QWindowKit to workaround without changing the Qt source code.
- And also due to limited manpower, although QWindowKit can be successfully compiled on Qt 5.12 or later, it can hardly work perfectly on all Qt versions.
- Therefore, the following Qt version ranges are recommended, if there are any exceptions with QWindowKit in your application, make sure the Qt version you use is in the ranges before raising the issue.
- Qt 5: 5.15.2 or higher (you may be able to build QWK on top of older Qt versions, however, QWK may not behave well and we won't accept bug reports from these unsupported versions)
- Qt 6: 6.6.2 or higher (the newer, the better)
#### Hot Switch
- Once you have made the window frameless, it will not be able to switch back to the system frame again unless you destroy your window and recreate it with different settings.
- Do not use `QWidget::setWindowFlags` to change the window flags after `windowAgent.setup()` is called. As a result, `QWK::WidgetWindowAgent` can not be setup on `QDockWidget`.
#### Native Child Widget
- If you are about to add a widget with `Qt::WA_NativeWindow` property enabled as a descendent of the frameless window, you should enable `Qt::WA_DontCreateNativeAncestors` of it in advance.
#### Size Constrains
- If you want to disable window resizing, you can set a fixed size, which is officially supported by QWindowKit. If you use other special means to achieve this (eg. hook Win32 messages), QWK doesn't guarantee everything can still be fully functional.
- If you set a maximized width or height, the window should not be maximized because you cannot get the correct window size through Qt APIs. You may workaround this by using system APIs such as `GetWindowRect` or `GetClientRect`. The root cause lies deep in Qt QPA implementations and currently we don't know how to fix it without modifying Qt itself.
#### Windows 10
- Due to the inherent defects in the Windows 10 window system, the top border will disappear when the system title bar is removed. We have filtered Qt's event and perfectly reshown the system top border, thanks to the implementation of Windows Terminal for our reference. However, this workaround only works with QtWidgets and QtQuick (**only when rendering through OpenGL/D3D11/D3D12, not Vulkan**) applications.
- For QtQuick applications, when rendering through Vulkan, the top border will become a solid black line, that's a known issue and currently we are not able to fix it. Please use QWK's borderless version if you can't change your graphics backend.
- For QtQuick applications, when rendering through D3D11/D3D12, you may see a strange white line on window top, it may disappear if you resize the window. Currently it's a bug and we are working hard to find a suitable solution for it, for now you can set the environment variable `QT_QPA_DISABLE_REDIRECTION_SURFACE` to a non-zero value in your `main` function before any `QCoreApplication` instance is created to workaround this issue. This environment variable is first introduced in Qt 6.7.0 (qtbase/838fc606c170fac112f7bb5971c2507b7b56d08a). You must **NOT** enable this feature for OpenGL/Vulkan because their rendering will be totally broken.
## TODO
- Fix mouse cursor mapping issues
- More documentations
- When do we support Linux native features?
## Special Thanks
- [Maplespe](https://github.com/Maplespe)
- [zhiyiYo](https://github.com/zhiyiYo)
## License
QWindowKit is licensed under the [Apache 2.0 License](./LICENSE).
<!--
**You MUST keep a copyright notice of QWindowKit in a prominent place on your project, such as the README document and the About Dialog.**
**You MUST NOT remove the license text from the header files and source files of QWindowKit.**
-->

View File

@@ -0,0 +1,89 @@
# FramelessHelper 2.x
Cross-platform window customization framework for Qt Widgets and Qt Quick. Supports Windows, Linux and macOS.
## Join with Us :triangular_flag_on_post:
You can join our [Discord channel](https://discord.gg/grrM4Tmesy) to communicate with us. You can share your findings, thoughts and ideas on improving / implementing FramelessHelper functionalities on more platforms and apps!
## More
### Title Bar Design Guidance
- Microsoft: <https://docs.microsoft.com/en-us/windows/apps/design/basics/titlebar-design>
- KDE: <https://develop.kde.org/hig/>
- GNOME: <https://developer.gnome.org/hig/patterns/containers/header-bars.html>
- Apple: <https://developer.apple.com/design/human-interface-guidelines/macos/windows-and-views/window-anatomy/>
## Platform Notes
### Windows
- If DWM composition is disabled in some very rare cases (only possible on Windows 7), the top-left corner and top-right corner will appear in round shape. The round corners can be restored to square if you re-enable DWM composition.
- There's an OpenGL driver bug which will cause some frameless windows have a strange black bar right on top of your homemade title bar, and it also makes the controls in your windows shifted to the bottom-right corner for some pixels. It's a bug of your graphics card driver, specifically, your OpenGL driver, not FramelessHelper. There are some solutions provided by our users but some of them may not work in all conditions, you can pick one from them:
Solution | Principle
-------- | ---------
Upgrade the graphics driver | Try to use a newer driver which may ship with the fix
Change the system theme to "Basic" (in contrary to "Windows Aero") | Let Windows use pure software rendering
If there are multiple graphics cards, use another one instead | Try to use a different driver which may don't have such bug at all
Upgrade the system to at least Windows 11 | Windows 11 redesigned the windowing system so the bug can no longer be triggered
Remove the `WS_THICKFRAME` and `WS_OVERLAPPED` styles from the window, and maybe also add the `WS_POPUP` style at the same time, and don't do anything inside the `WM_NCCALCSIZE` block (just return `false` directly or remove/comment out the whole block) | Try to mirror Qt's `FramelessWindowHint`'s behavior
Use `Qt::FramelessWindowHint` instead of doing the `WM_NCCALCSIZE` trick | Qt's rendering code path is totally different between these two solutions
Force Qt to use the ANGLE backend instead of the Desktop OpenGL | ANGLE will translate OpenGL directives into D3D ones
Force Qt to use pure software rendering instead of rendering through OpenGL | Qt is not using OpenGL at all
Force Qt to use the Mesa 3D libraries instead of normal OpenGL | Try to use a different OpenGL implementation
Use Direct3D/Vulkan/Metal instead of OpenGL | Just don't use the buggy OpenGL
If you are lucky enough, one of them may fix the issue for you. If not, you may try to use multiple solutions together. **But I can't guarantee the issue can 100% be fixed.**
- Due to there are many sub-versions of Windows 10, it's highly recommended to use the latest version of Windows 10, at least **no older than Windows 10 1809**. If you try to use this framework on some very old Windows 10 versions such as 1507 or 1607, there may be some compatibility issues. Using this framework on Windows 7 is also supported but not recommended. To get the most stable behavior and the best appearance, you should use it on the latest version of Windows 10 or Windows 11.
- To make the snap layout work as expected, there are some additional rules for your homemade system buttons to follow:
- **Add a manifest file to your application. In the manifest file, you need to claim your application supports Windows 11 explicitly. This step is VERY VERY IMPORTANT. Without this step, the snap layout feature can't be enabled.**
- Call `setSystemButton()` for each button (it can be any *QWidget* or *QQuickItem*) to let FramelessHelper know which is the minimize/maximize/close button.
### Linux
- FramelessHelper will force your application to use the _XCB_ platform plugin when running on Wayland.
- The resize area is inside of the window.
### macOS
- Some users reported that the window is not resizable on some old macOS versions.
## Special Thanks
*Ordered by first contribution time (it may not be very accurate, sorry)*
- [Yuhang Zhao](https://github.com/wangwenx190): Help me create this project. This project is mainly based on his code.
- [Julien](https://github.com/JulienMaille): Help me test this library on many various environments and help me fix the bugs we found. Contributed many code to improve this library. The MainWindow example is mostly based on his code.
- [Altair Wei](https://github.com/altairwei): Help me fix quite some small bugs and give me many important suggestions, the 2.x version is also inspired by his idea during our discussions.
- [Kenji Mouri](https://github.com/MouriNaruto): Give me a lot of help on Win32 native developing.
- [Dylan Liu](https://github.com/mentalfl0w): Help me improve the build process on macOS.
- [SineStriker](https://github.com/SineStriker): Spent over a whole week helping me improve the Snap Layout implementation, fixing potential bugs and also give me a lot of professional and useful suggestions. Without his great effort, the new implementation may never come.
- And also thanks to other contributors not listed here! Without their valuable help, this library wouldn't have such good quality and user experience!
## License
```text
MIT License
Copyright (C) 2021-2023 by wangwenx190 (Yuhang Zhao)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

View File

@@ -0,0 +1,15 @@
# Visual Studio Guide
![Visual Studio Guide - Step 1](./images/vs-guide-1.png)
First, click the "View" menu, find "Other Windows", click "Property Manager".
![Visual Studio Guide - Step 2](./images/vs-guide-2.png)
Then, right click the project name item and pop up a context menu, click "Add
Existing Property Sheet...".
![Visual Studio Guide - Step 3](./images/vs-guide-3.png)
Finally, find the "QWindowKit.props" in the "[Build output folder of
QWindowKit]\share\QWindowKit" folder, click "Open" button.

View File

@@ -0,0 +1,23 @@
set(QWK_EXAMPLES_DIR ${CMAKE_CURRENT_SOURCE_DIR})
macro(qwk_add_example _target)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
add_executable(${_target})
qm_configure_target(${_target} ${ARGN})
qm_add_win_rc(${_target} ICON ${QWK_EXAMPLES_DIR}/shared/resources/app/example.ico)
qm_add_win_manifest(${_target})
qm_add_mac_bundle(${_target} ICON ${QWK_EXAMPLES_DIR}/shared/resources/app/example.icns)
endmacro()
add_subdirectory(shared)
if(QWINDOWKIT_BUILD_WIDGETS)
add_subdirectory(mainwindow)
endif()
if(QWINDOWKIT_BUILD_QUICK)
add_subdirectory(qml)
endif()

View File

@@ -0,0 +1,10 @@
project(QWKExample_MainWindow)
file(GLOB _src *.h *.cpp)
qwk_add_example(${PROJECT_NAME}
FEATURES cxx_std_17
SOURCES ${_src} mainwindow.qrc ../shared/resources/shared.qrc
QT_LINKS Core Gui Widgets # MultimediaWidgets
LINKS QWKWidgets WidgetFrame
)

View File

@@ -0,0 +1,170 @@
/* Window bar */
QWK--WindowBar[bar-active=true] {
/*background-color: #3C3C3C;*/
background-color: transparent;
}
QWK--WindowBar[bar-active=false] {
/*background-color: #505050;*/
background-color: transparent;
}
/* Title label */
QWK--WindowBar>QLabel#win-title-label {
padding: 0;
border: none;
color: #ECECEC;
background-color: transparent;
min-height: 28px;
}
/* System buttons */
QWK--WindowBar>QAbstractButton[system-button=true] {
qproperty-iconSize: 12px 12px;
min-width: 50px;
border: none;
padding: 0;
background-color: transparent;
}
QWK--WindowBar>QAbstractButton#pin-button {
qproperty-iconNormal: url(":/window-bar/pin.svg");
qproperty-iconChecked: url(":/window-bar/pin-fill.svg");
qproperty-iconSize: 15px 15px;
}
QWK--WindowBar>QAbstractButton#pin-button:hover,
QWK--WindowBar>QAbstractButton#pin-button:pressed {
background-color: rgba(255, 255, 255, 15%);
}
QWK--WindowBar>QAbstractButton#min-button {
qproperty-iconNormal: url(":/window-bar/minimize.svg");
}
QWK--WindowBar>QAbstractButton#min-button:hover,
QWK--WindowBar>QAbstractButton#min-button:pressed {
background-color: rgba(255, 255, 255, 15%);
}
QWK--WindowBar>QAbstractButton#max-button {
qproperty-iconNormal: url(":/window-bar/maximize.svg");
qproperty-iconChecked: url(":/window-bar/restore.svg");
}
QWK--WindowBar>QAbstractButton#max-button:hover,
QWK--WindowBar>QAbstractButton#max-button:pressed {
background-color: rgba(255, 255, 255, 15%);
}
QWK--WindowBar>QAbstractButton#close-button {
qproperty-iconNormal: url(":/window-bar/close.svg");
}
QWK--WindowBar>QAbstractButton#close-button:hover,
QWK--WindowBar>QAbstractButton#close-button:pressed {
background-color: #e81123;
}
/* Icon button */
QWK--WindowBar>QAbstractButton#icon-button {
qproperty-iconNormal: url(":/app/example.png");
qproperty-iconSize: 18px 18px;
min-width: 40px;
border: none;
padding: 0;
background-color: transparent;
}
/* Menu Bar */
QMenuBar {
background-color: transparent;
border: none;
}
QMenuBar>QToolButton#qt_menubar_ext_button {
qproperty-icon: url(":/window-bar/more-line.svg");
}
QMenuBar>QToolButton#qt_menubar_ext_button:hover,
QMenuBar>QToolButton#qt_menubar_ext_button:pressed {
background-color: rgba(255, 255, 255, 10%);
}
QMenuBar::item {
color: #CCCCCC;
border: none;
padding: 8px 12px;
}
QMenuBar::item:selected {
background-color: rgba(255, 255, 255, 10%);
}
/* Menu */
QMenu {
padding: 4px;
background: #303030;
border: 1px solid transparent;
}
QMenu::indicator {
left: 6px;
width: 20px;
height: 20px;
}
QMenu::icon {
left: 6px;
}
QMenu::item {
background: transparent;
color: #CCCCCC;
padding: 6px 24px;
}
QMenu::item:selected {
color: white;
background-color: #0060C0;
}
QMenu::item:disabled {
color: #666666;
background-color: transparent;
}
QMenu::separator {
height: 2px;
background-color: #5B5B5B;
margin: 6px 0;
}
/* Window */
MainWindow {
background-color: #1E1E1E;
}
MainWindow[custom-style=true] {
background-color: transparent;
}
QWidget#clock-widget {
font-size: 75px;
color: #FEFEFE;
font-weight: bold;
background-color: transparent;
}

View File

@@ -0,0 +1,168 @@
/* Window bar */
QWK--WindowBar[bar-active=true] {
background-color: #195ABE;
/* background-color: transparent; */
}
QWK--WindowBar[bar-active=false] {
background-color: #195ABE;
/* background-color: transparent; */
}
/* Title label */
QWK--WindowBar>QLabel#win-title-label {
padding: 0;
border: none;
color: #ECECEC;
background-color: transparent;
min-height: 28px;
}
/* System buttons */
QWK--WindowBar>QAbstractButton[system-button=true] {
qproperty-iconSize: 12px 12px;
min-width: 50px;
border: none;
padding: 0;
background-color: transparent;
}
QWK--WindowBar>QAbstractButton#pin-button {
qproperty-iconNormal: url(":/window-bar/pin.svg");
qproperty-iconChecked: url(":/window-bar/pin-fill.svg");
qproperty-iconSize: 15px 15px;
}
QWK--WindowBar>QAbstractButton#pin-button:hover,
QWK--WindowBar>QAbstractButton#pin-button:pressed {
background-color: rgba(0, 0, 0, 15%);
}
QWK--WindowBar>QAbstractButton#min-button {
qproperty-iconNormal: url(":/window-bar/minimize.svg");
}
QWK--WindowBar>QAbstractButton#min-button:hover,
QWK--WindowBar>QAbstractButton#min-button:pressed {
background-color: rgba(0, 0, 0, 15%);
}
QWK--WindowBar>QAbstractButton#max-button {
qproperty-iconNormal: url(":/window-bar/maximize.svg");
qproperty-iconChecked: url(":/window-bar/restore.svg");
}
QWK--WindowBar>QAbstractButton#max-button:hover,
QWK--WindowBar>QAbstractButton#max-button:pressed {
background-color: rgba(0, 0, 0, 15%);
}
QWK--WindowBar>QAbstractButton#close-button {
qproperty-iconNormal: url(":/window-bar/close.svg");
}
QWK--WindowBar>QAbstractButton#close-button:hover,
QWK--WindowBar>QAbstractButton#close-button:pressed {
background-color: #e81123;
}
/* Icon button */
QWK--WindowBar>QAbstractButton#icon-button {
qproperty-iconNormal: url(":/app/example.png");
qproperty-iconSize: 18px 18px;
min-width: 40px;
border: none;
padding: 0;
background-color: transparent;
}
/* Menu Bar */
QMenuBar {
background-color: transparent;
border: none;
}
QMenuBar>QToolButton#qt_menubar_ext_button {
qproperty-icon: url(":/window-bar/more-line.svg");
}
QMenuBar>QToolButton#qt_menubar_ext_button:hover,
QMenuBar>QToolButton#qt_menubar_ext_button:pressed {
background-color: rgba(255, 255, 255, 10%);
}
QMenuBar::item {
color: #EEEEEE;
border: none;
padding: 8px 12px;
}
QMenuBar::item:selected {
background-color: rgba(255, 255, 255, 10%);
}
/* Menu */
QMenu {
padding: 4px;
background: white;
border: 1px solid #E0E0E0;
}
QMenu::indicator {
left: 6px;
width: 20px;
height: 20px;
}
QMenu::icon {
left: 6px;
}
QMenu::item {
background: transparent;
color: #333333;
padding: 6px 24px;
}
QMenu::item:selected {
background-color: rgba(0, 0, 0, 10%);
}
QMenu::item:disabled {
color: #CCCCCC;
}
QMenu::separator {
height: 2px;
background-color: #CCCCCC;
margin: 6px 0;
}
/* Window */
MainWindow {
background-color: #F3F3F3;
}
MainWindow[custom-style=true] {
background-color: transparent;
}
QWidget#clock-widget {
font-size: 75px;
color: #333333;
font-weight: bold;
background-color: transparent;
}

View File

@@ -0,0 +1,34 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#include <QtWidgets/QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[]) {
qputenv("QT_WIN_DEBUG_CONSOLE", "attach");
qputenv("QSG_INFO", "1");
//qputenv("QT_WIDGETS_HIGHDPI_DOWNSCALE", "1");
//qputenv("QT_WIDGETS_RHI", "1");
//qputenv("QSG_RHI_BACKEND", "d3d12");
//qputenv("QSG_RHI_HDR", "scrgb");
//qputenv("QT_QPA_DISABLE_REDIRECTION_SURFACE", "1");
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}

View File

@@ -0,0 +1,385 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#include "mainwindow.h"
#include <QtCore/QDebug>
#include <QtCore/QFile>
#include <QtCore/QTime>
#include <QtCore/QTimer>
#include <QtGui/QPainter>
#include <QtGui/QWindow>
#include <QtWidgets/QApplication>
#include <QtWidgets/QStyle>
#include <QtWidgets/QPushButton>
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
# include <QtGui/QActionGroup>
#else
# include <QtWidgets/QActionGroup>
#endif
// #include <QtWebEngineWidgets/QWebEngineView>
#include <QWKWidgets/widgetwindowagent.h>
#include <widgetframe/windowbar.h>
#include <widgetframe/windowbutton.h>
class ClockWidget : public QLabel {
public:
explicit ClockWidget(QWidget *parent = nullptr) : QLabel(parent) {
startTimer(100);
setAlignment(Qt::AlignCenter);
}
~ClockWidget() override = default;
protected:
void timerEvent(QTimerEvent *event) override {
QLabel::timerEvent(event);
setText(QTime::currentTime().toString(QStringLiteral("hh:mm:ss")));
}
};
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
setAttribute(Qt::WA_DontCreateNativeAncestors);
installWindowAgent();
#if 1
auto clockWidget = new ClockWidget();
clockWidget->setObjectName(QStringLiteral("clock-widget"));
clockWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setCentralWidget(clockWidget);
#else
auto webView = new QWebEngineView();
webView->load(QUrl("https://www.baidu.com"));
setCentralWidget(webView);
#endif
loadStyleSheet(Dark);
setWindowTitle(tr("Example MainWindow"));
resize(800, 600);
// setFixedHeight(600);
// windowAgent->centralize();
}
static inline void emulateLeaveEvent(QWidget *widget) {
Q_ASSERT(widget);
if (!widget) {
return;
}
QTimer::singleShot(0, widget, [widget]() {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
const QScreen *screen = widget->screen();
#else
const QScreen *screen = widget->windowHandle()->screen();
#endif
const QPoint globalPos = QCursor::pos(screen);
if (!QRect(widget->mapToGlobal(QPoint{0, 0}), widget->size()).contains(globalPos)) {
QCoreApplication::postEvent(widget, new QEvent(QEvent::Leave));
if (widget->testAttribute(Qt::WA_Hover)) {
const QPoint localPos = widget->mapFromGlobal(globalPos);
const QPoint scenePos = widget->window()->mapFromGlobal(globalPos);
static constexpr const auto oldPos = QPoint{};
const Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0))
const auto event =
new QHoverEvent(QEvent::HoverLeave, scenePos, globalPos, oldPos, modifiers);
Q_UNUSED(localPos);
#elif (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0))
const auto event = new QHoverEvent(QEvent::HoverLeave, localPos, globalPos, oldPos, modifiers);
Q_UNUSED(scenePos);
#else
const auto event = new QHoverEvent(QEvent::HoverLeave, localPos, oldPos, modifiers);
Q_UNUSED(scenePos);
#endif
QCoreApplication::postEvent(widget, event);
}
}
});
}
MainWindow::~MainWindow() = default;
bool MainWindow::event(QEvent *event) {
switch (event->type()) {
case QEvent::WindowActivate: {
auto menu = menuWidget();
if (menu) {
menu->setProperty("bar-active", true);
style()->polish(menu);
}
break;
}
case QEvent::WindowDeactivate: {
auto menu = menuWidget();
if (menu) {
menu->setProperty("bar-active", false);
style()->polish(menu);
}
break;
}
default:
break;
}
return QMainWindow::event(event);
}
void MainWindow::installWindowAgent() {
// 1. Setup window agent
windowAgent = new QWK::WidgetWindowAgent(this);
windowAgent->setup(this);
// 2. Construct your title bar
auto menuBar = [this]() {
auto menuBar = new QMenuBar(this);
// Virtual menu
auto file = new QMenu(tr("File(&F)"), menuBar);
file->addAction(new QAction(tr("New(&N)"), menuBar));
file->addAction(new QAction(tr("Open(&O)"), menuBar));
file->addSeparator();
auto edit = new QMenu(tr("Edit(&E)"), menuBar);
edit->addAction(new QAction(tr("Undo(&U)"), menuBar));
edit->addAction(new QAction(tr("Redo(&R)"), menuBar));
// Theme action
auto darkAction = new QAction(tr("Enable dark theme"), menuBar);
darkAction->setCheckable(true);
connect(darkAction, &QAction::triggered, this, [this](bool checked) {
loadStyleSheet(checked ? Dark : Light); //
});
connect(this, &MainWindow::themeChanged, darkAction, [this, darkAction]() {
darkAction->setChecked(currentTheme == Dark); //
});
#ifdef Q_OS_WIN
auto noneAction = new QAction(tr("None"), menuBar);
noneAction->setData(QStringLiteral("none"));
noneAction->setCheckable(true);
noneAction->setChecked(true);
auto dwmBlurAction = new QAction(tr("Enable DWM blur"), menuBar);
dwmBlurAction->setData(QStringLiteral("dwm-blur"));
dwmBlurAction->setCheckable(true);
auto acrylicAction = new QAction(tr("Enable acrylic material"), menuBar);
acrylicAction->setData(QStringLiteral("acrylic-material"));
acrylicAction->setCheckable(true);
auto micaAction = new QAction(tr("Enable mica"), menuBar);
micaAction->setData(QStringLiteral("mica"));
micaAction->setCheckable(true);
auto micaAltAction = new QAction(tr("Enable mica alt"), menuBar);
micaAltAction->setData(QStringLiteral("mica-alt"));
micaAltAction->setCheckable(true);
auto winStyleGroup = new QActionGroup(menuBar);
winStyleGroup->addAction(noneAction);
winStyleGroup->addAction(dwmBlurAction);
winStyleGroup->addAction(acrylicAction);
winStyleGroup->addAction(micaAction);
winStyleGroup->addAction(micaAltAction);
connect(winStyleGroup, &QActionGroup::triggered, this,
[this, winStyleGroup](QAction *action) {
// Unset all custom style attributes first, otherwise the style will not display
// correctly
for (const QAction *_act : winStyleGroup->actions()) {
const QString data = _act->data().toString();
if (data.isEmpty() || data == QStringLiteral("none")) {
continue;
}
windowAgent->setWindowAttribute(data, false);
}
const QString data = action->data().toString();
if (data == QStringLiteral("none")) {
setProperty("custom-style", false);
} else if (!data.isEmpty()) {
windowAgent->setWindowAttribute(data, true);
setProperty("custom-style", true);
}
style()->polish(this);
});
#elif defined(Q_OS_MAC)
// Set whether to use system buttons (close/minimize/zoom)
// - true: Hide system buttons (use custom UI controls)
// - false: Show native system buttons (default behavior)
windowAgent->setWindowAttribute(QStringLiteral("no-system-buttons"), false);
auto darkBlurAction = new QAction(tr("Dark blur"), menuBar);
darkBlurAction->setCheckable(true);
connect(darkBlurAction, &QAction::toggled, this, [this](bool checked) {
if (!windowAgent->setWindowAttribute(QStringLiteral("blur-effect"), "dark")) {
return;
}
if (checked) {
setProperty("custom-style", true);
style()->polish(this);
}
});
auto lightBlurAction = new QAction(tr("Light blur"), menuBar);
lightBlurAction->setCheckable(true);
connect(lightBlurAction, &QAction::toggled, this, [this](bool checked) {
if (!windowAgent->setWindowAttribute(QStringLiteral("blur-effect"), "light")) {
return;
}
if (checked) {
setProperty("custom-style", true);
style()->polish(this);
}
});
auto noBlurAction = new QAction(tr("No blur"), menuBar);
noBlurAction->setCheckable(true);
connect(noBlurAction, &QAction::toggled, this, [this](bool checked) {
if (!windowAgent->setWindowAttribute(QStringLiteral("blur-effect"), "none")) {
return;
}
if (checked) {
setProperty("custom-style", false);
style()->polish(this);
}
});
auto macStyleGroup = new QActionGroup(menuBar);
macStyleGroup->addAction(darkBlurAction);
macStyleGroup->addAction(lightBlurAction);
macStyleGroup->addAction(noBlurAction);
#endif
// Real menu
auto settings = new QMenu(tr("Settings(&S)"), menuBar);
settings->addAction(darkAction);
#ifdef Q_OS_WIN
settings->addSeparator();
settings->addAction(noneAction);
settings->addAction(dwmBlurAction);
settings->addAction(acrylicAction);
settings->addAction(micaAction);
settings->addAction(micaAltAction);
#elif defined(Q_OS_MAC)
settings->addAction(darkBlurAction);
settings->addAction(lightBlurAction);
settings->addAction(noBlurAction);
#endif
menuBar->addMenu(file);
menuBar->addMenu(edit);
menuBar->addMenu(settings);
return menuBar;
}();
menuBar->setObjectName(QStringLiteral("win-menu-bar"));
auto titleLabel = new QLabel();
titleLabel->setAlignment(Qt::AlignCenter);
titleLabel->setObjectName(QStringLiteral("win-title-label"));
#ifndef Q_OS_MAC
auto iconButton = new QWK::WindowButton();
iconButton->setObjectName(QStringLiteral("icon-button"));
iconButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
auto pinButton = new QWK::WindowButton();
pinButton->setCheckable(true);
pinButton->setObjectName(QStringLiteral("pin-button"));
pinButton->setProperty("system-button", true);
pinButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
auto minButton = new QWK::WindowButton();
minButton->setObjectName(QStringLiteral("min-button"));
minButton->setProperty("system-button", true);
minButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
auto maxButton = new QWK::WindowButton();
maxButton->setCheckable(true);
maxButton->setObjectName(QStringLiteral("max-button"));
maxButton->setProperty("system-button", true);
maxButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
auto closeButton = new QWK::WindowButton();
closeButton->setObjectName(QStringLiteral("close-button"));
closeButton->setProperty("system-button", true);
closeButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
#endif
auto windowBar = new QWK::WindowBar();
#ifndef Q_OS_MAC
windowBar->setIconButton(iconButton);
windowBar->setPinButton(pinButton);
windowBar->setMinButton(minButton);
windowBar->setMaxButton(maxButton);
windowBar->setCloseButton(closeButton);
#endif
windowBar->setMenuBar(menuBar);
windowBar->setTitleLabel(titleLabel);
windowBar->setHostWidget(this);
windowAgent->setTitleBar(windowBar);
#ifndef Q_OS_MAC
windowAgent->setHitTestVisible(pinButton, true);
windowAgent->setSystemButton(QWK::WindowAgentBase::WindowIcon, iconButton);
windowAgent->setSystemButton(QWK::WindowAgentBase::Minimize, minButton);
windowAgent->setSystemButton(QWK::WindowAgentBase::Maximize, maxButton);
windowAgent->setSystemButton(QWK::WindowAgentBase::Close, closeButton);
#endif
windowAgent->setHitTestVisible(menuBar, true);
#if defined(Q_OS_MAC) && 0
windowAgent->setSystemButtonAreaCallback([](const QSize &size) {
static constexpr const int width = 75;
return QRect(QPoint(size.width() - width, 0), QSize(width, size.height())); //
});
#endif
setMenuWidget(windowBar);
#ifndef Q_OS_MAC
connect(windowBar, &QWK::WindowBar::pinRequested, this, [this, pinButton](bool pin) {
if (isHidden() || isMinimized() || isMaximized() || isFullScreen()) {
return;
}
setWindowFlag(Qt::WindowStaysOnTopHint, pin);
show();
pinButton->setChecked(pin);
});
connect(windowBar, &QWK::WindowBar::minimizeRequested, this, &QWidget::showMinimized);
connect(windowBar, &QWK::WindowBar::maximizeRequested, this, [this, maxButton](bool max) {
if (max) {
showMaximized();
} else {
showNormal();
}
// It's a Qt issue that if a QAbstractButton::clicked triggers a window's maximization,
// the button remains to be hovered until the mouse move. As a result, we need to
// manually send leave events to the button.
emulateLeaveEvent(maxButton);
});
connect(windowBar, &QWK::WindowBar::closeRequested, this, &QWidget::close);
#endif
}
void MainWindow::loadStyleSheet(Theme theme) {
if (!styleSheet().isEmpty() && theme == currentTheme)
return;
currentTheme = theme;
if (QFile qss(theme == Dark ? QStringLiteral(":/dark-style.qss")
: QStringLiteral(":/light-style.qss"));
qss.open(QIODevice::ReadOnly | QIODevice::Text)) {
setStyleSheet(QString::fromUtf8(qss.readAll()));
Q_EMIT themeChanged();
}
}

View File

@@ -0,0 +1,42 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QtWidgets/QMainWindow>
namespace QWK {
class WidgetWindowAgent;
class StyleAgent;
}
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
enum Theme {
Dark,
Light,
};
Q_ENUM(Theme)
Q_SIGNALS:
void themeChanged();
protected:
bool event(QEvent *event) override;
private:
void installWindowAgent();
void loadStyleSheet(Theme theme);
Theme currentTheme{};
QWK::WidgetWindowAgent *windowAgent;
};
#endif // MAINWINDOW_H

View File

@@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/">
<file>dark-style.qss</file>
<file>light-style.qss</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,10 @@
project(QWKExample_QML)
file(GLOB _src *.h *.cpp *.qrc)
qwk_add_example(${PROJECT_NAME}
FEATURES cxx_std_17
SOURCES ${_src} ../shared/resources/shared.qrc
QT_LINKS Core Gui Qml Quick
LINKS QWKQuick
)

View File

@@ -0,0 +1,260 @@
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import Qt.labs.platform 1.1
import QWindowKit 1.0
Window {
property bool showWhenReady: true
property alias titleBar: titleBar
id: window
width: 800
height: 600
color: darkStyle.windowBackgroundColor
title: qsTr("QWindowKit QtQuick Demo")
Component.onCompleted: {
windowAgent.setup(window)
windowAgent.setWindowAttribute("dark-mode", true)
if (window.showWhenReady) {
window.visible = true
}
}
QtObject {
id: lightStyle
}
QtObject {
id: darkStyle
readonly property color windowBackgroundColor: "#1E1E1E"
}
Timer {
interval: 100
running: true
repeat: true
onTriggered: timeLabel.text = Qt.formatTime(new Date(), "hh:mm:ss")
}
WindowAgent {
id: windowAgent
}
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: contextMenu.open()
}
Rectangle {
id: titleBar
anchors {
top: parent.top
left: parent.left
right: parent.right
}
height: 32
//color: window.active ? "#3C3C3C" : "#505050"
color: "transparent"
Component.onCompleted: windowAgent.setTitleBar(titleBar)
Image {
id: iconButton
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: 10
}
width: 18
height: 18
mipmap: true
source: "qrc:///app/example.png"
fillMode: Image.PreserveAspectFit
Component.onCompleted: windowAgent.setSystemButton(WindowAgent.WindowIcon, iconButton)
}
Text {
anchors {
verticalCenter: parent.verticalCenter
left: iconButton.right
leftMargin: 10
}
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: window.title
font.pixelSize: 14
color: "#ECECEC"
}
Row {
anchors {
top: parent.top
right: parent.right
}
height: parent.height
QWKButton {
id: minButton
height: parent.height
source: "qrc:///window-bar/minimize.svg"
onClicked: window.showMinimized()
Component.onCompleted: windowAgent.setSystemButton(WindowAgent.Minimize, minButton)
}
QWKButton {
id: maxButton
height: parent.height
source: window.visibility === Window.Maximized ? "qrc:///window-bar/restore.svg" : "qrc:///window-bar/maximize.svg"
onClicked: {
if (window.visibility === Window.Maximized) {
window.showNormal()
} else {
window.showMaximized()
}
}
Component.onCompleted: windowAgent.setSystemButton(WindowAgent.Maximize, maxButton)
}
QWKButton {
id: closeButton
height: parent.height
source: "qrc:///window-bar/close.svg"
background: Rectangle {
color: {
if (!closeButton.enabled) {
return "gray";
}
if (closeButton.pressed) {
return "#e81123";
}
if (closeButton.hovered) {
return "#e81123";
}
return "transparent";
}
}
onClicked: window.close()
Component.onCompleted: windowAgent.setSystemButton(WindowAgent.Close, closeButton)
}
}
}
Label {
id: timeLabel
anchors.centerIn: parent
font {
pointSize: 75
bold: true
}
color: "#FEFEFE"
Component.onCompleted: {
if ($curveRenderingAvailable) {
console.log("Curve rendering for text is available.")
timeLabel.renderType = Text.CurveRendering
}
}
}
Menu {
id: contextMenu
Menu {
id: themeMenu
title: qsTr("Theme")
MenuItemGroup {
id: themeMenuGroup
items: themeMenu.items
}
MenuItem {
text: qsTr("Light")
checkable: true
onTriggered: windowAgent.setWindowAttribute("dark-mode", false)
}
MenuItem {
text: qsTr("Dark")
checkable: true
checked: true
onTriggered: windowAgent.setWindowAttribute("dark-mode", true)
}
}
Menu {
id: specialEffectMenu
title: qsTr("Special effect")
MenuItemGroup {
id: specialEffectMenuGroup
items: specialEffectMenu.items
}
MenuItem {
enabled: Qt.platform.os === "windows"
text: qsTr("None")
checkable: true
checked: true
onTriggered: {
window.color = darkStyle.windowBackgroundColor
windowAgent.setWindowAttribute("dwm-blur", false)
windowAgent.setWindowAttribute("acrylic-material", false)
windowAgent.setWindowAttribute("mica", false)
windowAgent.setWindowAttribute("mica-alt", false)
}
}
MenuItem {
enabled: Qt.platform.os === "windows"
text: qsTr("DWM blur")
checkable: true
onTriggered: {
window.color = "transparent"
windowAgent.setWindowAttribute("acrylic-material", false)
windowAgent.setWindowAttribute("mica", false)
windowAgent.setWindowAttribute("mica-alt", false)
windowAgent.setWindowAttribute("dwm-blur", true)
}
}
MenuItem {
enabled: Qt.platform.os === "windows"
text: qsTr("Acrylic material")
checkable: true
onTriggered: {
window.color = "transparent"
windowAgent.setWindowAttribute("dwm-blur", false)
windowAgent.setWindowAttribute("mica", false)
windowAgent.setWindowAttribute("mica-alt", false)
windowAgent.setWindowAttribute("acrylic-material", true)
}
}
MenuItem {
enabled: Qt.platform.os === "windows"
text: qsTr("Mica")
checkable: true
onTriggered: {
window.color = "transparent"
windowAgent.setWindowAttribute("dwm-blur", false)
windowAgent.setWindowAttribute("acrylic-material", false)
windowAgent.setWindowAttribute("mica-alt", false)
windowAgent.setWindowAttribute("mica", true)
}
}
MenuItem {
enabled: Qt.platform.os === "windows"
text: qsTr("Mica Alt")
checkable: true
onTriggered: {
window.color = "transparent"
windowAgent.setWindowAttribute("dwm-blur", false)
windowAgent.setWindowAttribute("acrylic-material", false)
windowAgent.setWindowAttribute("mica", false)
windowAgent.setWindowAttribute("mica-alt", true)
}
}
}
}
}

View File

@@ -0,0 +1,40 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
Button {
id: root
width: height * 1.5
leftPadding: 0
topPadding: 0
rightPadding: 0
bottomPadding: 0
leftInset: 0
topInset: 0
rightInset: 0
bottomInset: 0
property alias source: image.source
contentItem: Item {
Image {
id: image
anchors.centerIn: parent
mipmap: true
width: 12
height: 12
fillMode: Image.PreserveAspectFit
}
}
background: Rectangle {
color: {
if (!root.enabled) {
return "gray";
}
if (root.pressed) {
return Qt.rgba(0, 0, 0, 0.15);
}
if (root.hovered) {
return Qt.rgba(0, 0, 0, 0.15);
}
return "transparent";
}
}
}

View File

@@ -0,0 +1,54 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#include <QtGui/QGuiApplication>
#include <QtQml/QQmlApplicationEngine>
#include <QtQml/QQmlContext>
#include <QtQuick/QQuickWindow>
#include <QWKQuick/qwkquickglobal.h>
#ifdef Q_OS_WIN
// Indicates to hybrid graphics systems to prefer the discrete part by default.
extern "C" {
Q_DECL_EXPORT unsigned long NvOptimusEnablement = 0x00000001;
Q_DECL_EXPORT int AmdPowerXpressRequestHighPerformance = 1;
}
#endif
int main(int argc, char *argv[]) {
qputenv("QT_WIN_DEBUG_CONSOLE", "attach"); // or "new": create a separate console window
qputenv("QSG_INFO", "1");
qputenv("QSG_NO_VSYNC", "1");
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
qputenv("QT_QUICK_CONTROLS_STYLE", "Basic");
#else
qputenv("QT_QUICK_CONTROLS_STYLE", "Default");
#endif
#ifdef Q_OS_WINDOWS
qputenv("QSG_RHI_BACKEND", "d3d11"); // options: d3d11, d3d12, opengl, vulkan
qputenv("QT_QPA_DISABLE_REDIRECTION_SURFACE", "1");
#endif
//qputenv("QSG_RHI_HDR", "scrgb"); // other options: hdr10, p3
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif
QGuiApplication application(argc, argv);
// Make sure alpha channel is requested, our special effects on Windows depends on it.
QQuickWindow::setDefaultAlphaBuffer(true);
QQmlApplicationEngine engine;
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
const bool curveRenderingAvailable = true;
#else
const bool curveRenderingAvailable = false;
#endif
engine.rootContext()->setContextProperty(QStringLiteral("$curveRenderingAvailable"), QVariant(curveRenderingAvailable));
QWK::registerTypes(&engine);
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
return application.exec();
}

View File

@@ -0,0 +1,42 @@
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
FramelessWindow {
property FramelessWindow childWindow: FramelessWindow {
showWhenReady: false
}
Drawer {
id: drawer
width: 0.66 * parent.width
height: parent.height
edge: Qt.RightEdge
onAboutToShow: titleBar.enabled = false
onAboutToHide: titleBar.enabled = true
Label {
text: "Content goes here!"
anchors.centerIn: parent
}
}
Row {
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: 20
}
spacing: 10
Button {
text: qsTr("Open Child Window")
onClicked: childWindow.visible = true
}
Button {
text: qsTr("Open Drawer")
onClicked: drawer.visible = true
}
}
}

View File

@@ -0,0 +1,7 @@
<RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>QWKButton.qml</file>
<file>FramelessWindow.qml</file>
</qresource>
</RCC>

View File

@@ -0,0 +1 @@
add_subdirectory(widgetframe)

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,13 @@
<RCC>
<qresource prefix="/">
<file>window-bar/close.svg</file>
<file>window-bar/fullscreen.svg</file>
<file>window-bar/maximize.svg</file>
<file>window-bar/minimize.svg</file>
<file>window-bar/restore.svg</file>
<file>window-bar/more-line.svg</file>
<file>window-bar/pin.svg</file>
<file>window-bar/pin-fill.svg</file>
<file>app/example.png</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,15 @@
<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" width="10.88" height="10.88"
viewBox="0 0 10.88 10.88">
<defs>
<style>
.cls-1 {
fill: none;
stroke: white;
stroke-miterlimit: 10;
stroke-width: 1.25px;
}
</style>
</defs>
<line class="cls-1" x1="0.44" y1="0.44" x2="10.44" y2="10.44" />
<line class="cls-1" x1="0.44" y1="10.44" x2="10.44" y2="0.44" />
</svg>

After

Width:  |  Height:  |  Size: 444 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1594017175519"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1933"
xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
<defs>
<style type="text/css"></style>
</defs>
<path
d="M874.666667 128h-170.666667a21.333333 21.333333 0 0 0 0 42.666667h119.168l-176.917333 176.917333a21.333333 21.333333 0 1 0 30.165333 30.165333L853.333333 200.832V320a21.333333 21.333333 0 0 0 42.666667 0V149.333333a21.333333 21.333333 0 0 0-21.333333-21.333333zM347.584 646.250667L170.666667 823.168V704a21.333333 21.333333 0 0 0-42.666667 0v170.666667a21.333333 21.333333 0 0 0 21.333333 21.333333h170.666667a21.333333 21.333333 0 0 0 0-42.666667H200.832l176.917333-176.917333a21.333333 21.333333 0 0 0-30.165333-30.165333zM874.666667 682.666667a21.333333 21.333333 0 0 0-21.333334 21.333333v119.168l-176.917333-176.917333a21.333333 21.333333 0 0 0-30.165333 30.165333L823.168 853.333333H704a21.333333 21.333333 0 0 0 0 42.666667h170.666667a21.333333 21.333333 0 0 0 21.333333-21.333333v-170.666667a21.333333 21.333333 0 0 0-21.333333-21.333333zM200.832 170.666667H320a21.333333 21.333333 0 0 0 0-42.666667H149.333333a21.333333 21.333333 0 0 0-21.333333 21.333333v170.666667a21.333333 21.333333 0 0 0 42.666667 0V200.832l176.917333 176.917333a21.333333 21.333333 0 0 0 30.165333-30.165333z"
fill="#ffffff" p-id="1934"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,12 @@
<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
<defs>
<style>
.cls-1 {
fill: none;
stroke: white;
stroke-miterlimit: 10;
}
</style>
</defs>
<rect class="cls-1" x="0.5" y="0.5" width="9" height="9" />
</svg>

After

Width:  |  Height:  |  Size: 328 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" viewBox="0 0 10 10" style="enable-background:new 0 0 10 10;" xml:space="preserve">
<style type="text/css">
.st0 {
fill: white;
}
</style>
<rect y="4.5" class="st0" width="10" height="1" />
</svg>

After

Width:  |  Height:  |  Size: 467 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path fill="white"
d="M4.5 10.5c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S6 12.825 6 12s-.675-1.5-1.5-1.5zm15 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S21 12.825 21 12s-.675-1.5-1.5-1.5zm-7.5 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5 1.5-.675 1.5-1.5-.675-1.5-1.5-1.5z" />
</svg>

After

Width:  |  Height:  |  Size: 419 B

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1735286082742" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="2501" width="512" height="512"
xmlns:xlink="http://www.w3.org/1999/xlink">
<path
d="M648.728381 130.779429a73.142857 73.142857 0 0 1 22.674286 15.433142l191.561143 191.756191a73.142857 73.142857 0 0 1-22.137905 118.564571l-67.876572 30.061715-127.341714 127.488-10.093714 140.239238a73.142857 73.142857 0 0 1-124.684191 46.445714l-123.66019-123.782095-210.724572 211.699809-51.833904-51.614476 210.846476-211.821714-127.926857-128.024381a73.142857 73.142857 0 0 1 46.299428-124.635429l144.237715-10.776381 125.074285-125.220571 29.379048-67.779048a73.142857 73.142857 0 0 1 96.207238-38.034285z"
p-id="2502" fill="white"></path>
</svg>

After

Width:  |  Height:  |  Size: 932 B

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1735285955420" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="2053" width="512" height="512"
xmlns:xlink="http://www.w3.org/1999/xlink">
<path
d="M648.728381 130.779429a73.142857 73.142857 0 0 1 22.674286 15.433142l191.561143 191.756191a73.142857 73.142857 0 0 1-22.137905 118.564571l-67.876572 30.061715-127.341714 127.488-10.093714 140.239238a73.142857 73.142857 0 0 1-124.684191 46.445714l-123.66019-123.782095-210.724572 211.699809-51.833904-51.614476 210.846476-211.821714-127.926857-128.024381a73.142857 73.142857 0 0 1 46.299428-124.635429l144.237715-10.776381 125.074285-125.220571 29.379048-67.779048a73.142857 73.142857 0 0 1 96.207238-38.034285z m-29.086476 67.120761l-34.913524 80.530286-154.087619 154.331429-171.398095 12.751238 303.323428 303.542857 12.044191-167.399619 156.233143-156.428191 80.384-35.59619-191.585524-191.73181z"
p-id="2054" fill="white"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,16 @@
<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
<defs>
<style>
.cls-1 {
fill: none;
stroke: white;
stroke-miterlimit: 10;
}
</style>
</defs>
<rect class="cls-1" x="0.5" y="2.5" width="9" height="9" />
<line class="cls-1" x1="2.5" y1="2.5" x2="2.5" y2="0.5" />
<line class="cls-1" x1="12" y1="0.5" x2="2" y2="0.5" />
<line class="cls-1" x1="11.5" y1="10" x2="11.5" />
<line class="cls-1" x1="10" y1="9.5" x2="12" y2="9.5" />
</svg>

After

Width:  |  Height:  |  Size: 559 B

View File

@@ -0,0 +1,17 @@
project(WidgetFrame)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
file(GLOB _src *.h *.cpp)
add_library(${PROJECT_NAME} STATIC)
qm_configure_target(${PROJECT_NAME}
FEATURES cxx_std_17
SOURCES ${_src}
QT_LINKS Core Gui Widgets
)
target_include_directories(${PROJECT_NAME} PUBLIC . ..)

View File

@@ -0,0 +1,408 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#include "windowbar.h"
#include "windowbar_p.h"
#include <QtCore/QDebug>
#include <QtCore/QLocale>
#include <QtGui/QtEvents>
namespace QWK {
WindowBarPrivate::WindowBarPrivate() {
w = nullptr;
autoTitle = true;
autoIcon = false;
}
WindowBarPrivate::~WindowBarPrivate() = default;
void WindowBarPrivate::init() {
Q_Q(WindowBar);
layout = new QHBoxLayout();
if (QLocale::system().textDirection() == Qt::RightToLeft) {
layout->setDirection(QBoxLayout::RightToLeft);
}
layout->setContentsMargins(QMargins());
layout->setSpacing(0);
for (int i = IconButton; i <= CloseButton; ++i) {
insertDefaultSpace(i);
}
q->setLayout(layout);
}
void WindowBarPrivate::setWidgetAt(int index, QWidget *widget) {
auto item = layout->takeAt(index);
auto orgWidget = item->widget();
if (orgWidget) {
orgWidget->deleteLater();
}
delete item;
if (!widget) {
insertDefaultSpace(index);
} else {
layout->insertWidget(index, widget);
}
}
QWidget *WindowBarPrivate::takeWidgetAt(int index) {
auto item = layout->itemAt(index);
auto orgWidget = item->widget();
if (orgWidget) {
item = layout->takeAt(index);
delete item;
insertDefaultSpace(index);
}
return orgWidget;
}
WindowBar::WindowBar(QWidget *parent) : WindowBar(*new WindowBarPrivate(), parent) {
}
WindowBar::~WindowBar() = default;
QMenuBar *WindowBar::menuBar() const {
Q_D(const WindowBar);
return static_cast<QMenuBar *>(d->widgetAt(WindowBarPrivate::MenuWidget));
}
QLabel *WindowBar::titleLabel() const {
Q_D(const WindowBar);
return static_cast<QLabel *>(d->widgetAt(WindowBarPrivate::TitleLabel));
}
QAbstractButton *WindowBar::iconButton() const {
Q_D(const WindowBar);
return static_cast<QAbstractButton *>(d->widgetAt(WindowBarPrivate::IconButton));
}
QAbstractButton *WindowBar::backButton() const {
Q_D(const WindowBar);
return static_cast<QAbstractButton *>(d->widgetAt(WindowBarPrivate::BackButton));
}
QAbstractButton *WindowBar::homeButton() const {
Q_D(const WindowBar);
return static_cast<QAbstractButton *>(d->widgetAt(WindowBarPrivate::HomeButton));
}
QAbstractButton *WindowBar::favButton() const {
Q_D(const WindowBar);
return static_cast<QAbstractButton *>(d->widgetAt(WindowBarPrivate::FavButton));
}
QWidget *WindowBar::centerWidget() const {
Q_D(const WindowBar);
return d->widgetAt(WindowBarPrivate::CenterWidget);
}
QAbstractButton *WindowBar::pinButton() const {
Q_D(const WindowBar);
return static_cast<QAbstractButton *>(d->widgetAt(WindowBarPrivate::PinButton));
}
QAbstractButton *WindowBar::minButton() const {
Q_D(const WindowBar);
return static_cast<QAbstractButton *>(d->widgetAt(WindowBarPrivate::MinimizeButton));
}
QAbstractButton *WindowBar::maxButton() const {
Q_D(const WindowBar);
return static_cast<QAbstractButton *>(d->widgetAt(WindowBarPrivate::MaximizeButton));
}
QAbstractButton *WindowBar::closeButton() const {
Q_D(const WindowBar);
return static_cast<QAbstractButton *>(d->widgetAt(WindowBarPrivate::CloseButton));
}
void WindowBar::setMenuBar(QMenuBar *menuBar) {
Q_D(WindowBar);
auto org = takeMenuBar();
if (org)
org->deleteLater();
if (!menuBar)
return;
d->setWidgetAt(WindowBarPrivate::MenuWidget, menuBar);
menuBar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Minimum);
}
void WindowBar::setTitleLabel(QLabel *label) {
Q_D(WindowBar);
auto org = takeTitleLabel();
if (org)
org->deleteLater();
if (!label)
return;
d->setWidgetAt(WindowBarPrivate::TitleLabel, label);
if (d->autoTitle && d->w)
label->setText(d->w->windowTitle());
label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
}
void WindowBar::setIconButton(QAbstractButton *btn) {
Q_D(WindowBar);
auto org = takeIconButton();
if (org)
org->deleteLater();
if (!btn)
return;
d->setWidgetAt(WindowBarPrivate::IconButton, btn);
if (d->autoIcon && d->w)
btn->setIcon(d->w->windowIcon());
btn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
}
void WindowBar::setBackButton(QAbstractButton *btn) {
Q_D(WindowBar);
auto org = takeBackButton();
if (org)
org->deleteLater();
if (!btn)
return;
d->setWidgetAt(WindowBarPrivate::BackButton, btn);
btn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
}
void WindowBar::setHomeButton(QAbstractButton *btn) {
Q_D(WindowBar);
auto org = takeHomeButton();
if (org)
org->deleteLater();
if (!btn)
return;
d->setWidgetAt(WindowBarPrivate::HomeButton, btn);
btn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
}
void WindowBar::setFavButton(QAbstractButton *btn) {
Q_D(WindowBar);
auto org = takeFavButton();
if (org)
org->deleteLater();
if (!btn)
return;
d->setWidgetAt(WindowBarPrivate::FavButton, btn);
btn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
}
void WindowBar::setCenterWidget(QWidget *widget) {
Q_D(WindowBar);
auto org = takeCenterWidget();
if (org)
org->deleteLater();
if (!widget)
return;
d->setWidgetAt(WindowBarPrivate::CenterWidget, widget);
widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
}
void WindowBar::setPinButton(QAbstractButton *btn) {
Q_D(WindowBar);
auto org = takePinButton();
if (org)
org->deleteLater();
if (!btn)
return;
d->setWidgetAt(WindowBarPrivate::PinButton, btn);
connect(btn, &QAbstractButton::clicked, this, &WindowBar::pinRequested);
}
void WindowBar::setMinButton(QAbstractButton *btn) {
Q_D(WindowBar);
auto org = takeMinButton();
if (org)
org->deleteLater();
if (!btn)
return;
d->setWidgetAt(WindowBarPrivate::MinimizeButton, btn);
connect(btn, &QAbstractButton::clicked, this, &WindowBar::minimizeRequested);
}
void WindowBar::setMaxButton(QAbstractButton *btn) {
Q_D(WindowBar);
auto org = takeMaxButton();
if (org)
org->deleteLater();
if (!btn)
return;
d->setWidgetAt(WindowBarPrivate::MaximizeButton, btn);
connect(btn, &QAbstractButton::clicked, this, &WindowBar::maximizeRequested);
}
void WindowBar::setCloseButton(QAbstractButton *btn) {
Q_D(WindowBar);
auto org = takeCloseButton();
if (org)
org->deleteLater();
if (!btn)
return;
d->setWidgetAt(WindowBarPrivate::CloseButton, btn);
connect(btn, &QAbstractButton::clicked, this, &WindowBar::closeRequested);
}
QMenuBar *WindowBar::takeMenuBar() {
Q_D(WindowBar);
return static_cast<QMenuBar *>(d->takeWidgetAt(WindowBarPrivate::MenuWidget));
}
QLabel *WindowBar::takeTitleLabel() {
Q_D(WindowBar);
return static_cast<QLabel *>(d->takeWidgetAt(WindowBarPrivate::TitleLabel));
}
QAbstractButton *WindowBar::takeIconButton() {
Q_D(WindowBar);
return static_cast<QAbstractButton *>(d->takeWidgetAt(WindowBarPrivate::IconButton));
}
QAbstractButton *WindowBar::takeBackButton() {
Q_D(WindowBar);
return static_cast<QAbstractButton *>(d->takeWidgetAt(WindowBarPrivate::BackButton));
}
QAbstractButton *WindowBar::takeHomeButton() {
Q_D(WindowBar);
return static_cast<QAbstractButton *>(d->takeWidgetAt(WindowBarPrivate::HomeButton));
}
QAbstractButton *WindowBar::takeFavButton() {
Q_D(WindowBar);
return static_cast<QAbstractButton *>(d->takeWidgetAt(WindowBarPrivate::FavButton));
}
QWidget *WindowBar::takeCenterWidget() {
Q_D(WindowBar);
return d->takeWidgetAt(WindowBarPrivate::CenterWidget);
}
QAbstractButton *WindowBar::takePinButton() {
Q_D(WindowBar);
auto btn = static_cast<QAbstractButton *>(d->takeWidgetAt(WindowBarPrivate::PinButton));
if (!btn) {
return nullptr;
}
disconnect(btn, &QAbstractButton::clicked, this, &WindowBar::pinRequested);
return btn;
}
QAbstractButton *WindowBar::takeMinButton() {
Q_D(WindowBar);
auto btn = static_cast<QAbstractButton *>(d->takeWidgetAt(WindowBarPrivate::MinimizeButton));
if (!btn) {
return nullptr;
}
disconnect(btn, &QAbstractButton::clicked, this, &WindowBar::minimizeRequested);
return btn;
}
QAbstractButton *WindowBar::takeMaxButton() {
Q_D(WindowBar);
auto btn = static_cast<QAbstractButton *>(d->takeWidgetAt(WindowBarPrivate::MaximizeButton));
if (!btn) {
return nullptr;
}
disconnect(btn, &QAbstractButton::clicked, this, &WindowBar::maximizeRequested);
return btn;
}
QAbstractButton *WindowBar::takeCloseButton() {
Q_D(WindowBar);
auto btn = static_cast<QAbstractButton *>(d->takeWidgetAt(WindowBarPrivate::CloseButton));
if (!btn) {
return nullptr;
}
disconnect(btn, &QAbstractButton::clicked, this, &WindowBar::closeRequested);
return btn;
}
QWidget *WindowBar::hostWidget() const {
Q_D(const WindowBar);
return d->w;
}
void WindowBar::setHostWidget(QWidget *w) {
Q_D(WindowBar);
QWidget *org = d->w;
if (org) {
org->removeEventFilter(this);
}
d_ptr->w = w;
if (w) {
w->installEventFilter(this);
}
}
bool WindowBar::titleFollowWindow() const {
Q_D(const WindowBar);
return d->autoTitle;
}
void WindowBar::setTitleFollowWindow(bool value) {
Q_D(WindowBar);
d->autoTitle = value;
}
bool WindowBar::iconFollowWindow() const {
Q_D(const WindowBar);
return d->autoIcon;
}
void WindowBar::setIconFollowWindow(bool value) {
Q_D(WindowBar);
d->autoIcon = value;
}
bool WindowBar::eventFilter(QObject *obj, QEvent *event) {
Q_D(WindowBar);
auto w = d->w;
if (obj == w) {
QAbstractButton *iconBtn = iconButton();
QLabel *label = titleLabel();
QAbstractButton *maxBtn = maxButton();
switch (event->type()) {
case QEvent::WindowIconChange: {
if (d_ptr->autoIcon && iconBtn) {
iconBtn->setIcon(w->windowIcon());
iconChanged(w->windowIcon());
}
break;
}
case QEvent::WindowTitleChange: {
if (d_ptr->autoTitle && label) {
label->setText(w->windowTitle());
titleChanged(w->windowTitle());
}
break;
}
case QEvent::WindowStateChange: {
if (maxBtn) {
maxBtn->setChecked(w->isMaximized());
}
break;
}
default:
break;
}
}
return QWidget::eventFilter(obj, event);
}
void WindowBar::titleChanged(const QString &text) {
Q_UNUSED(text)
}
void WindowBar::iconChanged(const QIcon &icon){Q_UNUSED(icon)}
WindowBar::WindowBar(WindowBarPrivate &d, QWidget *parent)
: QFrame(parent), d_ptr(&d) {
d.q_ptr = this;
d.init();
}
}

View File

@@ -0,0 +1,91 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef WINDOWBAR_H
#define WINDOWBAR_H
#include <QFrame>
#include <QAbstractButton>
#include <QMenuBar>
#include <QLabel>
#include <QWidget>
namespace QWK {
class WindowBarPrivate;
class WindowBar : public QFrame {
Q_OBJECT
Q_DECLARE_PRIVATE(WindowBar)
public:
explicit WindowBar(QWidget *parent = nullptr);
~WindowBar();
public:
QMenuBar *menuBar() const;
QLabel *titleLabel() const;
QAbstractButton *iconButton() const;
QAbstractButton *backButton() const;
QAbstractButton *homeButton() const;
QAbstractButton *favButton() const;
QWidget *centerWidget() const;
QAbstractButton *pinButton() const;
QAbstractButton *minButton() const;
QAbstractButton *maxButton() const;
QAbstractButton *closeButton() const;
void setMenuBar(QMenuBar *menuBar);
void setTitleLabel(QLabel *label);
void setIconButton(QAbstractButton *btn);
void setBackButton(QAbstractButton *btn);
void setHomeButton(QAbstractButton *btn);
void setFavButton(QAbstractButton *btn);
void setCenterWidget(QWidget *widget);
void setPinButton(QAbstractButton *btn);
void setMinButton(QAbstractButton *btn);
void setMaxButton(QAbstractButton *btn);
void setCloseButton(QAbstractButton *btn);
QMenuBar *takeMenuBar();
QLabel *takeTitleLabel();
QAbstractButton *takeIconButton();
QAbstractButton *takeBackButton();
QAbstractButton *takeHomeButton();
QAbstractButton *takeFavButton();
QWidget *takeCenterWidget();
QAbstractButton *takePinButton();
QAbstractButton *takeMinButton();
QAbstractButton *takeMaxButton();
QAbstractButton *takeCloseButton();
QWidget *hostWidget() const;
void setHostWidget(QWidget *w);
bool titleFollowWindow() const;
void setTitleFollowWindow(bool value);
bool iconFollowWindow() const;
void setIconFollowWindow(bool value);
Q_SIGNALS:
void pinRequested(bool pin = false);
void minimizeRequested();
void maximizeRequested(bool max = false);
void closeRequested();
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
virtual void titleChanged(const QString &text);
virtual void iconChanged(const QIcon &icon);
protected:
WindowBar(WindowBarPrivate &d, QWidget *parent = nullptr);
QScopedPointer<WindowBarPrivate> d_ptr;
};
}
#endif // WINDOWBAR_H

View File

@@ -0,0 +1,62 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef WINDOWBARPRIVATE_H
#define WINDOWBARPRIVATE_H
#include <QBoxLayout>
#include "windowbar.h"
namespace QWK {
class WindowBarPrivate {
Q_DECLARE_PUBLIC(WindowBar)
public:
WindowBarPrivate();
virtual ~WindowBarPrivate();
void init();
WindowBar *q_ptr;
QWidget *w;
bool autoTitle;
bool autoIcon;
enum WindowBarItem {
IconButton,
MenuWidget,
TitleLabel,
BackButton,
HomeButton,
FavButton,
CenterWidget,
PinButton,
MinimizeButton,
MaximizeButton,
CloseButton,
};
QHBoxLayout *layout;
inline QWidget *widgetAt(int index) const {
return layout->itemAt(index)->widget();
}
void setWidgetAt(int index, QWidget *widget);
QWidget *takeWidgetAt(int index);
inline void insertDefaultSpace(int index) {
layout->insertSpacerItem(index, new QSpacerItem(0, 0));
}
private:
Q_DISABLE_COPY(WindowBarPrivate)
};
}
#endif // WINDOWBARPRIVATE_H

View File

@@ -0,0 +1,94 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#include "windowbutton.h"
#include "windowbutton_p.h"
#include <QtCore/QDebug>
#include <QtGui/QtEvents>
namespace QWK {
WindowButtonPrivate::WindowButtonPrivate() = default;
WindowButtonPrivate::~WindowButtonPrivate() = default;
void WindowButtonPrivate::init() {
}
void WindowButtonPrivate::reloadIcon() {
Q_Q(WindowButton);
if (!q->isEnabled() && !iconDisabled.isNull()) {
q->setIcon(iconDisabled);
return;
}
if (q->isChecked() && !iconChecked.isNull()) {
q->setIcon(iconChecked);
return;
}
if (!iconNormal.isNull()) {
q->setIcon(iconNormal);
}
}
WindowButton::WindowButton(QWidget *parent) : WindowButton(*new WindowButtonPrivate(), parent) {
}
WindowButton::~WindowButton() = default;
QIcon WindowButton::iconNormal() const {
Q_D(const WindowButton);
return d->iconNormal;
}
void WindowButton::setIconNormal(const QIcon &icon) {
Q_D(WindowButton);
d->iconNormal = icon;
d->reloadIcon();
}
QIcon WindowButton::iconChecked() const {
Q_D(const WindowButton);
return d->iconChecked;
}
void WindowButton::setIconChecked(const QIcon &icon) {
Q_D(WindowButton);
d->iconChecked = icon;
d->reloadIcon();
}
QIcon WindowButton::iconDisabled() const {
Q_D(const WindowButton);
return d->iconDisabled;
}
void WindowButton::setIconDisabled(const QIcon &icon) {
Q_D(WindowButton);
d->iconDisabled = icon;
d->reloadIcon();
}
void WindowButton::checkStateSet() {
Q_D(WindowButton);
d->reloadIcon();
}
void WindowButton::mouseDoubleClickEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton) {
Q_EMIT doubleClicked();
}
}
WindowButton::WindowButton(WindowButtonPrivate &d, QWidget *parent)
: QPushButton(parent), d_ptr(&d) {
d.q_ptr = this;
d.init();
}
}

View File

@@ -0,0 +1,50 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef WINDOWBUTTON_H
#define WINDOWBUTTON_H
#include <QtWidgets/QPushButton>
namespace QWK {
class WindowButtonPrivate;
class WindowButton : public QPushButton {
Q_OBJECT
Q_DECLARE_PRIVATE(WindowButton)
Q_PROPERTY(QIcon iconNormal READ iconNormal WRITE setIconNormal FINAL)
Q_PROPERTY(QIcon iconChecked READ iconChecked WRITE setIconChecked FINAL)
Q_PROPERTY(QIcon iconDisabled READ iconDisabled WRITE setIconDisabled FINAL)
public:
explicit WindowButton(QWidget *parent = nullptr);
~WindowButton();
public:
QIcon iconNormal() const;
void setIconNormal(const QIcon &icon);
QIcon iconChecked() const;
void setIconChecked(const QIcon &icon);
QIcon iconDisabled() const;
void setIconDisabled(const QIcon &icon);
Q_SIGNALS:
void doubleClicked();
protected:
void checkStateSet() override;
void mouseDoubleClickEvent(QMouseEvent *event) override;
protected:
WindowButton(WindowButtonPrivate &d, QWidget *parent = nullptr);
QScopedPointer<WindowButtonPrivate> d_ptr;
};
}
#endif // WINDOWBUTTON_H

View File

@@ -0,0 +1,31 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef WINDOWBUTTONPRIVATE_H
#define WINDOWBUTTONPRIVATE_H
#include "windowbutton.h"
namespace QWK {
class WindowButtonPrivate {
Q_DECLARE_PUBLIC(WindowButton)
public:
WindowButtonPrivate();
virtual ~WindowButtonPrivate();
void init();
WindowButton *q_ptr;
QIcon iconNormal;
QIcon iconChecked;
QIcon iconDisabled;
void reloadIcon();
};
}
#endif // WINDOWBUTTONPRIVATE_H

View File

@@ -0,0 +1,130 @@
# References:
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
# https://code.qt.io/cgit/qt/qt5.git/tree/_clang-format
BasedOnStyle: LLVM
Standard: c++17
# 指针和引用的对齐方式。
# 可能的值有:
# PAS_Left (在配置中: Left) 指针左对齐。
# PAS_Right (在配置中: Right) 指针右对齐。
# PAS_Middle (在配置中: Middle) 指针中间对齐。
PointerAlignment: Right
# public/protected/private 等访问修饰符偏移量
AccessModifierOffset: -4
# 缩进长度
IndentWidth: 4
# 连续空行的最大数
MaxEmptyLinesToKeep: 999
# 在OC中的@property后面添加一个空格。例如使用“@property (readonly)”而不是“@property(readonly)”
ObjCSpaceAfterProperty: true
# OC块中所拍的字符数
ObjCBlockIndentWidth: 4
# 取决于值, 语句“int f() { return 0; }”可以被放到一个单行。
# 可能的值有:
# SFS_None (在配置中: None) 从不合并方法或函数到单独的一行。
# SFS_Empty (在配置中: Empty) 仅合并空的函数。
# SFS_Inline (在配置中: Inline) 仅合并类中定义的方法或函数. 意味着 “empty”.
# SFS_All (在配置中: All) 合并所有的方法适应单行.
AllowShortFunctionsOnASingleLine: None
# 如果为真true, 语句“if (a) return;” 能被放到单行。
AllowShortIfStatementsOnASingleLine: false
# 如果为真true, 对齐注释。
AlignTrailingComments: true
# 如果为真,对齐连续的宏定义
AlignConsecutiveMacros: true
# 如果为真true,将会在“[”之后和“]”之前插入空格。
SpacesInSquareBrackets: false
# 如果为真true, 将会在“(”之后和“)”之前插入空格。
SpacesInParentheses : false
# 如果为真true, 校准连续的声明。
# 这将会校准连续多行的声明的名字。这将会导致像下面这样的格式:
# int aaaa = 12;
# float b = 23;
# std::string ccc = 23;
AlignConsecutiveDeclarations: false
# 如果为真true连续调整多行
# 这将会调整连续行中的分配操作符。这将会导致像下面这样的格式:
# int aaaa = 12;
# int b = 23;
# int ccc = 23;
AlignConsecutiveAssignments: false
# 如果为假false移除分配操作符=)前空格。
SpaceBeforeAssignmentOperators: true
# 如果为真true, 将会在字面量容器中插入空格(例如 OC和Javascript的数组和字典字面量)。
SpacesInContainerLiterals: false
# 缩进case标签
IndentCaseLabels: true
# 如果表达式中包含函数调用,并且函数调用因为表达式太长被放到了下一行,是否缩进
IndentWrappedFunctionNames: true
# 如果为真true, 保持块的起始空行。
# true: false:
# if (foo) { vs. if (foo) {
# bar();
# bar(); }
# }
KeepEmptyLinesAtTheStartOfBlocks: true
# 允许所有参数都被放在下一行
AllowAllParametersOfDeclarationOnNextLine: false
# 使用C风格强制类型转换后是否在中间添加一个空格
SpaceAfterCStyleCast: true
# 在模板定义后换行
AlwaysBreakTemplateDeclarations: Yes
# Tab长度
TabWidth: 4
# 是否使用Tab
UseTab: Never
# 在括号后对齐参数
# someLongFunction(argument1,
# argument2);
AlignAfterOpenBracket: Align
# 名字空间内部缩进
NamespaceIndentation: All
# 一行最长列数
ColumnLimit: 100
# 按层次缩进宏定义
IndentPPDirectives: AfterHash
# 预处理语句缩进为 2
PPIndentWidth: 2
# 数组元素对齐
AlignArrayOfStructures: Left
# 不对头文件排序
SortIncludes: Never
FixNamespaceComments: false
StatementMacros: ['__qas_attr__', '__qas_exclude__', '__qas_include__']
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE ]

98
libs/qwindowkit/qmsetup/.gitignore vendored Normal file
View File

@@ -0,0 +1,98 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.log
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
# *.res
# *.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
__pycache__
*.pyc
# Binaries
# --------
*.dll
*.exe
.DS_Store
*/.DS_Store
build-src*
build/
cmake-build*
*.user
*.lnk
_workingDir*
.vscode
.idea
.cache
cache
.vs
out/
CMakeSettings.json
# /vcpkg
/data
/*.natvis
*.sublime-*
setup-vcpkg.json
setup-vcpkg-temp*

4
libs/qwindowkit/qmsetup/.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "syscmdline"]
path = src/syscmdline
url = ../../SineStriker/syscmdline.git
branch = main

View File

@@ -0,0 +1,46 @@
cmake_minimum_required(VERSION 3.19)
project(qmsetup VERSION 1.0.0.0)
# ----------------------------------
# Configure Options
# ----------------------------------
option(QMSETUP_VCPKG_TOOLS_HINT "Install executables to tools directory" OFF)
option(QMSETUP_STATIC_RUNTIME "Static link runtime libraries on Windows" ON)
# ----------------------------------
# CMake Settings
# ----------------------------------
if(NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
endif()
if(NOT DEFINED CMAKE_LIBRARY_OUTPUT_DIRECTORY)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
endif()
if(NOT DEFINED CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
endif()
if(MSVC)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /manifest:no")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /manifest:no")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /manifest:no")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8")
if(NOT DEFINED CMAKE_DEBUG_POSTFIX)
set(CMAKE_DEBUG_POSTFIX "d")
endif()
endif()
# ----------------------------------
# Project Variables
# ----------------------------------
set(QMSETUP_VERSION ${PROJECT_VERSION})
set(QMSETUP_INSTALL_NAME ${PROJECT_NAME})
set(QMSETUP_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
include(cmake/QMSetupAPI.cmake)
add_subdirectory(src)

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) Stdware Collections
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,252 @@
# QMSetup
**QMSetup** is a set of CMake Modules and Basic Libraries for C/C++ projects.
**This project is independent from Qt and other 3rdparty libraries.** Due to the fact that it encompasses some tools that need to be compiled, it's strongly not suggested to be included as a subproject.
## Features
### Helpful Functions
+ Path, Version functions
+ Target configuration functions
+ Qt related functions
### Generators
+ Generate Windows RC files, manifest files
+ Generate MacOS Bundle info files
+ Generate configuration header files
+ Generate Git information header files
### Filesystem Utilities
+ Reorganize header files
+ Copy files and directories after build
### Install Utilities
+ Deploy project dependencies and fix rpaths
### Extended Build Rules
+ Create translations with **Qt Linguist** tools
+ Create source files with **Protobuf** compiler
+ Create documentations with **Doxygen**
## Support Platforms
+ Microsoft Windows
+ Apple Macintosh
+ GNU/Linux
## Dependencies
### Required Packages
#### Windows
Windows deploy command acquires the shared library paths by reading the PE files and searching the specified paths so that it doesn't depend on `dumpbin` tool.
#### Unix
Unix deploy command acquires the shared library paths by running `ldd`/`otool` command and fixes the *rpath*s by runing the `patchelf`/`install_name_tool` command, make sure you have installed them.
```sh
sudo apt install patchelf
```
### Build System
+ C++ 17
+ CMake 3.19
### Open-Source Libraries
+ https://github.com/SineStriker/syscmdline
+ https://github.com/jothepro/doxygen-awesome-css
## Integrate
### Clone
Via Https
```sh
git clone --recursive https://github.com/stdware/qmsetup.git
```
Via SSH
```sh
git clone --recursive git@github.com:stdware/qmsetup.git
```
### Preinstall (Suggested)
#### Build & Install
```sh
cmake -B build -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/path/to
cmake --build build --target all
cmake --build build --target install
```
#### Import
```sh
cmake -Dqmsetup_DIR=/path/to/lib/cmake/qmsetup ...
```
```cmake
find_package(qmsetup REQUIRED)
```
### Sub-project
It still needs to be installed, but the installation occurs during the CMake Configure phase and is executed only once.
```cmake
find_package(qmsetup QUIET)
if (NOT TARGET qmsetup::library)
# Modify this variable according to your project structure
set(_source_dir ${CMAKE_CURRENT_SOURCE_DIR}/qmsetup)
# Import install function
include("${_source_dir}/cmake/modules/private/InstallPackage.cmake")
# Install package in place
set(_package_path)
qm_install_package(qmsetup
SOURCE_DIR ${_source_dir}
BUILD_TYPE Release
RESULT_PATH _package_path
)
# Find package again
find_package(qmsetup REQUIRED PATHS ${_package_path})
# Update import path
set(qmsetup_DIR ${_package_path} CACHE PATH "" FORCE)
endif()
```
## Quick Start
### Examples
You can use the functions in this library to greatly simplify several kinds of common build rules.
#### Generate Configuration Header
```cmake
qm_import(Preprocess)
qm_add_definition(FOO false)
qm_add_definition(BAR 114514)
qm_add_definition(BAZ "ABC" STRING_LITERAL)
qm_generate_config(${CMAKE_BINARY_DIR}/conf.h)
```
#### Reorganize Include Directory
```cmake
qm_import(Preprocess)
qm_sync_include(src/core ${CMAKE_BINARY_DIR}/include/MyCore
INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR}/MyCore
)
```
#### Sync Resource Files After Build
```cmake
qm_import(Filesystem)
qm_add_copy_command(${PROJECT_NAME}
SOURCES
file.txt
dir_to_copy
dir_contents_to_copy/
DESTINATION .
)
```
#### Deploy Project And All Dependencies
```cmake
qm_import(Deploy)
qm_deploy_directory("${CMAKE_INSTALL_PREFIX}"
COMMENT "Deploy project spectacularly"
PLUGINS "iconengines/qsvgicon" "bearer/qgenericbearer"
QML Qt QtQml
PLUGIN_DIR share/plugins
QML_DIR share/qml
)
```
#### Add Qt Translations
```cmake
qm_import(Translate)
qm_find_qt(LinguistTools)
qm_add_translation(${PROJECT_NAME}_translations
LOCALES ja_JP zh_CN zh_TW
PREFIX ${PROJECT_NAME}
TARGETS ${PROJECT_NAME}
TS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/translations
QM_DIR ${CMAKE_CURRENT_BINARY_DIR}/translations
)
```
#### Generate Protubuf Source Files
```cmake
qm_import(Protobuf)
find_package(Protobuf REQUIRED)
qm_create_protobuf(_proto_src
INPUT a.proto b.proto
INCLUDE_DIRECTORIES src/proto
OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/proto
)
target_sources(${PROJECT_NAME} PUBLIC ${_proto_src})
```
#### Generate Doxygen HTML Documentations
```cmake
qm_import(Doxygen)
find_package(Doxygen REQUIRED)
qm_setup_doxygen(${PROJECT_NAME}_RunDoxygen
NAME ${PROJECT_NAME}
DESCRIPTION "my project"
MDFILE "${CMAKE_SOURCE_DIR}/README.md"
OUTPUT_DIR "${CMAK_BINARY_DIR}/doc"
INPUT src
TARGETS ${PROJECT_NAME}
DEPENDS ${PROJECT_NAME}
NO_EXPAND_MACROS
Q_OBJECT
Q_GADGET
Q_DECLARE_TR_FUNCTIONS
COMPILE_DEFINITIONS
Q_SIGNALS=Q_SIGNALS
Q_SLOTS=Q_SLOTS
GENERATE_TAGFILE "${PROJECT_NAME}_tagfile.xml"
INSTALL_DIR "doc"
)
```
### Find Modules
Use `qm_find_package` to find supported third-party packages.
+ YY-Thunks: https://github.com/Chuyu-Team/YY-Thunks
+ VC-LTL5: https://github.com/Chuyu-Team/VC-LTL5
### Detailed Documentations
+ [Core Command](docs/core-command.md)
The CMake Modules documentations is provided in the comments.
See `examples` to get detailed use cases.
## Contributors
+ [SineStriker](https://github.com/SineStriker)
+ [wangwenx190](https://github.com/wangwenx190)
+ [RigoLigoRLC](https://github.com/RigoLigoRLC)
+ [CrSjimo](https://github.com/CrSjimo)
## License
QMSetup is licensed under the MIT License.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,157 @@
/**
Doxygen Awesome
https://github.com/jothepro/doxygen-awesome-css
MIT License
Copyright (c) 2021 - 2023 jothepro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
class DoxygenAwesomeDarkModeToggle extends HTMLElement {
// SVG icons from https://fonts.google.com/icons
// Licensed under the Apache 2.0 license:
// https://www.apache.org/licenses/LICENSE-2.0.html
static lightModeIcon = `<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#FCBF00"><rect fill="none" height="24" width="24"/><circle cx="12" cy="12" opacity=".3" r="3"/><path d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"/></svg>`
static darkModeIcon = `<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#FE9700"><rect fill="none" height="24" width="24"/><path d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27 C17.45,17.19,14.93,19,12,19c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z" opacity=".3"/><path d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"/></svg>`
static title = "Toggle Light/Dark Mode"
static prefersLightModeInDarkModeKey = "prefers-light-mode-in-dark-mode"
static prefersDarkModeInLightModeKey = "prefers-dark-mode-in-light-mode"
static _staticConstructor = function() {
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.userPreference)
// Update the color scheme when the browsers preference changes
// without user interaction on the website.
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged()
})
// Update the color scheme when the tab is made visible again.
// It is possible that the appearance was changed in another tab
// while this tab was in the background.
document.addEventListener("visibilitychange", visibilityState => {
if (document.visibilityState === 'visible') {
DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged()
}
});
}()
static init() {
$(function() {
$(document).ready(function() {
const toggleButton = document.createElement('doxygen-awesome-dark-mode-toggle')
toggleButton.title = DoxygenAwesomeDarkModeToggle.title
toggleButton.updateIcon()
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
toggleButton.updateIcon()
})
document.addEventListener("visibilitychange", visibilityState => {
if (document.visibilityState === 'visible') {
toggleButton.updateIcon()
}
});
$(document).ready(function(){
document.getElementById("MSearchBox").parentNode.appendChild(toggleButton)
})
$(window).resize(function(){
document.getElementById("MSearchBox").parentNode.appendChild(toggleButton)
})
})
})
}
constructor() {
super();
this.onclick=this.toggleDarkMode
}
/**
* @returns `true` for dark-mode, `false` for light-mode system preference
*/
static get systemPreference() {
return window.matchMedia('(prefers-color-scheme: dark)').matches
}
/**
* @returns `true` for dark-mode, `false` for light-mode user preference
*/
static get userPreference() {
return (!DoxygenAwesomeDarkModeToggle.systemPreference && localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey)) ||
(DoxygenAwesomeDarkModeToggle.systemPreference && !localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey))
}
static set userPreference(userPreference) {
DoxygenAwesomeDarkModeToggle.darkModeEnabled = userPreference
if(!userPreference) {
if(DoxygenAwesomeDarkModeToggle.systemPreference) {
localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey, true)
} else {
localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey)
}
} else {
if(!DoxygenAwesomeDarkModeToggle.systemPreference) {
localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey, true)
} else {
localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey)
}
}
DoxygenAwesomeDarkModeToggle.onUserPreferenceChanged()
}
static enableDarkMode(enable) {
if(enable) {
DoxygenAwesomeDarkModeToggle.darkModeEnabled = true
document.documentElement.classList.add("dark-mode")
document.documentElement.classList.remove("light-mode")
} else {
DoxygenAwesomeDarkModeToggle.darkModeEnabled = false
document.documentElement.classList.remove("dark-mode")
document.documentElement.classList.add("light-mode")
}
}
static onSystemPreferenceChanged() {
DoxygenAwesomeDarkModeToggle.darkModeEnabled = DoxygenAwesomeDarkModeToggle.userPreference
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled)
}
static onUserPreferenceChanged() {
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled)
}
toggleDarkMode() {
DoxygenAwesomeDarkModeToggle.userPreference = !DoxygenAwesomeDarkModeToggle.userPreference
this.updateIcon()
}
updateIcon() {
if(DoxygenAwesomeDarkModeToggle.darkModeEnabled) {
this.innerHTML = DoxygenAwesomeDarkModeToggle.darkModeIcon
} else {
this.innerHTML = DoxygenAwesomeDarkModeToggle.lightModeIcon
}
}
}
customElements.define("doxygen-awesome-dark-mode-toggle", DoxygenAwesomeDarkModeToggle);

View File

@@ -0,0 +1,40 @@
/**
Doxygen Awesome
https://github.com/jothepro/doxygen-awesome-css
MIT License
Copyright (c) 2021 - 2023 jothepro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
@media screen and (min-width: 768px) {
#MSearchBox {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - var(--searchbar-height) - 1px);
}
#MSearchField {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 66px - var(--searchbar-height));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
table.memname tr {
float: left;
}
.memname td {
padding: 0;
}
.memname tr:last-child:not(:first-child) {
margin-left: -0.75em
}
html {
--external-link-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="%231779c4" d="M10.12,7.5H9.38A.38.38,0,0,0,9,7.88V10.5H1.5V3H4.88a.38.38,0,0,0,.37-.38V1.88a.38.38,0,0,0-.37-.38H1.12A1.12,1.12,0,0,0,0,2.62v8.26A1.12,1.12,0,0,0,1.12,12H9.38a1.12,1.12,0,0,0,1.12-1.12h0v-3A.38.38,0,0,0,10.12,7.5ZM11.44,0h-3A.56.56,0,0,0,8,1l.84.84L3.16,7.51a.56.56,0,0,0,0,.79h0l.54.53a.56.56,0,0,0,.79,0h0L10.2,3.12,11,4a.56.56,0,0,0,1-.4v-3A.56.56,0,0,0,11.44,0Z"/></svg>')
}
@media (prefers-color-scheme: dark) {
html:not(.light-mode) {
--external-link-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="%231982d2" d="M10.12,7.5H9.38A.38.38,0,0,0,9,7.88V10.5H1.5V3H4.88a.38.38,0,0,0,.37-.38V1.88a.38.38,0,0,0-.37-.38H1.12A1.12,1.12,0,0,0,0,2.62v8.26A1.12,1.12,0,0,0,1.12,12H9.38a1.12,1.12,0,0,0,1.12-1.12h0v-3A.38.38,0,0,0,10.12,7.5ZM11.44,0h-3A.56.56,0,0,0,8,1l.84.84L3.16,7.51a.56.56,0,0,0,0,.79h0l.54.53a.56.56,0,0,0,.79,0h0L10.2,3.12,11,4a.56.56,0,0,0,1-.4v-3A.56.56,0,0,0,11.44,0Z"/></svg>')
}
}
a[href^='http://'], a[href^='https://'] {
background-image: var(--external-link-icon);
background-repeat: no-repeat;
background-position: center right;
background-size: 0.75em;
padding-right: 0.875em;
}

View File

@@ -0,0 +1,90 @@
<!-- HTML header for doxygen 1.9.8-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="$langISO">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=11" />
<meta name="generator" content="Doxygen $doxygenversion" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!--BEGIN PROJECT_NAME-->
<title>$projectname: $title</title>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<title>$title</title>
<!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css" />
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN FULL_SIDEBAR-->
<script type="text/javascript">
var page_layout = 1;
</script>
<!--END FULL_SIDEBAR-->
<!--END DISABLE_INDEX-->
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$treeview $search $mathjax $darkmode
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" /> $extrastylesheet
<script type="text/javascript" src="$relpath^doxygen-awesome-darkmode-toggle.js"></script>
<script type="text/javascript">
DoxygenAwesomeDarkModeToggle.init()
</script>
</head>
<body>
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN FULL_SIDEBAR-->
<div id="side-nav" class="ui-resizable side-nav-resizable">
<!-- do not remove this div, it is closed by doxygen! -->
<!--END FULL_SIDEBAR-->
<!--END DISABLE_INDEX-->
<div id="top">
<!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr id="projectrow">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo" /></td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td id="projectalign">
<div id="projectname">$projectname
<!--BEGIN PROJECT_NUMBER--><span id="projectnumber">&#160;$projectnumber</span>
<!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF-->
<div id="projectbrief">$projectbrief</div>
<!--END PROJECT_BRIEF-->
</td>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<!--BEGIN PROJECT_BRIEF-->
<td>
<div id="projectbrief">$projectbrief</div>
</td>
<!--END PROJECT_BRIEF-->
<!--END !PROJECT_NAME-->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<!--BEGIN !FULL_SIDEBAR-->
<td>$searchbox</td>
<!--END !FULL_SIDEBAR-->
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
<!--BEGIN SEARCHENGINE-->
<!--BEGIN FULL_SIDEBAR-->
<tr>
<td colspan="2">$searchbox</td>
</tr>
<!--END FULL_SIDEBAR-->
<!--END SEARCHENGINE-->
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->

View File

@@ -0,0 +1,102 @@
# https://github.com/Chuyu-Team/VC-LTL5
cmake_minimum_required(VERSION 3.13)
if(NOT MSVC OR DEFINED __VC_LTL_CMAKE_INCLUDE_GUARD)
return()
endif()
set(__VC_LTL_CMAKE_INCLUDE_GUARD 1)
#
# VC-LTL自动化加载配置建议你将此文件单独复制到你的工程再使用该文件能自动识别当前环境是否存在VC-LTL并且自动应用。
#
# 使用方法:
# 1. 在“CMakeLists.txt” 添加 “include("VC-LTL helper for cmake.cmake")”。
#
# VC-LTL默认搜索顺序
# 1. “VC-LTL helper for cmake.cmake”所在根目录即 ${CMAKE_CURRENT_LIST_DIR}
# 2. 当前CMake根目录即 ${CMAKE_CURRENT_SOURCE_DIR}/VC-LTL
# 3. 当前项目根目录,即 ${PROJECT_SOURCE_DIR}/VC-LTL
# 4. 当前CMake父目录即 ${CMAKE_CURRENT_SOURCE_DIR}/../VC-LTL
# 5. 当前项目根目录,即 ${PROJECT_SOURCE_DIR}/../VC-LTL
# 6. 注册表HKEY_CURRENT_USER\Code\VC-LTL@Root
#
# 把VC-LTL放在其中一个位置即可VC-LTL就能被自动引用。
#
# 如果你对默认搜索顺序不满,你可以修改此文件。你也可以直接指定${VC_LTL_Root}宏更加任性的去加载VC-LTL。
#
#####################################################################VC-LTL设置#####################################################################
#控制最小兼容系统版本目前可用版本为5.1.2600.0 6.0.6000.0(默认) 6.2.9200.0 10.0.10240.0 10.0.19041.0
#注意VC-LTL依赖YY-Thunks否则可能无法兼容早期系统。如果需要支持Windows XP该值必须为5.1.2600.0。
if(NOT DEFINED WindowsTargetPlatformMinVersion)
set(WindowsTargetPlatformMinVersion "10.0.19041.0" CACHE STRING "" FORCE)
endif()
#VC-LTL使用的CRT模式SupportLTL可能值为
# * false禁用VC_LTL
# * true默认值让VC-LTL自动适应。当最小兼容版本>=10.0时使用ucrt模式其他系统使用msvcrt模式。
# * msvcrt使用msvcrt.dll作为CRT。注意msvcrt模式可能不完全支持所有ucrt的新功能。比如setloacl不支持UTF8。
# * ucrt使用ucrtbase.dll作为CRT。注意早期系统可能需要下载VC-LTL.Redist.Dlls.zip感谢msvcr14x项目提供兼容XP系统的ucrtbase.dll。
#如果需要兼容XP时也使用ucrt请指定SupportLTL=ucrt。
#set(SupportLTL "ucrt")
#(PR#70 引入)默认关开启后将使用cmake `INTERFACE`能力,然后单独`target_link_directories(工程名称 VC_LTL)` 即可引用
#option(VC_LTL_EnableCMakeInterface "VC_LTL_EnableCMakeInterface" on)
####################################################################################################################################################
if(NOT VC_LTL_Root)
if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/_msvcrt.h)
set(VC_LTL_Root ${CMAKE_CURRENT_LIST_DIR})
endif()
endif()
if(NOT VC_LTL_Root)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/VC-LTL/_msvcrt.h)
set(VC_LTL_Root ${CMAKE_CURRENT_SOURCE_DIR}/VC-LTL)
endif()
endif()
if(NOT VC_LTL_Root)
if(EXISTS ${PROJECT_SOURCE_DIR}/VC-LTL/_msvcrt.h)
set(VC_LTL_Root ${PROJECT_SOURCE_DIR}/VC-LTL)
endif()
endif()
if(NOT VC_LTL_Root)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/../VC-LTL/_msvcrt.h)
set(VC_LTL_Root ${CMAKE_CURRENT_SOURCE_DIR}/../VC-LTL)
endif()
endif()
if(NOT VC_LTL_Root)
if(EXISTS ${PROJECT_SOURCE_DIR}/../VC-LTL/_msvcrt.h)
set(VC_LTL_Root ${PROJECT_SOURCE_DIR}/../VC-LTL)
endif()
endif()
if(NOT VC_LTL_Root)
EXECUTE_PROCESS(COMMAND reg query "HKEY_CURRENT_USER\\Code\\VC-LTL" -v "Root"
OUTPUT_VARIABLE FOUND_FILE
ERROR_VARIABLE ERROR_INFO
)
string(REGEX MATCH "[a-zA-Z]:\\\\.+\\\\"
FOUND_LTL
${FOUND_FILE})
if (NOT ${FOUND_LTL} STREQUAL "")
set(VC_LTL_Root ${FOUND_LTL})
endif()
if(NOT DEFINED VC_LTL_Root)
string(REGEX MATCH "\\\\\\\\.+\\\\" FOUND_LTL ${FOUND_FILE})
if (NOT ${FOUND_LTL} STREQUAL "")
set(VC_LTL_Root ${FOUND_LTL})
endif()
endif()
endif()
if(VC_LTL_Root)
include("${VC_LTL_Root}\\config\\config.cmake")
endif()

View File

@@ -0,0 +1,102 @@
#[[
MIT License
Copyright (C) 2023 by wangwenx190 (Yuhang Zhao)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
# https://github.com/Chuyu-Team/YY-Thunks
if(NOT MSVC OR DEFINED __YY_THUNKS_CMAKE_INCLUDE_GUARD)
return()
endif()
set(__YY_THUNKS_CMAKE_INCLUDE_GUARD 1)
if(NOT DEFINED YYTHUNKS_TARGET_OS)
set(YYTHUNKS_TARGET_OS "Vista" CACHE STRING "Supported values: Vista, WinXP" FORCE)
endif()
set(__yy_thunks_dir "")
if(DEFINED ENV{YYTHUNKS_INSTALL_DIR})
set(__env "$ENV{YYTHUNKS_INSTALL_DIR}")
if(NOT "x${__env}" STREQUAL "x" AND EXISTS "${__env}")
set(__yy_thunks_dir "${__env}")
endif()
endif()
if("x${__yy_thunks_dir}" STREQUAL "x")
set(__yy_thunks_reg "")
cmake_host_system_information(RESULT __yy_thunks_reg
QUERY WINDOWS_REGISTRY "HKCU/Code/YY-Thunks"
VALUE "Root")
if(NOT "x${__yy_thunks_reg}" STREQUAL "x" AND EXISTS "${__yy_thunks_reg}")
set(__yy_thunks_dir "${__yy_thunks_reg}")
elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/YY-Thunks")
set(__yy_thunks_dir "${CMAKE_CURRENT_LIST_DIR}/YY-Thunks")
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/YY-Thunks")
set(__yy_thunks_dir "${CMAKE_CURRENT_SOURCE_DIR}/YY-Thunks")
elseif(EXISTS "${PROJECT_SOURCE_DIR}/YY-Thunks")
set(__yy_thunks_dir "${PROJECT_SOURCE_DIR}/YY-Thunks")
elseif(EXISTS "${CMAKE_SOURCE_DIR}/YY-Thunks")
set(__yy_thunks_dir "${CMAKE_SOURCE_DIR}/YY-Thunks")
elseif(EXISTS "${CMAKE_SOURCE_DIR}/../YY-Thunks")
set(__yy_thunks_dir "${CMAKE_SOURCE_DIR}/../YY-Thunks")
endif()
endif()
if(NOT "x${__yy_thunks_dir}" STREQUAL "x")
cmake_path(NORMAL_PATH __yy_thunks_dir OUTPUT_VARIABLE __yy_thunks_dir)
endif()
if(NOT "x${__yy_thunks_dir}" STREQUAL "x" AND EXISTS "${__yy_thunks_dir}")
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(__yy_thunks_arch x64)
else()
set(__yy_thunks_arch x86)
endif()
set(__yy_thunks_obj "${__yy_thunks_dir}/objs/${__yy_thunks_arch}/YY_Thunks_for_${YYTHUNKS_TARGET_OS}.obj")
cmake_path(NORMAL_PATH __yy_thunks_obj OUTPUT_VARIABLE __yy_thunks_obj)
if(EXISTS "${__yy_thunks_obj}")
set(YYTHUNKS_FOUND TRUE CACHE BOOL "" FORCE)
set(YYTHUNKS_INSTALL_DIR "${__yy_thunks_dir}" CACHE STRING "" FORCE)
set(YYTHUNKS_ARCH "${__yy_thunks_arch}" CACHE STRING "" FORCE)
set(YYTHUNKS_OBJ_FILE "YY_Thunks_for_${YYTHUNKS_TARGET_OS}.obj" CACHE STRING "" FORCE)
set(YYTHUNKS_OBJ_PATH "${__yy_thunks_obj}" CACHE STRING "" FORCE)
add_link_options("${__yy_thunks_obj}")
message("###################################################################################################")
message("# #")
message("# ██ ██ ██ ██ ████████ ██ ██ ██ ██ ███ ██ ██ ██ ███████ #")
message("# ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ #")
message("# ████ ████ █████ ██ ███████ ██ ██ ██ ██ ██ █████ ███████ #")
message("# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ #")
message("# ██ ██ ██ ██ ██ ██████ ██ ████ ██ ██ ███████ #")
message("# #")
message("###################################################################################################")
message("")
message(" YY-Thunks install dir :" ${__yy_thunks_dir})
message(" YY-Thunks target platform :" ${YYTHUNKS_TARGET_OS})
message(" YY-Thunks obj file path :" ${__yy_thunks_obj})
message("")
else()
message(WARNING "YY-Thunks's obj file is missing!")
endif()
else()
message(WARNING "Can't locate the YY-Thunks installation directory!")
endif()

View File

@@ -0,0 +1,238 @@
include_guard(DIRECTORY)
#[[
Disable all possible warnings from the compiler.
qm_compiler_no_warnings()
]] #
macro(qm_compiler_no_warnings)
foreach(__lang C CXX)
if(NOT "x${CMAKE_${__lang}_FLAGS}" STREQUAL "x")
if(MSVC)
string(REGEX REPLACE " [/-]W[01234] " " " CMAKE_${__lang}_FLAGS ${CMAKE_${__lang}_FLAGS})
else()
string(REGEX REPLACE " -W(all)?(extra)? " " " CMAKE_${__lang}_FLAGS ${CMAKE_${__lang}_FLAGS})
string(REGEX REPLACE " -[W]?pedantic " " " CMAKE_${__lang}_FLAGS ${CMAKE_${__lang}_FLAGS})
endif()
endif()
string(APPEND CMAKE_${__lang}_FLAGS " -w ")
endforeach()
if(MSVC)
add_compile_definitions(-D_CRT_NON_CONFORMING_SWPRINTFS)
add_compile_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_DEPRECATE)
add_compile_definitions(-D_CRT_NONSTDC_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE)
add_compile_definitions(-D_SCL_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_DEPRECATE)
add_compile_definitions(-D_SILENCE_ALL_MS_EXT_DEPRECATION_WARNINGS)
else()
foreach(__lang C CXX)
string(APPEND CMAKE_${__lang}_FLAGS " -fpermissive ")
endforeach()
endif()
endmacro()
#[[
Enable all possible warnings from the compiler.
qm_compiler_max_warnings()
]] #
function(qm_compiler_max_warnings)
if(MSVC)
add_compile_options(-W4)
elseif("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xClang")
add_compile_options(-Weverything)
else()
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
endfunction()
#[[
Treat all warnings as errors.
qm_compiler_warnings_are_errors()
]] #
function(qm_compiler_warnings_are_errors)
if(MSVC)
add_compile_options(-WX)
else()
add_compile_options(-Werror)
endif()
endfunction()
#[[
Prevent the compiler from receiving unknown parameters.
qm_compiler_no_unknown_options()
]] #
function(qm_compiler_no_unknown_options)
if(MSVC)
if(MSVC_VERSION GREATER_EQUAL 1930) # Visual Studio 2022 version 17.0
add_compile_options(-options:strict)
endif()
add_link_options(-WX)
endif()
endfunction()
#[[
Remove all unused code from the final binary.
qm_compiler_eliminate_dead_code()
]] #
function(qm_compiler_eliminate_dead_code)
if(MSVC)
add_compile_options(-Gw -Gy -Zc:inline)
add_link_options(-OPT:REF -OPT:ICF -OPT:LBR)
else()
add_compile_options(-ffunction-sections -fdata-sections)
if(APPLE)
add_link_options(-Wl,-dead_strip)
else()
add_link_options(-Wl,--as-needed -Wl,--strip-all -Wl,--gc-sections)
endif()
if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xClang")
add_link_options(-Wl,--icf=all)
endif()
endif()
endfunction()
#[[
Only export symbols which are marked to be exported, just like MSVC.
qm_compiler_dont_export_by_default()
]] #
macro(qm_compiler_dont_export_by_default)
set(CMAKE_C_VISIBILITY_PRESET "hidden")
set(CMAKE_CXX_VISIBILITY_PRESET "hidden")
set(CMAKE_VISIBILITY_INLINES_HIDDEN ON)
endmacro()
#[[
Enable all possible security issue mitigations from your compiler.
qm_compiler_enable_secure_code()
]] #
macro(qm_compiler_enable_secure_code)
if(MSVC)
add_compile_options(-GS -sdl -guard:cf)
add_link_options(-DYNAMICBASE -FIXED:NO -NXCOMPAT -GUARD:CF)
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
add_link_options(-SAFESEH)
endif()
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
add_link_options(-HIGHENTROPYVA)
endif()
if(MSVC_VERSION GREATER_EQUAL 1920) # Visual Studio 2019 version 16.0
add_link_options(-CETCOMPAT)
endif()
if(MSVC_VERSION GREATER_EQUAL 1925) # Visual Studio 2019 version 16.5
add_compile_options(-Qspectre-load)
elseif(MSVC_VERSION GREATER_EQUAL 1912) # Visual Studio 2017 version 15.5
add_compile_options(-Qspectre)
endif()
if(MSVC_VERSION GREATER_EQUAL 1927) # Visual Studio 2019 version 16.7
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
add_compile_options(-guard:ehcont)
add_link_options(-guard:ehcont)
endif()
endif()
if(MSVC_VERSION GREATER_EQUAL 1930) # Visual Studio 2022 version 17.0
add_compile_options(-Qspectre-jmp)
endif()
elseif(MINGW)
if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xClang")
add_compile_options(-mguard=cf)
add_link_options(-mguard=cf)
else()
endif()
else()
add_compile_options(-mshstk -ftrivial-auto-var-init=pattern
-fstack-protector-strong -fstack-clash-protection
-fcf-protection=full)
add_link_options(-Wl,-z,relro,-z,now)
if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xClang")
add_compile_options(-mretpoline -mspeculative-load-hardening)
if(NOT APPLE)
add_compile_options(-fsanitize=cfi -fsanitize-cfi-cross-dso)
endif()
endif()
endif()
endmacro()
#[[
Enable all possible Qt coding style policies.
qm_compiler_enable_strict_qt(
TARGETS <target1> <target2> ... <targetN>
[NO_DEPRECATED_API]
[ALLOW_KEYWORD]
[ALLOW_UNSAFE_FLAGS]
)
NO_DEPRECATED_API: Disable the use of any deprecated Qt APIs. Only has effect since Qt6.
ALLOW_KEYWORD: Allow the use of the traditional Qt keywords such as "signal" "slot" "emit".
ALLOW_UNSAFE_FLAGS: Allow the use of the unsafe QFlags (unsafe: can be implicitly cast to and from "int").
]] #
function(qm_compiler_enable_strict_qt)
cmake_parse_arguments(arg "NO_DEPRECATED_API;ALLOW_KEYWORD;ALLOW_UNSAFE_FLAGS" "" "TARGETS" ${ARGN})
if(NOT arg_TARGETS)
message(AUTHOR_WARNING "qm_compiler_enable_strict_qt: you need to specify at least one target!")
return()
endif()
if(arg_UNPARSED_ARGUMENTS)
message(AUTHOR_WARNING "qm_compiler_enable_strict_qt: Unrecognized arguments: ${arg_UNPARSED_ARGUMENTS}")
endif()
foreach(_target IN LISTS arg_TARGETS)
if(NOT TARGET ${_target})
message(AUTHOR_WARNING "qm_compiler_enable_strict_qt: ${_target} is not a valid CMake target!")
continue()
endif()
target_compile_definitions(${_target} PRIVATE
QT_NO_CAST_TO_ASCII
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_FROM_BYTEARRAY
QT_NO_URL_CAST_FROM_STRING
QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
QT_NO_JAVA_STYLE_ITERATORS
QT_NO_FOREACH QT_NO_QFOREACH
QT_NO_AS_CONST QT_NO_QASCONST
QT_NO_EXCHANGE QT_NO_QEXCHANGE
QT_NO_QPAIR
QT_NO_INTEGRAL_STRINGS
QT_NO_USING_NAMESPACE
QT_NO_CONTEXTLESS_CONNECT
QT_EXPLICIT_QFILE_CONSTRUCTION_FROM_PATH
QT_USE_NODISCARD_FILE_OPEN
QT_USE_QSTRINGBUILDER
QT_USE_FAST_OPERATOR_PLUS
QT_DEPRECATED_WARNINGS # Have no effect since 5.13
QT_DEPRECATED_WARNINGS_SINCE=0x0A0000 # Deprecated since 6.5
QT_WARN_DEPRECATED_UP_TO=0x0A0000 # Available since 6.5
)
if(arg_NO_DEPRECATED_API)
target_compile_definitions(${_target} PRIVATE
QT_DISABLE_DEPRECATED_BEFORE=0x0A0000 # Deprecated since 6.5
QT_DISABLE_DEPRECATED_UP_TO=0x0A0000 # Available since 6.5
)
endif()
# On Windows enabling this flag requires us re-compile Qt with this flag enabled,
# so only enable it on non-Windows platforms.
if(NOT WIN32)
target_compile_definitions(${_target} PRIVATE
QT_STRICT_ITERATORS
)
endif()
# We handle this flag specially because some Qt headers may still use the
# traditional keywords (especially some private headers).
if(NOT arg_ALLOW_KEYWORD)
target_compile_definitions(${_target} PRIVATE
QT_NO_KEYWORDS
)
endif()
# We handle this flag specially because some Qt headers may still use the
# unsafe flags (especially some QtQuick headers).
if(NOT arg_ALLOW_UNSAFE_FLAGS)
target_compile_definitions(${_target} PRIVATE
QT_TYPESAFE_FLAGS
)
endif()
endforeach()
endfunction()

View File

@@ -0,0 +1,421 @@
#[[
Warning: This module depends on `qmcorecmd` after installation.
]] #
if(NOT QMSETUP_CORECMD_EXECUTABLE)
message(FATAL_ERROR "QMSETUP_CORECMD_EXECUTABLE not defined. Add find_package(qmsetup) to CMake first.")
endif()
if(NOT DEFINED QMSETUP_APPLOCAL_DEPS_PATHS)
set(QMSETUP_APPLOCAL_DEPS_PATHS)
endif()
if(NOT DEFINED QMSETUP_APPLOCAL_DEPS_PATHS_DEBUG)
set(QMSETUP_APPLOCAL_DEPS_PATHS_DEBUG ${QMSETUP_APPLOCAL_DEPS_PATHS})
endif()
if(NOT DEFINED QMSETUP_APPLOCAL_DEPS_PATHS_RELEASE)
set(QMSETUP_APPLOCAL_DEPS_PATHS_RELEASE ${QMSETUP_APPLOCAL_DEPS_PATHS})
endif()
if(NOT DEFINED QMSETUP_APPLOCAL_DEPS_PATHS_RELWITHDEBINFO)
set(QMSETUP_APPLOCAL_DEPS_PATHS_RELWITHDEBINFO ${QMSETUP_APPLOCAL_DEPS_PATHS_RELEASE})
endif()
if(NOT DEFINED QMSETUP_APPLOCAL_DEPS_PATHS_MINSIZEREL)
set(QMSETUP_APPLOCAL_DEPS_PATHS_MINSIZEREL ${QMSETUP_APPLOCAL_DEPS_PATHS_RELEASE})
endif()
include_guard(DIRECTORY)
#[[
Record searching paths for Windows Executables, must be called before calling `qm_win_applocal_deps`
or `qm_deploy_directory` if your project supports Windows.
WARNING: This function is deprecated.
qm_win_record_deps(<target>)
]] #
function(qm_win_record_deps _target)
if(NOT WIN32)
return()
endif()
set(_paths)
get_target_property(_link_libraries ${_target} LINK_LIBRARIES)
foreach(_item IN LISTS _link_libraries)
if(NOT TARGET ${_item})
continue()
endif()
get_target_property(_imported ${_item} IMPORTED)
if(_imported)
get_target_property(_path ${_item} LOCATION)
if(NOT _path OR NOT ${_path} MATCHES "\\.dll$")
continue()
endif()
set(_path "$<TARGET_PROPERTY:${_item},LOCATION_$<CONFIG>>")
else()
get_target_property(_type ${_item} TYPE)
if(NOT ${_type} MATCHES "SHARED_LIBRARY")
continue()
endif()
set(_path "$<TARGET_FILE:${_item}>")
endif()
list(APPEND _paths ${_path})
endforeach()
if(NOT _paths)
return()
endif()
set(_deps_file "${CMAKE_CURRENT_BINARY_DIR}/${_target}_deps_$<CONFIG>.txt")
file(GENERATE OUTPUT ${_deps_file} CONTENT "$<JOIN:${_paths},\n>")
set_target_properties(${_target} PROPERTIES QMSETUP_DEPENDENCIES_FILE ${_deps_file})
endfunction()
#[[
Automatically copy dependencies for Windows Executables after build.
qm_win_applocal_deps(<target>
[CUSTOM_TARGET <target>]
[FORCE] [VERBOSE]
[EXTRA_SEARCHING_PATHS <path...>]
[EXTRA_TARGETS <target...>]
[OUTPUT_DIR <dir>]
[EXCLUDE <pattern...>]
)
]] #
function(qm_win_applocal_deps _target)
if(NOT WIN32)
return()
endif()
set(options FORCE VERBOSE)
set(oneValueArgs TARGET CUSTOM_TARGET OUTPUT_DIR)
set(multiValueArgs EXTRA_SEARCHING_PATHS EXTRA_TARGETS EXCLUDE)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# Get output directory and deploy target
set(_out_dir)
set(_deploy_target)
if(FUNC_CUSTOM_TARGET)
set(_deploy_target ${FUNC_CUSTOM_TARGET})
if(NOT TARGET ${_deploy_target})
add_custom_target(${_deploy_target})
endif()
else()
set(_deploy_target ${_target})
endif()
if(FUNC_OUTPUT_DIR)
set(_out_dir ${FUNC_OUTPUT_DIR})
else()
set(_out_dir "$<TARGET_FILE_DIR:${_target}>")
endif()
if(NOT _out_dir)
message(FATAL_ERROR "qm_win_applocal_deps: cannot determine output directory.")
endif()
# Get dep files
set(_dep_files)
_qm_win_get_all_dep_files(_dep_files ${_target})
foreach(_item IN LISTS FUNC_EXTRA_TARGETS)
_qm_win_get_all_dep_files(_dep_files ${_item})
endforeach()
# Prepare command
set(_args)
if(FUNC_FORCE)
list(APPEND _args -f)
endif()
if(FUNC_VERBOSE)
list(APPEND _args -V)
endif()
# Add extra searching paths
foreach(_item IN LISTS FUNC_EXTRA_SEARCHING_PATHS)
list(APPEND _args "-L${_item}")
endforeach()
# Add global extra searching paths
if(CMAKE_BUILD_TYPE)
string(TOUPPER ${CMAKE_BUILD_TYPE} _build_type_upper)
if(QMSETUP_APPLOCAL_DEPS_PATHS_${_build_type_upper})
foreach(_item IN LISTS QMSETUP_APPLOCAL_DEPS_PATHS_${_build_type_upper})
get_filename_component(_item ${_item} ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
list(APPEND _args "-L${_item}")
endforeach()
elseif(QMSETUP_APPLOCAL_DEPS_PATHS)
foreach(_item IN LISTS QMSETUP_APPLOCAL_DEPS_PATHS)
get_filename_component(_item ${_item} ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
list(APPEND _args "-L${_item}")
endforeach()
endif()
else()
foreach(_item IN LISTS QMSETUP_APPLOCAL_DEPS_PATHS)
get_filename_component(_item ${_item} ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
list(APPEND _args "-L${_item}")
endforeach()
endif()
foreach(_item IN LISTS _dep_files)
list(APPEND _args "--linkdirs-file" "${_item}")
endforeach()
foreach(_item IN LISTS FUNC_EXCLUDE)
list(APPEND _args -e ${_item})
endforeach()
list(APPEND _args "$<TARGET_FILE:${_target}>")
add_custom_command(TARGET ${_deploy_target} POST_BUILD
COMMAND ${QMSETUP_CORECMD_EXECUTABLE} deploy ${_args}
WORKING_DIRECTORY ${_out_dir}
VERBATIM
)
endfunction()
#[[
Add deploy command when install project, not available in debug mode.
qm_deploy_directory(<install_dir>
[FORCE] [STANDARD] [VERBOSE]
[LIBRARY_DIR <dir>]
[EXTRA_LIBRARIES <path>...]
[EXTRA_PLUGIN_PATHS <path>...]
[EXTRA_SEARCHING_PATHS <path>...]
[PLUGINS <plugin>...]
[PLUGIN_DIR <dir>]
[QML <qml>...]
[QML_DIR <dir>]
[WIN_TARGETS <target>...]
[COMMENT <comment>]
)
PLUGINS: Qt plugins, in format of `<category>/<name>`
PLUGIN_DIR: Qt plugins destination
EXTRA_PLUGIN_PATHS: Extra Qt plugins searching paths
QML: Qt qml directories
QML_DIR: Qt qml destination
LIBRARY_DIR: Library destination
EXTRA_LIBRARIES Extra library names list to deploy
EXTRA_SEARCHING_PATHS: Extra library searching paths
]] #
function(qm_deploy_directory _install_dir)
set(options FORCE STANDARD VERBOSE)
set(oneValueArgs LIBRARY_DIR PLUGIN_DIR QML_DIR COMMENT)
set(multiValueArgs EXTRA_PLUGIN_PATHS PLUGINS QML WIN_TARGETS EXTRA_SEARCHING_PATHS EXTRA_LIBRARIES)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# Get qmake
if((FUNC_PLUGINS OR FUNC_QML) AND NOT DEFINED QT_QMAKE_EXECUTABLE)
if(TARGET Qt${QT_VERSION_MAJOR}::qmake)
qm_get_executable_location(Qt${QT_VERSION_MAJOR}::qmake _qmake_path)
set(QT_QMAKE_EXECUTABLE "${_qmake_path}" CACHE FILEPATH "Path to qmake executable" FORCE)
elseif((FUNC_PLUGINS AND NOT FUNC_EXTRA_PLUGIN_PATHS) OR FUNC_QML)
message(FATAL_ERROR "qm_deploy_directory: qmake not defined. Add find_package(Qt5 COMPONENTS Core) to CMake to enable.")
endif()
endif()
# Set values
qm_set_value(_lib_dir FUNC_LIBRARY_DIR "${_install_dir}/${QMSETUP_SHARED_LIBRARY_CATEGORY}")
qm_set_value(_plugin_dir FUNC_PLUGIN_DIR "${_install_dir}/plugins")
qm_set_value(_qml_dir FUNC_QML_DIR "${_install_dir}/qml")
# Prepare commands
set(_args
-i "${_install_dir}"
-m "${QMSETUP_CORECMD_EXECUTABLE}"
--plugindir "${_plugin_dir}"
--libdir "${_lib_dir}"
--qmldir "${_qml_dir}"
)
set(_searching_paths)
if(QT_QMAKE_EXECUTABLE)
list(APPEND _args --qmake "${QT_QMAKE_EXECUTABLE}")
endif()
# Add Qt plugins
foreach(_item IN LISTS FUNC_PLUGINS)
list(APPEND _args --plugin "${_item}")
endforeach()
# Add QML modules
foreach(_item IN LISTS FUNC_QML)
list(APPEND _args --qml "${_item}")
endforeach()
# Add extra plugin paths
foreach(_item IN LISTS FUNC_EXTRA_PLUGIN_PATHS)
list(APPEND _args --extra "${_item}")
endforeach()
# Add extra searching paths
foreach(_item IN LISTS FUNC_EXTRA_SEARCHING_PATHS)
get_filename_component(_item ${_item} ABSOLUTE)
list(APPEND _searching_paths ${_item})
endforeach()
# Add global extra searching paths
if(CMAKE_BUILD_TYPE)
string(TOUPPER ${CMAKE_BUILD_TYPE} _build_type_upper)
if(QMSETUP_APPLOCAL_DEPS_PATHS_${_build_type_upper})
foreach(_item IN LISTS QMSETUP_APPLOCAL_DEPS_PATHS_${_build_type_upper})
get_filename_component(_item ${_item} ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
list(APPEND _searching_paths ${_item})
endforeach()
elseif(QMSETUP_APPLOCAL_DEPS_PATHS)
foreach(_item IN LISTS QMSETUP_APPLOCAL_DEPS_PATHS)
get_filename_component(_item ${_item} ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
list(APPEND _searching_paths ${_item})
endforeach()
endif()
else()
foreach(_item IN LISTS QMSETUP_APPLOCAL_DEPS_PATHS)
get_filename_component(_item ${_item} ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
list(APPEND _searching_paths ${_item})
endforeach()
endif()
foreach(_item IN LISTS _searching_paths)
list(APPEND _args -L "${_item}")
endforeach()
if(WIN32)
set(_dep_files)
if(FUNC_WIN_TARGETS)
_qm_win_get_all_dep_files(_dep_files ${FUNC_WIN_TARGETS})
endif()
foreach(_item IN LISTS _dep_files)
list(APPEND _args "--linkdirs-file" "${_item}")
endforeach()
set(_script_quoted "cmd /c \"${QMSETUP_MODULES_DIR}/scripts/windeps.bat\"")
else()
set(_script_quoted "bash \"${QMSETUP_MODULES_DIR}/scripts/unixdeps.sh\"")
endif()
# Add extra libraries
foreach(_item IN LISTS _searching_paths)
foreach(_lib IN LISTS FUNC_EXTRA_LIBRARIES)
set(_path "${_item}/${_lib}")
if((EXISTS ${_path}) AND(NOT IS_DIRECTORY ${_path}))
list(APPEND _args --copy ${_path} ${_lib_dir})
endif()
endforeach()
endforeach()
# Add options
if(FUNC_FORCE)
list(APPEND _args "-f")
endif()
if(FUNC_STANDARD)
list(APPEND _args "-s")
endif()
if(FUNC_VERBOSE)
list(APPEND _args "-V")
endif()
set(_args_quoted)
foreach(_item IN LISTS _args)
set(_args_quoted "${_args_quoted}\"${_item}\" ")
endforeach()
set(_comment_code)
if(FUNC_COMMENT)
set(_comment_code "message(STATUS \"${FUNC_COMMENT}\")")
endif()
# Add install command
install(CODE "
${_comment_code}
execute_process(
COMMAND ${_script_quoted} ${_args_quoted}
WORKING_DIRECTORY \"${_install_dir}\"
COMMAND_ERROR_IS_FATAL ANY
)
")
endfunction()
# ----------------------------------
# Private functions
# ----------------------------------
function(_qm_win_get_all_dep_files _out)
# Get searching paths
macro(get_recursive_dynamic_dependencies _current_target _result)
get_target_property(_deps ${_current_target} LINK_LIBRARIES)
if(_deps)
foreach(_dep IN LISTS _deps)
if(NOT TARGET ${_dep})
continue()
endif()
get_target_property(_type ${_dep} TYPE)
if(_type STREQUAL "SHARED_LIBRARY")
list(APPEND ${_result} ${_dep})
endif()
get_recursive_dynamic_dependencies(${_dep} ${_result})
endforeach()
endif()
endmacro()
set(_visited_targets ${ARGN})
foreach(_target ${ARGN})
set(_all_deps)
get_recursive_dynamic_dependencies(${_target} _all_deps)
foreach(_cur_dep IN LISTS _all_deps)
if(${_cur_dep} IN_LIST _visited_targets)
continue()
endif()
list(APPEND _visited_targets ${_cur_dep})
endforeach()
endforeach()
set(_dep_files)
foreach(_target IN LISTS _visited_targets)
# Add file
get_target_property(_file ${_target} QMSETUP_DEPENDENCIES_FILE)
if(NOT _file)
continue()
endif()
list(APPEND _dep_files ${_file})
endforeach()
set(${_out} ${_dep_files} PARENT_SCOPE)
endfunction()

View File

@@ -0,0 +1,235 @@
include_guard(DIRECTORY)
#[[
Add Doxygen documentation generating target.
qm_setup_doxygen(<target>
[NAME <name>]
[VERSION <version>]
[DESCRIPTION <desc>]
[LOGO <file>]
[MDFILE <file>]
[OUTPUT_DIR <dir>]
[INSTALL_DIR <dir>]
[TAGFILES <file> ...]
[GENERATE_TAGFILE <file>]
[INPUT <file> ...]
[INCLUDE_DIRECTORIES <dir> ...]
[COMPILE_DEFINITIONS <NAME=VALUE> ...]
[TARGETS <target> ...]
[ENVIRONMENT_EXPORTS <key> ...]
[NO_EXPAND_MACROS <macro> ...]
[DEPENDS <dependency> ...]
)
]] #
function(qm_setup_doxygen _target)
set(options)
set(oneValueArgs NAME VERSION DESCRIPTION LOGO MDFILE OUTPUT_DIR INSTALL_DIR GENERATE_TAGFILE)
set(multiValueArgs INPUT TAGFILES INCLUDE_DIRECTORIES COMPILE_DEFINITIONS TARGETS ENVIRONMENT_EXPORTS
NO_EXPAND_MACROS DEPENDS
)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT DOXYGEN_EXECUTABLE)
message(FATAL_ERROR "qm_setup_doxygen: DOXYGEN_EXECUTABLE not defined. Add find_package(Doxygen) to CMake to enable.")
endif()
set(DOXYGEN_FILE_DIR ${QMSETUP_MODULES_DIR}/doxygen)
if(FUNC_NAME)
set(_name ${FUNC_NAME})
else()
set(_name ${PROJECT_NAME})
endif()
if(FUNC_VERSION)
set(_version ${FUNC_VERSION})
else()
set(_version ${PROJECT_VERSION})
endif()
if(FUNC_DESCRIPTION)
set(_desc ${FUNC_DESCRIPTION})
elseif(PROJECT_DESCRIPTION)
set(_desc ${PROJECT_DESCRIPTION})
else()
set(_desc ${_name})
endif()
if(FUNC_LOGO)
set(_logo ${FUNC_LOGO})
else()
set(_logo)
endif()
if(FUNC_MDFILE)
set(_mdfile ${FUNC_MDFILE})
else()
set(_mdfile)
endif()
if(FUNC_GENERATE_TAGFILE)
set(_tagfile ${FUNC_GENERATE_TAGFILE})
else()
set(_tagfile)
endif()
set(_sep " \\\n ")
# Generate include file
set(_doxy_includes "${CMAKE_CURRENT_BINARY_DIR}/cmake/doxygen_${_target}_$<CONFIG>.inc")
set(_doxy_output_dir "${CMAKE_CURRENT_BINARY_DIR}/doxygen_${_target}")
set(_input "")
set(_tagfiles "")
set(_includes "")
set(_defines "")
set(_no_expand "")
if(FUNC_INPUT)
set(_input "INPUT = $<JOIN:${FUNC_INPUT},${_sep}>\n\n")
else()
set(_input "INPUT = \n\n")
endif()
if(FUNC_TAGFILES)
set(_tagfiles "TAGFILES = $<JOIN:${FUNC_TAGFILES},${_sep}>\n\n")
else()
set(_tagfiles "TAGFILES = \n\n")
endif()
if(FUNC_INCLUDE_DIRECTORIES)
set(_includes "INCLUDE_PATH = $<JOIN:${FUNC_INCLUDE_DIRECTORIES},${_sep}>\n\n")
else()
set(_includes "INCLUDE_PATH = \n\n")
endif()
if(FUNC_COMPILE_DEFINITIONS)
set(_defines "PREDEFINED = $<JOIN:${FUNC_COMPILE_DEFINITIONS},${_sep}>\n\n")
else()
set(_defines "PREDEFINED = \n\n")
endif()
if(FUNC_NO_EXPAND_MACROS)
set(_temp_list)
foreach(_item IN LISTS FUNC_NO_EXPAND_MACROS)
list(APPEND _temp_list "${_item}=")
endforeach()
set(_no_expand "PREDEFINED += $<JOIN:${_temp_list},${_sep}>\n\n")
unset(_temp_list)
endif()
# Extra
set(_extra_arguments)
if(FUNC_TARGETS)
foreach(item IN LISTS FUNC_TARGETS)
set(_extra_arguments
"${_extra_arguments}INCLUDE_PATH += $<JOIN:$<TARGET_PROPERTY:${item},INCLUDE_DIRECTORIES>,${_sep}>\n\n")
set(_extra_arguments
"${_extra_arguments}PREDEFINED += $<JOIN:$<TARGET_PROPERTY:${item},COMPILE_DEFINITIONS>,${_sep}>\n\n")
endforeach()
endif()
if(FUNC_OUTPUT_DIR)
set(_doxy_output_dir ${FUNC_OUTPUT_DIR})
endif()
if(_mdfile)
set(_extra_arguments "${_extra_arguments}INPUT += ${_mdfile}\n\n")
endif()
file(GENERATE
OUTPUT "${_doxy_includes}"
CONTENT "${_input}${_tagfiles}${_includes}${_defines}${_extra_arguments}${_no_expand}"
)
set(_env)
foreach(_export IN LISTS FUNC_ENVIRONMENT_EXPORTS)
if(NOT DEFINED "${_export}")
message(FATAL_ERROR "qm_setup_doxygen: ${_export} is not known when trying to export it.")
endif()
list(APPEND _env "${_export}=${${_export}}")
endforeach()
list(APPEND _env "DOXY_FILE_DIR=${DOXYGEN_FILE_DIR}")
list(APPEND _env "DOXY_INCLUDE_FILE=${_doxy_includes}")
list(APPEND _env "DOXY_PROJECT_NAME=${_name}")
list(APPEND _env "DOXY_PROJECT_VERSION=${_version}")
list(APPEND _env "DOXY_PROJECT_BRIEF=${_desc}")
list(APPEND _env "DOXY_PROJECT_LOGO=${_logo}")
list(APPEND _env "DOXY_MAINPAGE_MD_FILE=${_mdfile}")
set(_build_command "${CMAKE_COMMAND}" "-E" "env"
${_env}
"DOXY_OUTPUT_DIR=${_doxy_output_dir}"
"DOXY_GENERATE_TAGFILE=${_tagfile}"
"${DOXYGEN_EXECUTABLE}"
"${DOXYGEN_FILE_DIR}/Doxyfile"
)
if(FUNC_DEPENDS)
set(_dependencies DEPENDS ${FUNC_DEPENDS})
endif()
if(_tagfile)
get_filename_component(_tagfile_dir ${_tagfile} ABSOLUTE)
get_filename_component(_tagfile_dir ${_tagfile_dir} DIRECTORY)
set(_make_tagfile_dir_cmd COMMAND ${CMAKE_COMMAND} -E make_directory ${_tagfile_dir})
else()
set(_make_tagfile_dir_cmd)
endif()
add_custom_target(${_target}
COMMAND ${CMAKE_COMMAND} -E make_directory ${_doxy_output_dir}
${_make_tagfile_dir_cmd}
COMMAND ${_build_command}
COMMENT "Building HTML documentation"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
VERBATIM
${_dependencies}
)
if(FUNC_INSTALL_DIR)
set(_install_dir ${FUNC_INSTALL_DIR})
if(_tagfile)
get_filename_component(_name ${_tagfile} NAME)
set(_install_tagfile ${_name})
else()
set(_install_tagfile)
endif()
set(_install_command "${CMAKE_COMMAND}" "-E" "env"
${_env}
"DOXY_OUTPUT_DIR=\${_install_dir}"
"DOXY_GENERATE_TAGFILE=\${_install_dir}/${_install_tagfile}"
"${DOXYGEN_EXECUTABLE}"
"${DOXYGEN_FILE_DIR}/Doxyfile"
)
set(_install_command_quoted)
foreach(_item IN LISTS _install_command)
set(_install_command_quoted "${_install_command_quoted}\"${_item}\" ")
endforeach()
install(CODE "
message(STATUS \"Installing HTML documentation\")
get_filename_component(_install_dir \"${_install_dir}\" ABSOLUTE BASE_DIR \${CMAKE_INSTALL_PREFIX})
file(MAKE_DIRECTORY \${_install_dir})
execute_process(
COMMAND ${_install_command_quoted}
WORKING_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\"
OUTPUT_QUIET
)
")
endif()
endfunction()

View File

@@ -0,0 +1,199 @@
include_guard(DIRECTORY)
if(NOT QMSETUP_MODULES_DIR)
get_filename_component(QMSETUP_MODULES_DIR ${CMAKE_CURRENT_LIST_DIR} DIRECTORY)
endif()
#[[
Initialize the build output directories of targets and resources.
qm_init_directories()
]] #
macro(qm_init_directories)
if(NOT DEFINED QMSETUP_BUILD_DIR)
set(QMSETUP_BUILD_DIR "${CMAKE_BINARY_DIR}/out-$<LOWER_CASE:${CMAKE_SYSTEM_PROCESSOR}>-$<CONFIG>")
endif()
if(NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${QMSETUP_BUILD_DIR}/bin)
endif()
if(NOT DEFINED CMAKE_LIBRARY_OUTPUT_DIRECTORY)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${QMSETUP_BUILD_DIR}/lib)
endif()
if(NOT DEFINED CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${QMSETUP_BUILD_DIR}/lib)
endif()
if(NOT DEFINED CMAKE_BUILD_SHARE_DIR)
set(CMAKE_BUILD_SHARE_DIR ${QMSETUP_BUILD_DIR}/share)
endif()
endmacro()
#[[
Add a resources copying command for whole project.
qm_add_copy_command(<target>
[CUSTOM_TARGET <target>]
[EXTRA_ARGS <args...>]
[DEPENDS <targets...>]
[VERBOSE] [SKIP_BUILD] [SKIP_INSTALL]
SOURCES <file/dir...> [DESTINATION <dir>] [INSTALL_DIR <dir>]
)
CUSTOM_TARGET: Use a custom target to control the copy command
EXTRA_ARGS: Extra arguments to pass to file(INSTALL) statement
DEPENDS: Targets that the copy command depends
SOURCES: Source files or directories, directories ending with "/" will have their contents copied
DESTINATION: Copy the source file to the destination path. If the given value is a relative path,
the base directory depends on the type of the target
- `$<TARGET_FILE_DIR>`: real target
- `QMSETUP_BUILD_DIR`: custom target
INSTALL_DIR: Install the source files into a subdirectory in the given path. The subdirectory is the
relative path from the `QMSETUP_BUILD_DIR` to `DESTINATION`.
]] #
function(qm_add_copy_command _target)
set(options VERBOSE SKIP_BUILD SKIP_INSTALL)
set(oneValueArgs CUSTOM_TARGET DESTINATION INSTALL_DIR)
set(multiValueArgs SOURCES EXTRA_ARGS DEPENDS)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT FUNC_SOURCES)
message(FATAL_ERROR "qm_add_copy_command: SOURCES not specified.")
endif()
if(NOT TARGET ${_target})
add_custom_target(${_target})
endif()
# Determine destination
set(_dest .)
if(FUNC_DESTINATION)
set(_dest ${FUNC_DESTINATION})
endif()
# Determine destination base directory
get_target_property(_type ${_target} TYPE)
if(_type STREQUAL "UTILITY")
if(QMSETUP_BUILD_DIR)
set(_dest_base ${QMSETUP_BUILD_DIR})
else()
set(_dest_base ${CMAKE_CURRENT_SOURCE_DIR})
endif()
else()
set(_dest_base "$<TARGET_FILE_DIR:${_target}>")
endif()
# Set deploy target
if(FUNC_CUSTOM_TARGET)
set(_deploy_target ${FUNC_CUSTOM_TARGET})
if(NOT TARGET ${_deploy_target})
add_custom_target(${_deploy_target})
endif()
else()
set(_deploy_target ${_target})
endif()
if(FUNC_DEPENDS)
add_dependencies(${_deploy_target} ${FUNC_DEPENDS})
endif()
# Prepare arguments
set(_extra_args)
set(_ignore_stdout)
if(FUNC_EXTRA_ARGS)
list(APPEND _extra_args -D "args=${FUNC_EXTRA_ARGS}")
endif()
if(NOT FUNC_VERBOSE)
set(_ignore_stdout ${QMSETUP_IGNORE_STDOUT})
endif()
if(NOT FUNC_SKIP_BUILD)
# Build phase
add_custom_command(TARGET ${_deploy_target} POST_BUILD
COMMAND ${CMAKE_COMMAND}
-D "src=${FUNC_SOURCES}"
-D "dest=${_dest}"
-D "dest_base=${_dest_base}"
${_extra_args}
-P "${QMSETUP_MODULES_DIR}/scripts/copy.cmake" ${_ignore_stdout}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
VERBATIM
)
endif()
if(FUNC_INSTALL_DIR AND NOT FUNC_SKIP_INSTALL)
if(NOT QMSETUP_BUILD_DIR)
message(FATAL_ERROR "qm_add_copy_command: `QMSETUP_BUILD_DIR` not defined, the install directory cannot be determined.")
endif()
# Install phase
install(CODE "
set(_src \"${FUNC_SOURCES}\")
set(_extra_args \"${FUNC_EXTRA_ARGS}\")
# Calculate the relative path from build phase destination to build directory
get_filename_component(_build_dir \"${_dest}\" ABSOLUTE BASE_DIR \"${_dest_base}\")
file(RELATIVE_PATH _rel_path \"${QMSETUP_BUILD_DIR}\" \${_build_dir})
# Calculate real install directory
get_filename_component(_dest \"${FUNC_INSTALL_DIR}/\${_rel_path}\" ABSOLUTE BASE_DIR \${CMAKE_INSTALL_PREFIX})
execute_process(
COMMAND \${CMAKE_COMMAND}
-D \"src=\${_src}\"
-D \"dest=\${_dest}\"
\${_extra_args}
-P \"${QMSETUP_MODULES_DIR}/scripts/copy.cmake\"
WORKING_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\"
)
")
endif()
endfunction()
#[[
Add a custom command to run `configure_file`.
qm_future_configure_file(<_input> <output>
[FORCE]
[EXTRA_ARGS <args...>]
[DEPENDS <deps...>]
[VARIABLES <var...>]
)
]] #
function(qm_future_configure_file _input _output)
set(options FORCE)
set(oneValueArgs)
set(multiValueArgs VARIABLES EXTRA_ARGS DEPENDS)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(_options)
foreach(_item IN LISTS FUNC_VARIABLES)
list(APPEND _options -D "${_item}=${${_item}}")
endforeach()
if(FUNC_FORCE)
list(APPLE _options -D "force=TRUE")
endif()
add_custom_command(OUTPUT ${_output}
COMMAND ${CMAKE_COMMAND}
-D "input=${_input}"
-D "output=${_output}"
-D "args=${FUNC_EXTRA_ARGS}"
${_options}
-P "${QMSETUP_MODULES_DIR}/scripts/configure_file.cmake"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${FUNC_DEPENDS}
VERBATIM
)
endfunction()

View File

@@ -0,0 +1,538 @@
#[[
Warning: This module depends on `qmcorecmd` after installation.
]] #
if(NOT QMSETUP_CORECMD_EXECUTABLE)
message(FATAL_ERROR "QMSETUP_CORECMD_EXECUTABLE not defined. Add find_package(qmsetup) to CMake first.")
endif()
if(NOT DEFINED QMSETUP_DEFINITION_NUMERICAL)
set(QMSETUP_DEFINITION_NUMERICAL off)
endif()
if(NOT DEFINED QMSETUP_DEFINITION_SCOPE)
set(QMSETUP_DEFINITION_SCOPE)
endif()
if(NOT DEFINED QMSETUP_DEFINITION_PROPERTY)
set(QMSETUP_DEFINITION_PROPERTY)
endif()
if(NOT DEFINED QMSETUP_SYNC_INCLUDE_STANDARD)
set(QMSETUP_SYNC_INCLUDE_STANDARD on)
endif()
include_guard(DIRECTORY)
#[[
Generate indirect reference files for header files to make the include statements more orderly.
The generated file has the same timestamp as the source file.
qm_sync_include(<src> <dest>
[STANDARD] [NO_STANDARD] [NO_ALL]
[INCLUDE <expr> <sub> ...]
[EXCLUDE <expr...>]
[INSTALL_DIR <dir>]
[FORCE] [VERBOSE]
)
STANDARD: Enable standard public-private pattern, can be forced to enable by enabling `QMSETUP_SYNC_INCLUDE_STANDARD`
NO_STANDARD: Disable standard public-private pattern, enable it to override `QMSETUP_SYNC_INCLUDE_STANDARD`
#]]
function(qm_sync_include _src_dir _dest_dir)
set(options FORCE VERBOSE NO_STANDARD NO_ALL)
set(oneValueArgs INSTALL_DIR)
set(multiValueArgs INCLUDE EXCLUDE)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT IS_ABSOLUTE ${_src_dir})
get_filename_component(_src_dir ${_src_dir} ABSOLUTE)
else()
string(REPLACE "\\" "/" _src_dir ${_src_dir})
endif()
if(NOT IS_ABSOLUTE ${_dest_dir})
get_filename_component(_dest_dir ${_dest_dir} ABSOLUTE)
else()
string(REPLACE "\\" "/" _dest_dir ${_dest_dir})
endif()
if(IS_DIRECTORY ${_src_dir})
file(GLOB_RECURSE header_files ${_src_dir}/*.h ${_src_dir}/*.hpp)
set(_args)
if(FUNC_STANDARD OR(QMSETUP_SYNC_INCLUDE_STANDARD AND NOT FUNC_NO_STANDARD))
list(APPEND _args -s)
endif()
if(FUNC_NO_ALL)
list(APPEND _args -n)
endif()
set(_even off)
foreach(_item IN LISTS FUNC_INCLUDE)
if(_even)
set(_even off)
list(APPEND _args ${_item})
else()
set(_even on)
list(APPEND _args -i ${_item})
endif()
endforeach()
foreach(_item IN LISTS FUNC_EXCLUDE)
list(APPEND _args -e ${_item})
endforeach()
if(FUNC_VERBOSE)
list(APPEND _args -V)
endif()
if(FUNC_FORCE OR NOT EXISTS ${_dest_dir})
if(EXISTS ${_dest_dir})
file(REMOVE_RECURSE ${_dest_dir})
endif()
execute_process(
COMMAND ${QMSETUP_CORECMD_EXECUTABLE} incsync ${_args} ${_src_dir} ${_dest_dir}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
endif()
if(FUNC_INSTALL_DIR)
set(_install_dir ${FUNC_INSTALL_DIR})
set(_args_quoted)
foreach(_item IN LISTS _args)
set(_args_quoted "${_args_quoted}\"${_item}\" ")
endforeach()
# Get command output only and use file(INSTALL) to install files
install(CODE "
get_filename_component(_install_dir \"${_install_dir}\" ABSOLUTE BASE_DIR \${CMAKE_INSTALL_PREFIX})
execute_process(
COMMAND \"${QMSETUP_CORECMD_EXECUTABLE}\" incsync -d
${_args_quoted} \"${_src_dir}\" \${_install_dir}
WORKING_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\"
OUTPUT_VARIABLE _output_contents
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY
)
string(REPLACE \"\\n\" \";\" _lines \"\${_output_contents}\")
foreach(_line IN LISTS _lines)
string(REGEX MATCH \"from \\\"([^\\\"]*)\\\" to \\\"([^\\\"]*)\\\"\" _ \${_line})
get_filename_component(_target_path \${CMAKE_MATCH_2} DIRECTORY)
file(INSTALL \${CMAKE_MATCH_1} DESTINATION \${_target_path})
endforeach()
")
endif()
else()
message(FATAL_ERROR "qm_sync_include: source directory doesn't exist.")
endif()
endfunction()
#[[
Add a definition to a property scope.
qm_add_definition( <key | key=value> | <key> <value>
[GLOBAL | TARGET <target> | SOURCE <file> | DIRECTORY <dir>]
[PROPERTY <prop>]
[CONDITION <cond...>]
[STRING_LITERAL] [NO_KEYWORD]
[NUMERICAL] [CLASSICAL]
)
STRING_LITERAL: Force quotes on values
NO_KEYWORD: Treat any keyword as string
NUMERICAL: Use 1/-1 as defined/undefined, can be forced to enable by enabling `QMSETUP_DEFINITION_NUMERICAL`
CLASSICAL: Use classical definition, enable it to override `QMSETUP_DEFINITION_NUMERICAL`
]] #
function(qm_add_definition _first)
set(options STRING_LITERAL NO_KEYWORD NUMERICAL CLASSICAL)
set(oneValueArgs TARGET SOURCE DIRECTORY PROPERTY)
set(multiValueArgs CONDITION)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(_result)
set(_is_pair off)
set(_defined off)
set(_list ${_first} ${FUNC_UNPARSED_ARGUMENTS})
list(LENGTH _list _len)
set(_cond on)
set(_numerical off)
if(NOT FUNC_CLASSICAL AND(QMSETUP_DEFINITION_NUMERICAL OR FUNC_NUMERICAL))
set(_numerical on)
endif()
if(FUNC_CONDITION)
if(NOT(${FUNC_CONDITION}))
set(_cond off)
endif()
elseif(DEFINED FUNC_CONDITION)
set(_cond off)
endif()
if(_len EQUAL 1)
set(_result ${_list})
set(_defined on)
if(NOT _cond)
set(_defined off)
endif()
elseif(_len EQUAL 2)
# Get key
list(POP_FRONT _list _key)
list(POP_FRONT _list _val)
if(FUNC_STRING_LITERAL AND NOT ${_val} MATCHES "\".+\"")
set(_val "\"${_val}\"")
endif()
# Boolean
string(TOLOWER ${_val} _val_lower)
if(NOT FUNC_NO_KEYWORD AND(${_val_lower} STREQUAL "off" OR ${_val_lower} STREQUAL "false"))
set(_result ${_key})
set(_defined off)
if(NOT _cond)
set(_defined on)
endif()
elseif(NOT FUNC_NO_KEYWORD AND(${_val_lower} STREQUAL "on" OR ${_val_lower} STREQUAL "true"))
set(_result ${_key})
set(_defined on)
if(NOT _cond)
set(_defined off)
endif()
else()
set(_result "${_key}=${_val}")
set(_is_pair on)
set(_defined on)
if(NOT _cond)
set(_defined off)
endif()
endif()
else()
message(FATAL_ERROR "qm_add_definition: called with incorrect number of arguments")
endif()
if(_numerical AND NOT _is_pair)
if(_defined)
set(_result "${_result}=1")
else()
set(_result "${_result}=-1")
endif()
elseif(NOT _defined)
return()
endif()
_qm_calc_property_scope_helper(_scope _prop)
set_property(${_scope} APPEND PROPERTY ${_prop} "${_result}")
endfunction()
#[[
Remove a definition from a property scope.
qm_remove_definition(<key>
[GLOBAL | TARGET <target> | SOURCE <file> | DIRECTORY <dir>]
[PROPERTY <prop>]
)
]] #
function(qm_remove_definition _key)
set(options)
set(oneValueArgs TARGET PROPERTY)
set(multiValueArgs)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(FUNC_TARGET)
get_target_property(_definitions ${FUNC_TARGET} ${_prop})
else()
get_property(_definitions GLOBAL PROPERTY ${_prop})
endif()
# Filter
list(FILTER _definitions EXCLUDE REGEX "^${_key}(=.*)?$")
_qm_calc_property_scope_helper(_scope _prop)
set_property(${_scope} PROPERTY ${_prop} "${_definitions}")
endfunction()
#[[
Generate a configuration header of a property scope. If the configuration has not changed,
the generated file's timestemp will not be updated when you reconfigure it.
qm_generate_config(<file>
[GLOBAL | TARGET <target> | SOURCE <file> | DIRECTORY <dir>]
[PROPERTY <prop>]
[PROJECT_NAME <name>]
[WARNING_FILE <file>]
[NO_WARNING]
[NO_HASH]
)
]] #
function(qm_generate_config _file)
set(options NO_WARNING NO_HASH)
set(oneValueArgs TARGET PROPERTY PROJECT_NAME WARNING_FILE)
set(multiValueArgs)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
_qm_calc_property_scope_helper(_scope _prop)
get_property(_definitions ${_scope} PROPERTY ${_prop})
if(NOT _definitions)
set(_definitions) # May be _-NOTFOUND
endif()
_qm_generate_config_helper()
endfunction()
#[[
Generate build info information header.
qm_generate_build_info(<file>
[REQUIRED]
[ROOT_DIRECTORY <dir>]
[PREFIX <prefix>]
[YEAR] [TIME]
[PROJECT_NAME <name>]
[WARNING_FILE <file>]
[NO_WARNING]
[NO_HASH]
)
file: Output file
REQUIRED: Abort if there's any error with git
ROOT_DIRECTORY: Repository root directory (CMake will try to run `git` at this directory)
PREFIX: Macros prefix, default to the upper case of `PROJECT_NAME`
]] #
function(qm_generate_build_info _file)
set(options NO_WARNING NO_HASH YEAR TIME REQUIRED)
set(oneValueArgs ROOT_DIRECTORY PREFIX PROJECT_NAME WARNING_FILE)
set(multiValueArgs)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(FUNC_PREFIX)
set(_prefix ${FUNC_PREFIX})
else()
string(TOUPPER "${PROJECT_NAME}" _prefix)
string(MAKE_C_IDENTIFIER ${_prefix} _prefix)
endif()
set(_dir)
qm_set_value(_dir FUNC_ROOT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set(_git_branch "unknown")
set(_git_hash "unknown")
set(_git_commit_time "unknown")
set(_git_commit_author "unknown")
set(_git_commit_email "unknown")
set(_git_revision_id "0")
# Check `git` command
if(NOT GIT_EXECUTABLE)
find_package(Git)
if(NOT Git_FOUND)
if(FUNC_REQUIRED)
message(FATAL_ERROR "Git not found")
endif()
endif()
endif()
if(GIT_EXECUTABLE)
# Branch
execute_process(
COMMAND ${GIT_EXECUTABLE} symbolic-ref --short -q HEAD
OUTPUT_VARIABLE _temp
ERROR_VARIABLE _err
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
WORKING_DIRECTORY ${_dir}
RESULT_VARIABLE _code
)
if(_code EQUAL 0)
set(_git_branch ${_temp})
elseif(FUNC_REQUIRED)
message(FATAL_ERROR "Error running git symbolic-ref: ${_err}")
endif()
# Hash
execute_process(
COMMAND ${GIT_EXECUTABLE} log -1 "--pretty=format:%H;%aI;%aN;%aE" # Use `;` as separator
OUTPUT_VARIABLE _temp
ERROR_VARIABLE _err
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
WORKING_DIRECTORY ${_dir}
RESULT_VARIABLE _code
)
if(_code EQUAL 0)
list(GET _temp 0 _git_hash)
list(GET _temp 1 _git_commit_time)
list(GET _temp 2 _git_commit_author)
list(GET _temp 3 _git_commit_email)
elseif(FUNC_REQUIRED)
message(FATAL_ERROR "Error running git log: ${_err}")
endif()
# Revision ID
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-list --count HEAD
OUTPUT_VARIABLE _temp
ERROR_VARIABLE _err
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
WORKING_DIRECTORY ${_dir}
RESULT_VARIABLE _code
)
if(_code EQUAL 0)
set(_git_revision_id ${_temp})
elseif(FUNC_REQUIRED)
message(FATAL_ERROR "Error running git rev-list: ${_err}")
endif()
endif()
qm_set_value(_system_name CMAKE_SYSTEM_NAME unknown)
qm_set_value(_system_version CMAKE_SYSTEM_VERSION unknown)
qm_set_value(_system_processor CMAKE_SYSTEM_PROCESSOR unknown)
qm_set_value(_host_system_name CMAKE_HOST_SYSTEM_NAME unknown)
qm_set_value(_host_system_version CMAKE_HOST_SYSTEM_VERSION unknown)
qm_set_value(_host_system_processor CMAKE_HOST_SYSTEM_PROCESSOR unknown)
qm_set_value(_compiler_name CMAKE_CXX_COMPILER_ID unknown)
qm_set_value(_compiler_version CMAKE_CXX_COMPILER_VERSION unknown)
qm_set_value(_compiler_arch CMAKE_CXX_COMPILER_ARCHITECTURE_ID unknown)
qm_set_value(_compiler_abi CMAKE_CXX_COMPILER_ABI unknown)
set(_definitions)
set(_has_time off)
# year
if(FUNC_YEAR)
string(TIMESTAMP _build_year "%Y")
list(APPEND _definitions ${_prefix}_BUILD_YEAR=\"${_build_year}\")
set(_has_time on)
endif()
# time
if(FUNC_TIME)
string(TIMESTAMP _build_time UTC)
list(APPEND _definitions ${_prefix}_BUILD_TIME=\"${_build_time}\")
set(_has_time on)
endif()
if(_has_time)
list(APPEND _definitions "%")
endif()
# system
list(APPEND _definitions ${_prefix}_SYSTEM_NAME=\"${_system_name}\")
list(APPEND _definitions ${_prefix}_SYSTEM_VERSION=\"${_system_version}\")
list(APPEND _definitions ${_prefix}_SYSTEM_PROCESSOR=\"${_system_processor}\")
list(APPEND _definitions ${_prefix}_HOST_SYSTEM_NAME=\"${_host_system_name}\")
list(APPEND _definitions ${_prefix}_HOST_SYSTEM_VERSION=\"${_host_system_version}\")
list(APPEND _definitions ${_prefix}_HOST_SYSTEM_PROCESSOR=\"${_host_system_processor}\")
list(APPEND _definitions "%")
# compiler
list(APPEND _definitions ${_prefix}_COMPILER_ID=\"${_compiler_name}\")
list(APPEND _definitions ${_prefix}_COMPILER_VERSION=\"${_compiler_version}\")
list(APPEND _definitions ${_prefix}_COMPILER_ARCH=\"${_compiler_arch}\")
list(APPEND _definitions ${_prefix}_COMPILER_ABI=\"${_compiler_abi}\")
list(APPEND _definitions "%")
# build time (deprecated)
# list(APPEND _definitions ${_prefix}_BUILD_DATE_TIME=\"${_build_time}\")
# list(APPEND _definitions ${_prefix}_BUILD_YEAR=\"${_build_year}\")
# list(APPEND _definitions "%")
# git info
list(APPEND _definitions ${_prefix}_GIT_BRANCH=\"${_git_branch}\")
list(APPEND _definitions ${_prefix}_GIT_LAST_COMMIT_HASH=\"${_git_hash}\")
list(APPEND _definitions ${_prefix}_GIT_LAST_COMMIT_TIME=\"${_git_commit_time}\")
list(APPEND _definitions ${_prefix}_GIT_LAST_COMMIT_AUTHOR=\"${_git_commit_author}\")
list(APPEND _definitions ${_prefix}_GIT_LAST_COMMIT_EMAIL=\"${_git_commit_email}\")
list(APPEND _definitions ${_prefix}_GIT_REVISION_ID=\"${_git_revision_id}\")
_qm_generate_config_helper()
endfunction()
# ----------------------------------
# Private functions
# ----------------------------------
function(_qm_calc_property_scope_helper _scope _prop)
if(FUNC_TARGET)
set(_scope TARGET ${FUNC_TARGET})
elseif(FUNC_SOURCE)
set(_scope SOURCE ${FUNC__SOURCE})
elseif(FUNC_DIRECTORY)
set(_scope DIRECTORY ${FUNC_DIRECTORY})
elseif(FUNC_GLOBAL)
set(_scope GLOBAL)
elseif(QMSETUP_DEFINITION_SCOPE)
set(_scope ${QMSETUP_DEFINITION_SCOPE})
else()
set(_scope GLOBAL)
endif()
qm_set_value(_prop FUNC_PROPERTY QMSETUP_DEFINITION_PROPERTY "CONFIG_DEFINITIONS")
set(_scope ${_scope} PARENT_SCOPE)
set(_prop ${_prop} PARENT_SCOPE)
endfunction()
function(_qm_generate_config_helper)
set(_args)
foreach(_item IN LISTS _definitions)
list(APPEND _args "-D${_item}")
endforeach()
if(FUNC_PROJECT_NAME)
list(APPEND _args "-p" ${FUNC_PROJECT_NAME})
endif()
if(FUNC_NO_HASH)
list(APPEND _args "-f")
endif()
list(APPEND _args ${_file})
if(NOT FUNC_NO_WARNING)
list(APPEND _args "-w")
if(FUNC_WARNING_FILE)
list(APPEND _args ${FUNC_WARNING_FILE})
endif()
endif()
execute_process(COMMAND ${QMSETUP_CORECMD_EXECUTABLE} configure ${_args}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
endfunction()

View File

@@ -0,0 +1,141 @@
include_guard(DIRECTORY)
#[[
Add rules for creating Google Protobuf source files.
qm_create_protobuf(<OUT>
INPUT <files...>
[OUTPUT_DIR <dir>]
[TARGET <target>]
[INCLUDE_DIRECTORIES <dirs...>]
[OPTIONS <options...>]
[DEPENDS <deps...>]
[CREATE_ONCE]
)
INPUT: source files
OUTPUT_DIR: output directory
TARGET: add a custom target to run the generating command
INCLUDE_DIRECTORIES: extra include directories
OPTIONS: extra options passed to protobuf compiler
DEPENDS: dependencies
CREATE_ONCE: create proto code files at configure phase if not exist
OUT: output source file paths
#]]
function(qm_create_protobuf _out)
set(options CREATE_ONCE)
set(oneValueArgs OUTPUT_DIR TARGET)
set(multiValueArgs INPUT INCLUDE_DIRECTORIES OPTIONS DEPENDS)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# Find `protoc`
if(NOT PROTOC_EXECUTABLE)
if(NOT TARGET protobuf::protoc)
message(FATAL_ERROR "qm_create_protobuf: protobuf compiler not found. Add find_package(Protobuf) to CMake to enable.")
endif()
get_target_property(PROTOC_EXECUTABLE protobuf::protoc LOCATION)
if(NOT PROTOC_EXECUTABLE)
message(FATAL_ERROR "qm_create_protobuf: failed to get the location of `protoc`, you could set PROTOC_EXECUTABLE manually.")
endif()
# Cache value
set(PROTOC_EXECUTABLE ${PROTOC_EXECUTABLE} PARENT_SCOPE)
endif()
if(NOT FUNC_INPUT)
message(FATAL_ERROR "qm_create_protobuf: INPUT not specified.")
endif()
if(FUNC_OUTPUT_DIR)
get_filename_component(_out_dir ${FUNC_OUTPUT_DIR} ABSOLUTE)
file(MAKE_DIRECTORY ${_out_dir})
else()
set(_out_dir ${CMAKE_CURRENT_BINARY_DIR})
endif()
# Collect include paths and out files
set(_include_options)
set(_out_files)
set(_input_names)
foreach(_item IN LISTS FUNC_INCLUDE_DIRECTORIES)
get_filename_component(_abs_path ${_item} ABSOLUTE)
list(APPEND _include_options -I${_abs_path})
endforeach()
foreach(_item IN LISTS FUNC_INPUT)
get_filename_component(_item ${_item} ABSOLUTE)
get_filename_component(_abs_path ${_item} DIRECTORY)
list(APPEND _include_options -I${_abs_path})
endforeach()
list(REMOVE_DUPLICATES _include_options)
set(_create_once_warning)
set(_create_once_warning_printed off)
# Prepare for create once
if(FUNC_CREATE_ONCE)
# Check if options contain generator expressions
foreach(_opt IN LISTS _include_options LISTS FUNC_OPTIONS)
string(GENEX_STRIP "${_opt}" _no_genex)
if(NOT _no_genex STREQUAL _opt)
set(_create_once_warning "options contain generator expressions, skip generating source file now")
break()
endif()
endforeach()
endif()
foreach(_item IN LISTS FUNC_INPUT)
get_filename_component(_basename ${_item} NAME_WLE)
list(APPEND _out_files ${_out_dir}/${_basename}.pb.h ${_out_dir}/${_basename}.pb.cc)
get_filename_component(_name ${_item} NAME)
list(APPEND _input_names ${_name})
if(FUNC_CREATE_ONCE AND(NOT EXISTS ${_out_dir}/${_basename}.pb.h OR NOT EXISTS ${_out_dir}/${_basename}.pb.cc))
if(_create_once_warning)
if(NOT _create_once_warning_printed)
message(WARNING "qm_create_protobuf: ${_create_once_warning}")
set(_create_once_warning_printed on)
endif()
else()
get_filename_component(_abs_file ${_item} ABSOLUTE)
if(NOT EXISTS ${_abs_file})
message(WARNING "qm_create_protobuf: input file \"${_name}\" is not available, skip generating source file now")
else()
message(STATUS "Protoc: Generating ${_basename}.pb.h, ${_basename}.pb.cc")
execute_process(COMMAND
${PROTOC_EXECUTABLE} --cpp_out=${_out_dir} ${_include_options} ${FUNC_OPTIONS} ${_name}
)
endif()
endif()
endif()
add_custom_command(
OUTPUT ${_out_dir}/${_basename}.pb.h ${_out_dir}/${_basename}.pb.cc
COMMAND ${PROTOC_EXECUTABLE} --cpp_out=${_out_dir} ${_include_options} ${FUNC_OPTIONS} ${_name}
DEPENDS ${_item} ${FUNC_DEPENDS}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
VERBATIM
)
endforeach()
if(FUNC_TARGET)
if(NOT TARGET ${FUNC_TARGET})
add_custom_target(${FUNC_TARGET})
endif()
add_dependencies(${FUNC_TARGET} ${_out_files})
endif()
set(${_out} ${_out_files} PARENT_SCOPE)
endfunction()

View File

@@ -0,0 +1,96 @@
include_guard(DIRECTORY)
#[[
Installs a QML module and its runtime loadable plugin, meta information, QML files, and resources.
The module is identified by the given target name.
The Qt version should be greater than or equal to 6.3.
qm_install_qml_modules(target
[PREFIX prefix]
)
Arguments:
PREFIX: install directory prefix (default: "qml")
Notice:
For static library backing targets, you should specify "OUTPUT_TARGETS" when calling "qt_add_qml_module()"
to collect the internally generated targets (mainly object libraries), and then install them by calling:
install(TARGETS ${_output_targets}
EXPORT <target set>
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
OBJECTS DESTINATION lib
)
See also: https://doc.qt.io/qt-6/qt-add-qml-module.html
]] #
function(qm_install_qml_modules _target)
set(options)
set(oneValueArgs PREFIX)
set(multiValueArgs)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(_prefix)
qm_set_value(_prefix FUNC_PREFIX "qml")
qt_query_qml_module(${_target}
URI _module_uri
VERSION _module_version
PLUGIN_TARGET _module_plugin_target
TARGET_PATH _module_target_path
QMLDIR _module_qmldir
TYPEINFO _module_typeinfo
QML_FILES _module_qml_files
QML_FILES_DEPLOY_PATHS _qml_files_deploy_paths
RESOURCES _module_resources
RESOURCES_DEPLOY_PATHS _resources_deploy_paths
)
# See also: https://doc.qt.io/qt-6/qt-query-qml-module.html#example
# Install the QML module runtime loadable plugin
set(_module_dir "${_prefix}/${_module_target_path}")
install(TARGETS "${_module_plugin_target}"
LIBRARY DESTINATION "${_module_dir}"
RUNTIME DESTINATION "${_module_dir}"
ARCHIVE DESTINATION "${_module_dir}"
)
# Install the QML module meta information.
install(FILES "${_module_qmldir}" DESTINATION "${_module_dir}")
install(FILES "${_module_typeinfo}" DESTINATION "${_module_dir}")
# Install QML files, possibly renamed.
list(LENGTH _module_qml_files _num_files)
if(_num_files GREATER 0)
math(EXPR _last_index "${_num_files} - 1")
foreach(_i RANGE 0 ${_last_index})
list(GET _module_qml_files ${_i} _src_file)
list(GET _qml_files_deploy_paths ${_i} _deploy_path)
get_filename_component(_dest_name "${_deploy_path}" NAME)
get_filename_component(_dest_dir "${_deploy_path}" DIRECTORY)
install(FILES "${_src_file}" DESTINATION "${_module_dir}/${_dest_dir}" RENAME "${_dest_name}")
endforeach()
endif()
# Install resources, possibly renamed.
list(LENGTH _module_resources _num_files)
if(_num_files GREATER 0)
math(EXPR _last_index "${_num_files} - 1")
foreach(_i RANGE 0 ${_last_index})
list(GET _module_resources ${_i} _src_file)
list(GET _resources_deploy_paths ${_i} _deploy_path)
get_filename_component(_dest_name "${_deploy_path}" NAME)
get_filename_component(_dest_dir "${_deploy_path}" DIRECTORY)
install(FILES "${_src_file}" DESTINATION "${_module_dir}/${_dest_dir}" RENAME "${_dest_name}")
endforeach()
endif()
endfunction()

View File

@@ -0,0 +1,374 @@
include_guard(DIRECTORY)
#[[
Add qt translation target.
qm_add_translation(<target>
[LOCALES locales]
[PREFIX prefix]
[SOURCES files... | DIRECTORIES dirs... | TARGETS targets... | TS_FILES files...]
[TS_DIR dir]
[QM_DIR dir]
[TS_OPTIONS options...]
[QM_OPTIONS options...]
[TS_DEPENDS targets...]
[QM_DEPENDS targets...]
[CREATE_ONCE]
)
Arguments:
LOCALES: language names, e.g. zh_CN en_US, must specify if SOURCES or TARGETS is specified
PREFIX: translation file prefix, default to target name
SOURCES: source files
DIRECTORIES: source directories
TARGETS: target names, the source files of which will be collected
TS_FILES: ts file names, add the specified ts file
TS_DIR: ts files destination, default to `CMAKE_CURRENT_SOURCE_DIR`
QM_DIR: qm files destination, default to `CMAKE_CURRENT_BINARY_DIR`
TS_DEPENDS: add lupdate task as a dependency to the given targets
QM_DEPENDS: add lrelease task as a dependency to the given targets
CREATE_ONCE: create translations at configure phase if not exist
]] #
function(qm_add_translation _target)
set(options CREATE_ONCE)
set(oneValueArgs PREFIX TS_DIR QM_DIR)
set(multiValueArgs LOCALES SOURCES DIRECTORIES TARGETS TS_FILES TS_OPTIONS QM_OPTIONS TS_DEPENDS QM_DEPENDS)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# Get linguist tools
if(NOT TARGET Qt${QT_VERSION_MAJOR}::lupdate OR NOT TARGET Qt${QT_VERSION_MAJOR}::lrelease)
message(FATAL_ERROR "qm_add_translation: linguist tools not defined. Add find_package(Qt\${QT_VERSION_MAJOR} COMPONENTS LinguistTools) to CMake to enable.")
endif()
set(_src_files)
set(_include_dirs)
# Collect source files
if(FUNC_SOURCES)
list(APPEND _src_files ${FUNC_SOURCES})
endif()
# Collect source files
if(FUNC_TARGETS)
foreach(_item IN LISTS FUNC_TARGETS)
get_target_property(_type ${_item} TYPE)
if((_type STREQUAL "UTILITY") OR(_type STREQUAL "INTERFACE_LIBRARY"))
continue()
endif()
set(_tmp_files)
get_target_property(_tmp_files ${_item} SOURCES)
set(_qml_files)
get_target_property(_qml_files ${_item} QT_QML_MODULE_QML_FILES)
if (NOT _qml_files STREQUAL ${_item}-NOTFOUND)
list(APPEND _tmp_files ${_qml_files})
endif()
list(FILTER _tmp_files INCLUDE REGEX ".+\\.(h|hh|hpp|hxx|c|cc|cpp|cxx|m|mm|qml|js|mjs)$")
list(FILTER _tmp_files EXCLUDE REGEX "^(qasc|moc)_.+")
# Need to convert to absolute path
get_target_property(_target_dir ${_item} SOURCE_DIR)
foreach(_file IN LISTS _tmp_files)
get_filename_component(_abs_file ${_file} ABSOLUTE BASE_DIR ${_target_dir})
list(APPEND _src_files ${_abs_file})
endforeach()
unset(_tmp_files)
get_target_property(_tmp_dirs ${_item} INCLUDE_DIRECTORIES)
list(APPEND _include_dirs ${_tmp_dirs})
endforeach()
endif()
# Collect source directories
if(FUNC_DIRECTORIES)
foreach(_item IN LISTS FUNC_DIRECTORIES)
file(GLOB _tmp
${_item}/*.h ${_item}/*.hpp
${_item}/*.hh ${_item}/*.hxx
${_item}/*.cpp ${_item}/*.cxx
${_item}/*.c ${_item}/*.cc
${_item}/*.m ${_item}/*.mm
)
list(APPEND _src_files ${_tmp})
endforeach()
endif()
if(_src_files)
if(NOT FUNC_LOCALES)
message(FATAL_ERROR "qm_add_translation: source files collected but LOCALES not specified!")
endif()
elseif(NOT FUNC_TS_FILES)
message(FATAL_ERROR "qm_add_translation: no source files or ts files collected!")
endif()
add_custom_target(${_target})
set(_qm_depends)
if(FUNC_TS_OPTIONS)
set(_ts_options ${FUNC_TS_OPTIONS})
endif()
if(FUNC_QM_OPTIONS)
set(_qm_options ${FUNC_QM_OPTIONS})
endif()
if(_src_files)
if(FUNC_PREFIX)
set(_prefix ${FUNC_PREFIX})
else()
set(_prefix ${_target})
endif()
if(FUNC_TS_DIR)
set(_ts_dir ${FUNC_TS_DIR})
else()
set(_ts_dir ${CMAKE_CURRENT_SOURCE_DIR})
endif()
set(_ts_files)
foreach(_loc IN LISTS FUNC_LOCALES)
list(APPEND _ts_files ${_ts_dir}/${_prefix}_${_loc}.ts)
endforeach()
# Include options
set(_include_options)
foreach(_inc IN LISTS _include_dirs)
list(APPEND _include_options "-I${_inc}")
endforeach()
# May be an lupdate bug, so we skip passing include directories
# list(APPEND _ts_options ${_include_options})
if(_ts_options)
list(PREPEND _ts_options OPTIONS)
endif()
set(_create_once)
if(FUNC_CREATE_ONCE)
set(_create_once CREATE_ONCE)
endif()
_qm_add_lupdate_target(${_target}_lupdate
INPUT ${_src_files}
OUTPUT ${_ts_files}
${_ts_options}
${_create_once}
)
# Add update dependencies
# add_dependencies(${_target} ${_target}_lupdate)
foreach(_item IN LISTS FUNC_TS_DEPENDS)
add_dependencies(${_item} ${_target}_lupdate)
endforeach()
# list(APPEND _qm_depends DEPENDS ${_target}_lupdate)
else()
if(FUNC_PREFIX)
message(WARNING "qm_add_translation: no source files collected, PREFIX ignored")
endif()
if(FUNC_TS_DIR)
message(WARNING "qm_add_translation: no source files collected, TS_DIR ignored")
endif()
if(FUNC_TS_DEPENDS)
message(WARNING "qm_add_translation: no source files collected, TS_DEPENDS ignored")
endif()
endif()
if(FUNC_QM_DIR)
set(_qm_dir ${FUNC_QM_DIR})
else()
set(_qm_dir ${CMAKE_CURRENT_BINARY_DIR})
endif()
set(_qm_target)
if(_qm_options)
list(PREPEND _qm_options OPTIONS)
endif()
_qm_add_lrelease_target(${_target}_lrelease
INPUT ${_ts_files} ${FUNC_TS_FILES}
DESTINATION ${_qm_dir}
${_qm_options}
${_qm_depends}
)
add_dependencies(${_target} ${_target}_lrelease)
# Add release dependencies
if(FUNC_TARGETS)
foreach(_item IN LISTS FUNC_TARGETS)
add_dependencies(${_item} ${_target}_lrelease)
endforeach()
endif()
foreach(_item IN LISTS FUNC_QM_DEPENDS)
add_dependencies(${_item} ${_target}_lrelease)
endforeach()
endfunction()
# ----------------------------------
# Private functions
# ----------------------------------
# Input: cxx source files
# Output: target ts files
function(_qm_add_lupdate_target _target)
set(options CREATE_ONCE)
set(oneValueArgs)
set(multiValueArgs INPUT OUTPUT OPTIONS DEPENDS)
cmake_parse_arguments(_LUPDATE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(_lupdate_deps ${_LUPDATE_DEPENDS})
set(_my_sources ${_LUPDATE_INPUT})
set(_my_tsfiles ${_LUPDATE_OUTPUT})
add_custom_target(${_target} DEPENDS ${_lupdate_deps})
qm_get_executable_location(Qt${QT_VERSION_MAJOR}::lupdate _lupdate_exe)
set(_create_once_warning)
set(_create_once_warning_printed off)
# Prepare for create once
if(_LUPDATE_CREATE_ONCE)
# Check if all src files are available
foreach(_file IN LISTS _my_sources)
get_filename_component(_abs_file ${_file} ABSOLUTE)
if(NOT EXISTS ${_abs_file})
get_filename_component(_file ${_file} NAME)
set(_create_once_warning "source file \"${_file}\" is not available, skip generating ts file now")
break()
endif()
endforeach()
# Check if options contain generator expressions
if(NOT _create_once_warning)
foreach(_opt IN LISTS _LUPDATE_OPTIONS)
string(GENEX_STRIP "${_opt}" _no_genex)
if(NOT _no_genex STREQUAL _opt)
set(_create_once_warning "lupdate options contain generator expressions, skip generating ts file now")
break()
endif()
endforeach()
endif()
endif()
foreach(_ts_file IN LISTS _my_tsfiles)
# make a list file to call lupdate on, so we don't make our commands too
# long for some systems
get_filename_component(_ts_name ${_ts_file} NAME)
set(_ts_lst_file "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${_ts_name}_lst_file")
set(_lst_file_srcs)
foreach(_lst_file_src IN LISTS _my_sources)
set(_lst_file_srcs "${_lst_file_src}\n${_lst_file_srcs}")
endforeach()
get_directory_property(_inc_DIRS INCLUDE_DIRECTORIES)
foreach(_pro_include IN LISTS _inc_DIRS)
get_filename_component(_abs_include "${_pro_include}" ABSOLUTE)
set(_lst_file_srcs "-I${_pro_include}\n${_lst_file_srcs}")
endforeach()
file(WRITE ${_ts_lst_file} "${_lst_file_srcs}")
get_filename_component(_ts_abs ${_ts_file} ABSOLUTE)
if(_LUPDATE_CREATE_ONCE AND NOT EXISTS ${_ts_abs})
if(_create_once_warning)
if(NOT _create_once_warning_printed)
message(WARNING "qm_add_translation: ${_create_once_warning}")
set(_create_once_warning_printed on)
endif()
else()
message(STATUS "Lupdate: Generating ${_ts_name}")
get_filename_component(_abs_file ${_ts_file} ABSOLUTE)
get_filename_component(_dir ${_abs_file} DIRECTORY)
file(MAKE_DIRECTORY ${_dir})
execute_process(
COMMAND ${_lupdate_exe} ${_LUPDATE_OPTIONS} "@${_ts_lst_file}" -ts ${_ts_file}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_QUIET
COMMAND_ERROR_IS_FATAL ANY
)
endif()
endif()
add_custom_command(
TARGET ${_target} POST_BUILD
COMMAND ${_lupdate_exe}
ARGS ${_LUPDATE_OPTIONS} "@${_ts_lst_file}" -ts ${_ts_file}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
BYPRODUCTS ${_ts_lst_file}
VERBATIM
)
endforeach()
endfunction()
# Input: ts files
# Output: list to append qm files
function(_qm_add_lrelease_target _target)
set(options)
set(oneValueArgs DESTINATION OUTPUT)
set(multiValueArgs INPUT OPTIONS DEPENDS)
cmake_parse_arguments(_LRELEASE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(_lrelease_files ${_LRELEASE_INPUT})
set(_lrelease_deps ${_LRELEASE_DEPENDS})
qm_get_executable_location(Qt${QT_VERSION_MAJOR}::lrelease _lrelease_exe)
set(_qm_files)
foreach(_file IN LISTS _lrelease_files)
get_filename_component(_abs_FILE ${_file} ABSOLUTE)
get_filename_component(_qm_file ${_file} NAME)
# everything before the last dot has to be considered the file name (including other dots)
string(REGEX REPLACE "\\.[^.]*$" "" FILE_NAME ${_qm_file})
get_source_file_property(output_location ${_abs_FILE} OUTPUT_LOCATION)
if(output_location)
set(_out_dir ${output_location})
elseif(_LRELEASE_DESTINATION)
set(_out_dir ${_LRELEASE_DESTINATION})
else()
set(_out_dir ${CMAKE_CURRENT_BINARY_DIR})
endif()
set(_qm_file "${_out_dir}/${FILE_NAME}.qm")
add_custom_command(
OUTPUT ${_qm_file}
COMMAND ${CMAKE_COMMAND} -E make_directory ${_out_dir}
COMMAND ${_lrelease_exe} ARGS ${_LRELEASE_OPTIONS} ${_abs_FILE} -qm ${_qm_file}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${_lrelease_files}
VERBATIM
)
list(APPEND _qm_files ${_qm_file})
endforeach()
add_custom_target(${_target} ALL DEPENDS ${_lrelease_deps} ${_qm_files})
if(_LRELEASE_OUTPUT)
set(${_LRELEASE_OUTPUT} ${_qm_files} PARENT_SCOPE)
endif()
endfunction()

View File

@@ -0,0 +1,68 @@
#[[
Warning: This module is private, may be modified or removed in the future, please use with caution.
]] #
include_guard(DIRECTORY)
#[[
Create the names of output files preserving relative dirs. (Ported from MOC command)
qm_make_output_file(<infile> <prefix> <ext> <OUT>)
OUT: output source file paths
#]]
function(qm_make_output_file _infile _prefix _ext _out)
string(LENGTH ${CMAKE_CURRENT_BINARY_DIR} _binlength)
string(LENGTH ${_infile} _infileLength)
set(_checkinfile ${CMAKE_CURRENT_SOURCE_DIR})
if(_infileLength GREATER _binlength)
string(SUBSTRING "${_infile}" 0 ${_binlength} _checkinfile)
if(_checkinfile STREQUAL "${CMAKE_CURRENT_BINARY_DIR}")
file(RELATIVE_PATH _name ${CMAKE_CURRENT_BINARY_DIR} ${_infile})
else()
file(RELATIVE_PATH _name ${CMAKE_CURRENT_SOURCE_DIR} ${_infile})
endif()
else()
file(RELATIVE_PATH _name ${CMAKE_CURRENT_SOURCE_DIR} ${_infile})
endif()
if(CMAKE_HOST_WIN32 AND _name MATCHES "^([a-zA-Z]):(.*)$") # absolute path
set(_name "${CMAKE_MATCH_1}_${CMAKE_MATCH_2}")
endif()
set(_outfile "${CMAKE_CURRENT_BINARY_DIR}/${_name}")
string(REPLACE ".." "__" _outfile ${_outfile})
get_filename_component(_outpath ${_outfile} PATH)
get_filename_component(_outfile ${_outfile} NAME_WLE)
file(MAKE_DIRECTORY ${_outpath})
set(${_out} ${_outpath}/${_prefix}${_outfile}.${_ext} PARENT_SCOPE)
endfunction()
#[[
Create a custom command to run `xxd`.
qm_add_binary_resource(<input> <output>)
#]]
function(qm_add_binary_resource _input _output)
find_program(_xxd "xxd")
if(NOT _xxd)
get_filename_component(_name ${_output} NAME)
string(MAKE_C_IDENTIFIER ${_name} _name)
set(_cmd "${CMAKE_COMMAND}"
-D "input=${_input}"
-D "output=${_output}"
-D "name=${_name}"
-P "${QMSETUP_MODULES_DIR}/scripts/xxd.cmake")
else()
set(_cmd "${_xxd}" "-i" "${_input}" "${_output}")
endif()
add_custom_command(
OUTPUT ${_output}
COMMAND ${_cmd}
DEPENDS ${_input}
)
endfunction()

View File

@@ -0,0 +1,158 @@
#[[
Warning: This module is private, may be modified or removed in the future, please use with caution.
]] #
if(NOT DEFINED QMSETUP_PACKAGE_BUILD_TYPE)
set(QMSETUP_PACKAGE_BUILD_TYPE "Release")
endif()
include_guard(DIRECTORY)
#[[
Install external package at configuration phase.
qm_install_package(<name>
[SOURCE_DIR <dir>]
[BUILD_TREE_DIR <dir>]
[INSTALL_DIR <dir>]
[CMAKE_PACKAGE_SUBDIR <subdir>]
[BUILD_TYPE <type>]
[CONFIGURE_ARGS <arg...>]
[RESULT_PATH <VAR>]
)
]] #
function(qm_install_package _name)
set(options)
set(oneValueArgs SOURCE_DIR BUILD_TREE_DIR INSTALL_DIR CMAKE_PACKAGE_SUBDIR BUILD_TYPE RESULT_PATH)
set(multiValueArgs CONFIGURE_ARGS)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# Directories
if(NOT FUNC_SOURCE_DIR)
message(FATAL_ERROR "pre_install_package: SOURCE_DIR is not spefified")
endif()
set(_src_dir ${FUNC_SOURCE_DIR})
if(FUNC_BUILD_TREE_DIR)
set(_build_tree_dir ${FUNC_BUILD_TREE_DIR})
else()
set(_build_tree_dir ${CMAKE_BINARY_DIR}/_build)
endif()
if(FUNC_INSTALL_DIR)
set(_install_dir ${FUNC_INSTALL_DIR})
else()
set(_install_dir ${CMAKE_BINARY_DIR}/_install)
endif()
if(FUNC_CMAKE_PACKAGE_SUBDIR)
set(_cmake_subdir ${FUNC_CMAKE_PACKAGE_SUBDIR})
else()
include(GNUInstallDirs)
set(_cmake_subdir "${CMAKE_INSTALL_LIBDIR}/cmake/${_name}")
endif()
# Build types
if(FUNC_BUILD_TYPE)
set(_build_type -DCMAKE_BUILD_TYPE=${FUNC_BUILD_TYPE})
elseif(CMAKE_BUILD_TYPE)
set(_build_type -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE})
elseif(NOT CMAKE_CONFIGURATION_TYPES)
set(_build_type -DCMAKE_BUILD_TYPE=${QMSETUP_PACKAGE_BUILD_TYPE})
else()
set(_build_type)
endif()
if(FUNC_BUILD_TYPE)
set(_build_types ${FUNC_BUILD_TYPE})
else()
set(_build_types ${QMSETUP_PACKAGE_BUILD_TYPE})
endif()
# Do it
set(_install_cmake_dir ${_install_dir}/${_cmake_subdir})
set(_build_dir ${_build_tree_dir}/${_name})
if(NOT IS_DIRECTORY ${_install_cmake_dir})
# Determine generator
set(_extra_args)
if(CMAKE_GENERATOR)
set(_extra_args -G "${CMAKE_GENERATOR}")
endif()
if(CMAKE_GENERATOR_PLATFORM)
set(_extra_args -A "${CMAKE_GENERATOR_PLATFORM}")
endif()
# Remove old build directory
if(IS_DIRECTORY ${_build_dir})
file(REMOVE_RECURSE ${_build_dir})
endif()
file(MAKE_DIRECTORY ${_build_tree_dir})
# Configure
message(STATUS "Configuring ${_name}...")
set(_log_file ${_build_tree_dir}/${_name}_configure.log)
execute_process(
COMMAND ${CMAKE_COMMAND} -S ${_src_dir} -B ${_build_dir}
${_extra_args} ${_build_type}
# Pass through CMAKE_INSTALL_LIBDIR to ensure the package uses the same
# lib directory convention (e.g., lib vs lib64) as the parent project.
# Without this, packages using GNUInstallDirs may default to a different
# directory structure than expected.
"-DCMAKE_INSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR}"
"-DCMAKE_INSTALL_PREFIX=${_install_dir}" ${FUNC_CONFIGURE_ARGS}
OUTPUT_FILE ${_log_file}
ERROR_FILE ${_log_file}
RESULT_VARIABLE _code
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
if(NOT ${_code} EQUAL 0)
message(FATAL_ERROR "Configure failed, check \"${_log_file}\"")
endif()
# Build
foreach(_item IN LISTS _build_types)
message(STATUS "Building ${_name} (${_item})...")
set(_log_file ${_build_tree_dir}/${_name}_build-${_item}.log)
execute_process(
COMMAND ${CMAKE_COMMAND} --build ${_build_dir} --config ${_item} --parallel
OUTPUT_FILE ${_log_file}
ERROR_FILE ${_log_file}
RESULT_VARIABLE _code
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
if(NOT ${_code} EQUAL 0)
message(FATAL_ERROR "Build failed, check \"${_log_file}\"")
endif()
endforeach()
# Install
foreach(_item IN LISTS _build_types)
message(STATUS "Installing ${_name} (${_item})...")
set(_log_file ${_build_tree_dir}/${_name}_install-${_item}.log)
execute_process(
COMMAND ${CMAKE_COMMAND} --build ${_build_dir} --config ${_item} --target install
OUTPUT_FILE ${_log_file}
ERROR_FILE ${_log_file}
RESULT_VARIABLE _code
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
if(NOT ${_code} EQUAL 0)
message(FATAL_ERROR "Install failed, check \"${_log_file}\"")
endif()
endforeach()
endif()
if(FUNC_RESULT_PATH)
set(${FUNC_RESULT_PATH} ${_install_cmake_dir} PARENT_SCOPE)
endif()
endfunction()

View File

@@ -0,0 +1,37 @@
include_guard(DIRECTORY)
function(qm_get_windows_proxy _out)
if(NOT WIN32)
return()
endif()
execute_process(
COMMAND reg query "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable
OUTPUT_VARIABLE _proxy_enable_output
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if(NOT _proxy_enable_output MATCHES "ProxyEnable[ \t\r\n]+REG_DWORD[ \t\r\n]+0x1")
return()
endif()
execute_process(
COMMAND reg query "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyServer
OUTPUT_VARIABLE _proxy_server_output
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if(NOT _proxy_server_output MATCHES "ProxyServer[ \t\r\n]+REG_SZ[ \t\r\n]+(.*)")
return()
endif()
set(${_out} ${CMAKE_MATCH_1} PARENT_SCOPE)
endfunction()
macro(qm_set_proxy_env _proxy)
set(ENV{HTTP_PROXY} "http://${_proxy}")
set(ENV{HTTPS_PROXY} "http://${_proxy}")
set(ENV{ALL_PROXY} "http://${_proxy}")
endmacro()

View File

@@ -0,0 +1,19 @@
# MIT License
# Copyright (c) 2023 SineStriker
# Description:
# Command that executes `configure_file`.
# Usage:
# cmake
# -D input=<file>
# -D output=<file>
# -D force=TRUE/FALSE
# [-D args=<args...>]
# -P configure_file.cmake
if(force AND EXISTS ${_output})
file(REMOVE ${_output})
endif()
configure_file(${input} ${output} ${args})

View File

@@ -0,0 +1,64 @@
# MIT License
# Copyright (c) 2023 SineStriker
# Description:
# Copy file or directory to destination if different.
# Mainly use `file(INSTALL)` to implement.
# Usage:
# cmake
# -D src=<files/dirs...>
# -D dest=<dir>
# [-D dest_base=<dir>]
# [-D args=<args...>]
# -P copy.cmake
# Check required arguments
if(NOT src)
message(FATAL_ERROR "src not defined.")
endif()
if(NOT dest)
message(FATAL_ERROR "dest not defined.")
endif()
# Calculate destination
if(dest_base)
set(_dest_base ${dest_base})
else()
set(_dest_base ${CMAKE_BINARY_DIR})
endif()
get_filename_component(_dest ${dest} ABSOLUTE BASE_DIR ${_dest_base})
# Copy
foreach(_file IN LISTS src)
# Avoid using `get_filename_component` to keep the trailing slash
set(_path ${_file})
if(NOT IS_ABSOLUTE ${_path})
set(_path "${CMAKE_BINARY_DIR}/${_path}")
endif()
if(IS_DIRECTORY ${_path})
file(INSTALL DESTINATION ${_dest}
TYPE DIRECTORY
FILES ${_path}
${args}
)
else()
set(_paths)
if(${_path} MATCHES "\\*\\*")
file(GLOB_RECURSE _paths ${_path})
else()
file(GLOB _paths ${_path})
endif()
file(INSTALL DESTINATION ${_dest}
TYPE FILE
FILES ${_paths}
${args}
)
endif()
endforeach()

View File

@@ -0,0 +1,245 @@
#!/bin/bash
# MIT License
# Copyright (c) 2023 SineStriker
# Description: This script calls `qmcorecmd` to deploy dependencies on Mac/Linux.
# Show usage
usage() {
echo "Usage: $(basename $0) -i <dir> -m <path>"
echo " --plugindir <plugin_dir> --libdir <lib_dir> --qmldir <qml_dir>"
echo " [--qmake <qmake_path>] [--extra <extra_path>]..."
echo " [--qml <qml_module>]... [--plugin <plugin>]... [--copy <src> <dest>]..."
echo " [-f] [-s] [-V] [-h]"
echo " -i <input_dir> Directory containing binaries and libraries"
echo " -m <corecmd_path> Path to corecmd"
echo " --plugindir <plugin_dir> Output directory for plugins"
echo " --libdir <lib_dir> Output directory for libraries"
echo " --qmldir <qml_dir> Output directory for QML files"
echo " --qmake <qmake_path> Path to qmake (optional)"
echo " --extra <extra_path> Extra plugin searching path (repeatable)"
echo " --qml <qml_module> Relative path to QML directory (repeatable)"
echo " --plugin <plugin> Specify a Qt plugin to deploy (repeatable)"
echo " --copy <src> <dest> Specify additional binary file to copy and its destination directory (repeatable)"
echo " --linkdirs-file Add library searching paths from a list file"
echo " -L Add a library searching path"
echo " -f Force overwrite existing files"
echo " -s Ignore C/C++ runtime and system libraries"
echo " -V Show verbose output"
echo " -h Show this help message"
}
# Initialize arguments
EXTRA_PLUGIN_PATHS=()
QML_REL_PATHS=()
ARGS=()
VERBOSE=""
PLUGINS=()
FILES=""
# Parse command line
while (( "$#" )); do
case "$1" in
-i) INPUT_DIR="$2"; shift 2;;
-m) CORECMD_PATH="$2"; shift 2;;
-L) ARGS+=("-L \"$2\""); shift 2;;
--linkdirs-file) ARGS+=("--linkdirs-file \"$2\""); shift 2;;
--plugindir) PLUGIN_DIR="$2"; shift 2;;
--libdir) LIB_DIR="$2"; shift 2;;
--qmldir) QML_DIR="$2"; shift 2;;
--qmake) QMAKE_PATH="$2"; shift 2;;
--extra) EXTRA_PLUGIN_PATHS+=("$2"); shift 2;;
--plugin) PLUGINS+=("$2"); shift 2;;
--qml) QML_REL_PATHS+=("$2"); shift 2;;
--copy) ARGS+=("-c \"$2\" \"$3\""); shift 3;;
-f|-s) ARGS+=("$1"); shift;;
-V) VERBOSE="-V"; shift;;
-h) usage; exit 0;;
*) echo "Error: Unsupported argument $1"; usage; exit 1;;
esac
done
# Check required arguments
for arg in INPUT_DIR PLUGIN_DIR LIB_DIR QML_DIR CORECMD_PATH; do
if [[ -z ${!arg} ]]; then
echo "Error: Missing required argument '$arg'"
usage
exit 1
fi
done
# Get Qt plugin and QML paths
PLUGIN_PATHS=()
QML_PATH=""
if [[ -n "$QMAKE_PATH" ]]; then
QMAKE_PLUGIN_PATH=$($QMAKE_PATH -query QT_INSTALL_PLUGINS)
PLUGIN_PATHS+=("$QMAKE_PLUGIN_PATH")
QML_PATH=$($QMAKE_PATH -query QT_INSTALL_QML)
fi
# Add extra plugin searching paths
PLUGIN_PATHS+=("${EXTRA_PLUGIN_PATHS[@]}")
# Ensure that the QML search path is not empty
# when the QML related path is specified (qmake needs to be specified)
if [[ ${#QML_REL_PATHS[@]} -gt 0 && -z "$QML_PATH" ]]; then
echo "Error: qmake path must be specified when QML paths are provided"
usage
exit 1
fi
# Search input directory
search_input_dir() {
local path="$1"
for item in "$path"/*; do
if [ -d "$item" ]; then
# Check if the directory is mac .framework
if [[ "OSTYPE" == "darwin"* ]] && [[ "$item" == *.framework ]]; then
FILES="$FILES \"$item\""
else
search_input_dir "$item"
fi
elif [ -f "$item" ]; then
if [[ "$OSTYPE" == "msys"* ]] || [[ "$OSTYPE" == "win32" ]] || [[ "$OSTYPE" == "cygwin"* ]]; then
# On Windows, search for.exe and.dll files
FILES="$FILES \"$item\""
else
# On Unix, traverse all files, using the file command to
# check for executable binary files
file_type=$(file -b "$item")
if [[ ($file_type == "ELF"* || $file_type == "Mach-O"*) && -x "$item" ]]; then
FILES="$FILES \"$item\""
fi
fi
fi
done
}
search_input_dir "$INPUT_DIR"
# Find the full path to the Qt plugin
for plugin_path in "${PLUGINS[@]}"; do
# Check format
if [[ $plugin_path == */* ]]; then
IFS='/' read -r -a plugin_parts <<< "$plugin_path"
# Extracts the category and name
category=${plugin_parts[0]}
name=${plugin_parts[1]}
# Calculate destination directory
dest_dir="${PLUGIN_DIR}/${category}"
# Initialize an array to store found plugins
found_plugins=""
# Traverse the path and find the specific plug-in files
for search_path in "${PLUGIN_PATHS[@]}"; do
while IFS= read -r plugin; do
# Get name
plugin_name=$(basename "$plugin")
# Check if the plugin was already found to avoid duplicates
if [[ ! $found_plugins =~ $plugin_name ]]; then
found_plugins+="$plugin_name "
ARGS+=("-c \"$plugin\" \"$dest_dir\"")
fi
done < <(find "${search_path}/${category}" -name "lib${name}.*" ! -name "*debug*" ! -type d -print)
done
if [ ${#found_plugins[@]} -eq 0 ]; then
echo "Error: Plugin '${plugin_path}' not found in any search paths."
exit 1
fi
else
echo "Error: Invalid plugin format '${plugin_path}'. Expected format: <category>/<name>"
exit 1
fi
done
# Process QML item
handle_qml_file() {
local file="$1"
local rel_path="${file#$QML_PATH/}"
local target="$QML_DIR/$rel_path"
local target_dir="$(dirname "$target")"
# Directory: must be mac framework
if [ -d "$file" ]; then
ARGS+=("-c \"$file\" \"$target_dir\"")
return
fi
# Ignore specific files
if [[ "$OSTYPE" == "msys"* ]] || [[ "$OSTYPE" == "win32" ]] || [[ "$OSTYPE" == "cygwin"* ]]; then
if [[ "$file" == *.pdb ]] || [[ "$file" == *d.dll ]]; then
return
fi
else
if [[ "$file" == *_debug.dylib ]] || [[ "$file" == *.so.debug ]]; then
return
fi
fi
# Determine whether it is an executable binary file and handle it accordingly
if [[ "$OSTYPE" == "msys"* ]] || [[ "$OSTYPE" == "win32" ]] || [[ "$OSTYPE" == "cygwin"* ]]; then
if [[ "$file" == *.dll || "$file" == *.exe ]]; then
ARGS+=("-c \"$file\" \"$target_dir\"")
else
mkdir -p "$target_dir"
cp "$file" "$target"
fi
else
file_type=$(file -b "$file")
if [[ ($file_type == "ELF"* || $file_type == "Mach-O"*) && -x "$file" ]]; then
ARGS+=("-c \"$file\" \"$target_dir\"")
else
mkdir -p "$target_dir"
cp "$file" "$target"
fi
fi
}
# Search QML directory
search_qml_dir() {
local path="$1"
for item in "$path"/*; do
if [ -d "$item" ]; then
# Check if the path is mac .framework
if [[ "OSTYPE" == "darwin"* ]] && [[ "$item" == *.framework ]]; then
handle_qml_file "$item"
else
search_qml_dir "$item"
fi
elif [ -f "$item" ]; then
handle_qml_file "$item"
fi
done
}
# Process QML directories
for qml_rel_path in "${QML_REL_PATHS[@]}"; do
full_path="$QML_PATH/$qml_rel_path"
if [[ -d "$full_path" ]]; then
# Directory
search_qml_dir "$full_path"
elif [[ -f "$full_path" ]]; then
# File
handle_qml_file "$full_path" "$QML_DIR"
fi
done
# Build and execute the deploy command
DEPLOY_CMD="$CORECMD_PATH deploy $FILES ${ARGS[@]} -o \"$LIB_DIR\" $VERBOSE"
if [[ "$VERBOSE" == "-V" ]]; then
echo "Executing: $DEPLOY_CMD"
fi
eval $DEPLOY_CMD
# Check the deployment result
if [ $? -ne 0 ]; then
exit 1
fi

View File

@@ -0,0 +1,294 @@
:: MIT License
:: Copyright (c) 2023 SineStriker
:: Description: This script calls `qmcorecmd` to deploy dependencies on Windows.
@echo off
setlocal enabledelayedexpansion
:: Initialize arguments
set "INPUT_DIR="
set "PLUGIN_DIR="
set "LIB_DIR="
set "QML_DIR="
set "QMAKE_PATH="
set "CORECMD_PATH="
set "VERBOSE="
set "FILES="
set "EXTRA_PLUGIN_PATHS="
set "PLUGINS=" & set /a "PLUGIN_COUNT=0"
set "QML_REL_PATHS="
set "ARGS=" & set /a "ARG_COUNT=0"
:: Parse command line
:parse_args
if "%~1"=="" goto :end_parse_args
if "%1"=="-i" set "INPUT_DIR=%~2" & shift & shift & goto :parse_args
if "%1"=="-m" set "CORECMD_PATH=%~2" & shift & shift & goto :parse_args
if "%1"=="--plugindir" set "PLUGIN_DIR=%~2" & shift & shift & goto :parse_args
if "%1"=="--libdir" set "LIB_DIR=%~2" & shift & shift & goto :parse_args
if "%1"=="--qmldir" set "QML_DIR=%~2" & shift & shift & goto :parse_args
if "%1"=="--qmake" set "QMAKE_PATH=%~2" & shift & shift & goto :parse_args
if "%1"=="--extra" set "EXTRA_PLUGIN_PATHS=!EXTRA_PLUGIN_PATHS! %~2" & shift & shift & goto :parse_args
if "%1"=="--plugin" set /a "PLUGIN_COUNT+=1" & set "PLUGINS[!PLUGIN_COUNT!]=%~2" & shift & shift & goto :parse_args
if "%1"=="--qml" set "QML_REL_PATHS=!QML_REL_PATHS! %~2" & shift & shift & goto :parse_args
if "%1"=="--copy" call :push_args -c %~2 %~3 & shift & shift & shift & goto :parse_args
if "%1"=="-f" call :push_args -f & shift & goto :parse_args
if "%1"=="-s" call :push_args -s & shift & goto :parse_args
if "%1"=="-V" set "VERBOSE=-V" & shift & goto :parse_args
if "%1"=="-h" call :usage & exit /b
if "%1"=="--linkdirs-file" call :push_args --linkdirs-file %~2 & shift & shift & goto :parse_args
if "%1"=="-L" call :push_args -L %~2 & shift & shift & goto :parse_args
shift
goto :parse_args
:end_parse_args
:: Check required arguments
if not defined INPUT_DIR echo Error: Missing required argument 'INPUT_DIR' & call :usage & exit /b
if not defined PLUGIN_DIR echo Error: Missing required argument 'PLUGIN_DIR' & call :usage & exit /b
if not defined LIB_DIR echo Error: Missing required argument 'LIB_DIR' & call :usage & exit /b
if not defined QML_DIR echo Error: Missing required argument 'QML_DIR' & call :usage & exit /b
if not defined CORECMD_PATH echo Error: Missing required argument 'CORECMD_PATH' & call :usage & exit /b
:: Normalize
set "INPUT_DIR=!INPUT_DIR:/=\!"
set "PLUGIN_DIR=!PLUGIN_DIR:/=\!"
set "LIB_DIR=!LIB_DIR:/=\!"
set "QML_DIR=!QML_DIR:/=\!"
set "CORECMD_PATH=!CORECMD_PATH:/=\!"
:: Get Qt plugin and QML paths
set "PLUGIN_PATHS="
set "QML_PATH="
if defined QMAKE_PATH (
for /f "tokens=*" %%a in ('!QMAKE_PATH! -query QT_INSTALL_PLUGINS') do set "QMAKE_PLUGIN_PATH=%%a"
set "PLUGIN_PATHS=!QMAKE_PLUGIN_PATH!"
for /f "tokens=*" %%a in ('!QMAKE_PATH! -query QT_INSTALL_QML') do set "QML_PATH=%%a"
set "QML_PATH=!QML_PATH:/=\!"
:: Add Qt bin directory
for /f "tokens=*" %%a in ('!QMAKE_PATH! -query QT_INSTALL_BINS') do set "QT_BIN_PATH=%%a"
call :push_args -L !QT_BIN_PATH!
)
:: Add extra plugin searching paths
set "PLUGIN_PATHS=!PLUGIN_PATHS! !EXTRA_PLUGIN_PATHS!"
:: Ensure that the QML search path is not empty
:: when the QML related path is specified (qmake needs to be specified)
if not "%QML_REL_PATHS%"=="" (
if "%QML_PATH%"=="" (
echo Error: qmake path must be specified when QML paths are provided
exit /b
)
)
:: The type of file to be searched depends on the operating system
:: On Windows, search for.exe and.dll files
for /r "%INPUT_DIR%" %%f in (*.exe *.dll) do (
set "FILES=!FILES! %%f"
)
:: Find the full path to the Qt plugin
for /L %%i in (1,1,%PLUGIN_COUNT%) do (
set "plugin_path=!PLUGINS[%%i]!"
:: Check format
echo !plugin_path! | findstr /R "[^/]*\/[^/]*" >nul
if errorlevel 1 (
echo Error: Invalid plugin format '!plugin_path!'. Expected format: ^<category^>/^<name^>
exit /b
)
:: Extracts the category and name
for /f "tokens=1,2 delims=/" %%a in ("!plugin_path!") do (
set "category=%%a"
set "name=%%b"
:: Calculate destination directory
set "DESTINATION_DIR=!PLUGIN_DIR!\!category!"
set "DESTINATION_DIR=!DESTINATION_DIR:/=\!"
:: Traverse the path and find the specific plug-in file
set "FOUND_PLUGINS="
call :search_plugin
if not defined FOUND_PLUGINS (
echo Error: Plugin '!plugin_path!' not found in any search paths.
exit /b
)
)
)
:: Process QML directories
for %%q in (%QML_REL_PATHS%) do (
call :search_qml_dir "%%q"
)
:: Build and execute the deploy
set "ARGS_FILE=windeps_args.txt"
if exist "%ARGS_FILE%" del %ARGS_FILE%
for %%a in (!FILES!) do (
echo %%a >> %ARGS_FILE%
)
for /L %%i in (1,1,%ARG_COUNT%) do (
echo !ARGS[%%i]! >> %ARGS_FILE%
)
echo -o >> %ARGS_FILE%
echo !LIB_DIR! >> %ARGS_FILE%
echo !VERBOSE! >> %ARGS_FILE%
set "DEPLOY_CMD=!CORECMD_PATH! deploy @!ARGS_FILE!"
call !DEPLOY_CMD!
:: Check the deployment result
if %errorlevel% neq 0 exit /b
del %ARGS_FILE%
exit /b
:: ----------------------------------------------------------------------------------
:: Add args
:push_args
for %%x in (%*) do set /a "ARG_COUNT+=1" & set "ARGS[!ARG_COUNT!]=%%x"
exit /b
:: ----------------------------------------------------------------------------------
:: ----------------------------------------------------------------------------------
:: Search plugins
:search_plugin
for %%d in (!PLUGIN_PATHS!) do (
for %%f in ("%%d\!category!\!name!.dll") do (
if exist "%%f" (
call :check_debug "%%f"
if "!ok!"=="0" (
call :add_plugin "%%f"
)
)
)
)
exit /b
:: ----------------------------------------------------------------------------------
:: ----------------------------------------------------------------------------------
:: Add plugin if not already found
:add_plugin
set "plugin=%~1"
set "plugin_name=%~nx1"
for %%i in (!FOUND_PLUGINS!) do (
if "%%i"=="!plugin_name!" (
exit /b
)
)
set "FOUND_PLUGINS=!FOUND_PLUGINS! !plugin_name!"
call :push_args -c !plugin! !DESTINATION_DIR!
exit /b
:: ----------------------------------------------------------------------------------
:: ----------------------------------------------------------------------------------
:: Search QML directory
:search_qml_dir
set "full_path=%QML_PATH%\%~1"
if exist "%full_path%\" (
:: Directory
for /r "%full_path%" %%f in (*) do (
call :handle_qml_file "%%f"
)
) else if exist "%full_path%" (
:: File
call :handle_qml_file "%full_path%"
)
exit /b
:: ----------------------------------------------------------------------------------
:: ----------------------------------------------------------------------------------
:: Check debug version of a dll
:check_debug
set "ok=1"
set "file_path=%~1"
if "!file_path:~-4!"==".pdb" exit /b
if "!file_path:~-10!"==".dll.debug" exit /b
if "!file_path:~-5!" == "d.dll" (
set "prefix=!file_path:~0,-5!"
if exist "!prefix!.dll" (
exit /b
)
)
set "ok=0"
exit /b
:: ----------------------------------------------------------------------------------
:: ----------------------------------------------------------------------------------
:: Copy or add to a deployment command
:handle_qml_file
set "file=%~1"
set "file=!file:/=\!"
:: Ignore specific files (example)
call :check_debug "%file%"
if "!ok!"=="1" (
exit /b
)
:: Computes target file and folder in a very stupid way
set "rel_path=!file:%QML_PATH%\=!"
set "target=%QML_DIR%\%rel_path%"
for %%I in ("!file!") do (
set "file_dir=%%~dpI"
)
set "rel_dir_path=!file_dir:%QML_PATH%\=!"
set "target_dir=%QML_DIR%\%rel_dir_path%"
:: Determine whether it is an executable binary file and handle it accordingly
if "%file:~-4%"==".dll" (
call :push_args -c !file! !target_dir!
) else if "%file:~-4%"==".exe" (
call :push_args -c !file! !target_dir!
) else (
if not exist "%target%" (
mkdir "%target_dir%" >nul 2>&1
)
copy /Y "%file%" "%target%" >nul 2>&1
)
exit /b
:: ----------------------------------------------------------------------------------
:: ----------------------------------------------------------------------------------
:: Show usage
:usage
echo Usage: %~n0 -i ^<dir^> -m ^<path^>
echo --plugindir ^<plugin_dir^> --libdir ^<lib_dir^> --qmldir ^<qml_dir^>
echo [--qmake ^<qmake_path^>] [--extra ^<extra_path^>]...
echo [--qml ^<qml_module^>]... [--plugin ^<plugin^>]... [--copy ^<src^> ^<dest^>]...
echo [--linkdirs-file ^<file^>]... [-L ^<path^>]...
echo [-f] [-s] [-V] [-h]
exit /b
:: ----------------------------------------------------------------------------------

View File

@@ -0,0 +1,23 @@
if(NOT DEFINED input)
message(FATAL_ERROR "input not defined")
endif()
if(NOT DEFINED output)
message(FATAL_ERROR "output not defined")
endif()
if(NOT DEFINED name)
message(FATAL_ERROR "name not defined")
endif()
get_filename_component(_output_dir ${output} DIRECTORY)
if(NOT EXISTS ${_output_dir})
file(MAKE_DIRECTORY ${_output_dir})
endif()
file(READ ${input} _file_content HEX)
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " _hex_content ${_file_content})
file(WRITE ${output}
"unsigned char ${name}[] = {${_hex_content}};\n"
"unsigned int ${name}_len = sizeof(${name});\n"
)

View File

@@ -0,0 +1,8 @@
Set oWS = WScript.CreateObject("WScript.Shell")
sLinkFile = "@SHORTCUT_PATH@"
Set oLink = oWS.CreateShortcut(sLinkFile)
oLink.TargetPath = "@SHORTCUT_TARGET_PATH@"
oLink.WorkingDirectory = "@SHORTCUT_WORKING_DIRECOTRY@"
oLink.Description = "@SHORTCUT_DESCRIPTION@"
oLink.IconLocation = "@SHORTCUT_ICON_LOCATION@"
oLink.Save

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<description>@MANIFEST_DESCRIPTION@</description>
<assemblyIdentity
version="@MANIFEST_VERSION@"
name="@MANIFEST_IDENTIFIER@"
type="win32"
/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
@MANIFEST_PRIVILEGES@
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- XAML Island won't work without the maxversiontested list -->
<!-- Windows 10 Version 1809 (October 2018 Update) -->
<maxversiontested Id="10.0.17763.0"/>
<!-- Windows 10 Version 1903 (May 2019 Update) -->
<maxversiontested Id="10.0.18362.0"/>
<!-- Windows 10 Version 1909 (November 2019 Update) -->
<maxversiontested Id="10.0.18363.0"/>
<!-- Windows 10 Version 2004 (May 2020 Update) -->
<maxversiontested Id="10.0.19041.0"/>
<!-- Windows 10 Version 20H2 (October 2020 Update) -->
<maxversiontested Id="10.0.19042.0"/>
<!-- Windows 10 Version 21H1 (May 2021 Update) -->
<maxversiontested Id="10.0.19043.0"/>
<!-- Windows 10 Version 21H2 (November 2021 Update) -->
<maxversiontested Id="10.0.19044.0"/>
<!-- Windows 10 Version 22H2 -->
<maxversiontested Id="10.0.19045.0"/>
<!-- Windows 11 Version 21H2 -->
<maxversiontested Id="10.0.22000.0"/>
<!-- Windows 11 Version 22H2 -->
<maxversiontested Id="10.0.22621.0"/>
<!-- Windows 11 Version 23H2 -->
<maxversiontested Id="10.0.22631.0"/>
<!-- Windows 11 Version 24H2 -->
<maxversiontested Id="10.0.26100.0"/>
<!-- Windows 11 Version 25H2 -->
<maxversiontested Id="10.0.26200.0"/>
<!-- Windows Vista and Windows Server 2008 -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<!-- Windows 7 and Windows Server 2008 R2 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!-- Windows 8 and Windows Server 2012 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 8.1 and Windows Server 2012 R2 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
<printerDriverIsolation xmlns="http://schemas.microsoft.com/SMI/2011/WindowsSettings">True</printerDriverIsolation>
<disableWindowFiltering xmlns="http://schemas.microsoft.com/SMI/2011/WindowsSettings">True</disableWindowFiltering>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">True</longPathAware>
<heapType xmlns="http://schemas.microsoft.com/SMI/2020/WindowsSettings">SegmentHeap</heapType>
@MANIFEST_UTF8@
</windowsSettings>
</application>
</assembly>

View File

@@ -0,0 +1,29 @@
#include <windows.h>
#ifndef VS_VERSION_INFO
#define VS_VERSION_INFO 1
#endif
@RC_ICON_COMMENT@ IDI_ICON1 ICON DISCARDABLE "@RC_ICON_PATH@"
VS_VERSION_INFO VERSIONINFO
FILEVERSION @RC_VERSION@
PRODUCTVERSION @RC_VERSION@
{
BLOCK "StringFileInfo"
{
// U.S. English - Windows, Multilingual
BLOCK "040904E4"
{
VALUE "FileDescription", "@RC_DESCRIPTION@"
VALUE "FileVersion", "@RC_VERSION_STRING@"
VALUE "ProductName", "@RC_APPLICATION_NAME@"
VALUE "ProductVersion", "@RC_VERSION_STRING@"
VALUE "LegalCopyright", "@RC_COPYRIGHT@"
}
}
BLOCK "VarFileInfo"
{
VALUE "Translation", 0x409, 1252 // 1252 = 0x04E4
}
}

View File

@@ -0,0 +1,38 @@
#include <windows.h>
@RC_ICONS@
VS_VERSION_INFO VERSIONINFO
FILEVERSION @RC_VERSION@
PRODUCTVERSION @RC_VERSION@
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE @RC_FILE_TYPE@
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "Comments", "@RC_COMMENTS@"
VALUE "CompanyName", "@RC_COMPANY@"
VALUE "FileDescription", "@RC_DESCRIPTION@"
VALUE "FileVersion", "@RC_VERSION_STRING@"
VALUE "InternalName", "@RC_INTERNAL_NAME@"
VALUE "LegalCopyright", "@RC_COPYRIGHT@"
VALUE "LegalTrademarks", "@RC_TRADEMARK@"
VALUE "OriginalFilename", "@RC_ORIGINAL_FILENAME@"
VALUE "ProductName", "@RC_APPLICATION_NAME@"
VALUE "ProductVersion", "@RC_VERSION_STRING@"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

View File

@@ -0,0 +1,56 @@
# Core Command
`qmcorecmd` is an executable written by C++. It is designed to provide a set of solutions that are fixed in form and that CMake scripts cannot implement due to insufficient features or inefficiencies.
`qmcorecmd` uses `syscmdline` to parse command line arguments and have multiple subcommands.
You can refer to the help message of each command to acquire more usage information, since this document provides only a brief introduction.
## Filesystem Commands
### Copy
Copies a file or folder. If the given folder path ends with the separator, copies the contents of the folder.
+ Read timestamp to determine whether to overwrite an existing object file.
+ Support ignoring specific files specified by regular expressions.
### Rmdir
Recursively deletes all empty directories.
Since CMake copies all directories unconditionally when executing the `install(DIRECTORY ...)` command, even if the directory is empty. This command can be used to fix this unreasonable phenomenon.
### Touch
Since Microsoft Windows does not provide a native command for handling file timestamps, this command is designed to copy or update a file's time stamp.
Only some of the functionality of Unix `touch` command is implemented.
## Buildsystem Commands
### Configure
Generates configuration header files.
It allows users to add macro definitions via the `-D` option, which are directly written into the generated header file. Additionally, users can specify a warning file with the `-w` option, the contents of which are added as comments at the top of the header file.
A key feature of this command is its ability to compute the SHA-256 hash of the definitions and embed it in the generated file, facilitating the tracking of configuration changes.
### Incsync
Reorganizes header files in include directories.
It supports defining path patterns and corresponding subdirectories through the `-i` option, reorganizing header files by writing relative reference statement or copying directly. The `-e` option can be used to exclude files matching certain patterns.
While processing header files, `incsync` considers the last modified time to determine whether to copy or update files.
### Deploy
Resolves and deploys a binary's shared library dependencies.
It analyzes dependencies of specified files and copies these dependencies to a designated output directory. Moreover, it handles details like fixing RPATH or dealing with Windows-specific library search paths.
The `-s` option is provided to ignore standard C/C++ runtime and system libraries, reducing unnecessary dependency copying.
All shared libraries that are deployed must have different file names.

View File

@@ -0,0 +1,12 @@
#ifndef QMSETUP_GLOBAL_H
#define QMSETUP_GLOBAL_H
#ifdef _WIN32
# define QMSETUP_DECL_EXPORT __declspec(dllexport)
# define QMSETUP_DECL_IMPORT __declspec(dllimport)
#else
# define QMSETUP_DECL_EXPORT __attribute__((visibility("default")))
# define QMSETUP_DECL_IMPORT
#endif
#endif // QMSETUP_GLOBAL_H

View File

@@ -0,0 +1,79 @@
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
if(QMSETUP_STATIC_RUNTIME)
# To avoid a command execution that can't find the library, we choose to link compiler runtime statically
if(MINGW)
add_link_options(-static -static-libgcc -static-libstdc++)
elseif(MSVC)
add_compile_options(/MT)
endif()
endif()
# Get syscmdline
find_package(syscmdline QUIET)
if(NOT TARGET syscmdline::syscmdline)
set(SYSCMDLINE_INSTALL off)
add_subdirectory(syscmdline)
endif()
# Add subdirectories
add_subdirectory(corecmd)
# Add headers
set(_corelib_name library)
add_library(${_corelib_name} INTERFACE)
target_include_directories(${_corelib_name} INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${QMSETUP_INSTALL_NAME}>"
)
install(TARGETS ${_corelib_name}
EXPORT ${QMSETUP_INSTALL_NAME}Targets
)
if(TRUE)
# Add install target
set(_install_dir ${CMAKE_INSTALL_LIBDIR}/cmake/${QMSETUP_INSTALL_NAME})
# Add version file
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${QMSETUP_INSTALL_NAME}ConfigVersion.cmake"
VERSION ${QMSETUP_VERSION}
COMPATIBILITY AnyNewerVersion
)
# Add configuration file
configure_package_config_file(
${CMAKE_CURRENT_LIST_DIR}/${QMSETUP_INSTALL_NAME}Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/${QMSETUP_INSTALL_NAME}Config.cmake"
INSTALL_DESTINATION ${_install_dir}
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
# Install cmake files
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/${QMSETUP_INSTALL_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${QMSETUP_INSTALL_NAME}ConfigVersion.cmake"
DESTINATION ${_install_dir}
)
# Install cmake targets files
install(EXPORT ${QMSETUP_INSTALL_NAME}Targets
FILE "${QMSETUP_INSTALL_NAME}Targets.cmake"
NAMESPACE qmsetup::
DESTINATION ${_install_dir}
)
# Install headers
install(DIRECTORY ../include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
# Install cmake modules
install(DIRECTORY ../cmake/
DESTINATION ${_install_dir}/cmake
)
endif()

View File

@@ -0,0 +1,89 @@
project(qmcorecmd VERSION ${QMSETUP_VERSION})
string(TIMESTAMP _year "%Y")
set(RC_COPYRIGHT "Copyright 2023-${_year} Stdware Collections")
set(RC_DESCRIPTION "QMSetup Core Utility Command")
set(_src
main.cpp
sha-256.h
sha-256.cpp
utils.h
utils.cpp
)
if(WIN32)
list(APPEND _src utils_win.cpp)
else()
list(APPEND _src utils_unix.cpp)
endif()
add_executable(${PROJECT_NAME} ${_src})
target_link_libraries(${PROJECT_NAME} PRIVATE syscmdline::syscmdline)
# Add features
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20 cxx_std_17)
set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_EXTENSIONS OFF
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
)
# Compat with gcc 8
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9")
target_link_libraries(${PROJECT_NAME} PRIVATE stdc++fs)
endif()
if(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE shlwapi)
target_compile_definitions(${PROJECT_NAME} PRIVATE _CRT_SECURE_NO_WARNINGS)
endif()
if(WIN32)
qm_add_win_rc(${PROJECT_NAME}
COPYRIGHT ${RC_COPYRIGHT}
DESCRIPTION ${RC_DESCRIPTION}
)
# Make sure Windows UAC does not believe it is an installer
qm_add_win_manifest(${PROJECT_NAME}
DESCRIPTION ${RC_DESCRIPTION}
)
endif()
target_compile_definitions(${PROJECT_NAME} PRIVATE TOOL_DESC="${RC_DESCRIPTION}, Version ${QMSETUP_VERSION}")
target_compile_definitions(${PROJECT_NAME} PRIVATE TOOL_COPYRIGHT="${RC_COPYRIGHT}, checkout https://github.com/stdware/qmsetup")
target_compile_definitions(${PROJECT_NAME} PRIVATE TOOL_VERSION="${QMSETUP_VERSION}")
set_target_properties(${PROJECT_NAME} PROPERTIES EXPORT_NAME corecmd)
# Vcpkg requires the binary to be installed in "tools/${PORT}" directory
if(QMSETUP_VCPKG_TOOLS_HINT)
set(_tools_dir tools/${QMSETUP_INSTALL_NAME})
else()
set(_tools_dir ${CMAKE_INSTALL_BINDIR})
endif()
install(TARGETS ${PROJECT_NAME}
EXPORT ${QMSETUP_INSTALL_NAME}Targets
RUNTIME DESTINATION "${_tools_dir}" OPTIONAL
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL
)
# Check patchelf
if (NOT WIN32 AND NOT APPLE)
execute_process(
COMMAND patchelf --version
OUTPUT_VARIABLE _patchelf_version_output
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT "${_patchelf_version_output}" MATCHES "patchelf")
message(WARNING "Patchelf not found, the deploy feature won't work.")
else()
string(REGEX REPLACE "patchelf (.+)" "\\1" _patchelf_version ${_patchelf_version_output})
message(STATUS "Found patchelf, version ${_patchelf_version}")
endif()
endif()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,210 @@
#include "sha-256.h"
#include <cstring>
#include <string>
static constexpr const auto CHUNK_SIZE = 64;
static constexpr const auto TOTAL_LEN_LEN = 8;
/*
* ABOUT bool: this file does not use bool in order to be as pre-C99 compatible as possible.
*/
/*
* Comments from pseudo-code at https://en.wikipedia.org/wiki/SHA-2 are reproduced here.
* When useful for clarification, portions of the pseudo-code are reproduced here too.
*/
/*
* Initialize array of round constants:
* (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311):
*/
static constexpr const uint32_t k[] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
struct buffer_state {
const uint8_t * p;
size_t len;
size_t total_len;
int single_one_delivered; /* bool */
int total_len_delivered; /* bool */
};
static inline constexpr uint32_t right_rot(uint32_t value, unsigned int count)
{
/*
* Defined behaviour in standard C for all count where 0 < count < 32,
* which is what we need here.
*/
return value >> count | value << (32 - count);
}
static void init_buf_state(struct buffer_state * state, const void * input, size_t len)
{
state->p = static_cast<const uint8_t*>(input);
state->len = len;
state->total_len = len;
state->single_one_delivered = 0;
state->total_len_delivered = 0;
}
/* Return value: bool */
static int calc_chunk(uint8_t chunk[CHUNK_SIZE], struct buffer_state * state)
{
size_t space_in_chunk;
if (state->total_len_delivered) {
return 0;
}
if (state->len >= CHUNK_SIZE) {
std::memcpy(chunk, state->p, CHUNK_SIZE);
state->p += CHUNK_SIZE;
state->len -= CHUNK_SIZE;
return 1;
}
std::memcpy(chunk, state->p, state->len);
chunk += state->len;
space_in_chunk = CHUNK_SIZE - state->len;
state->p += state->len;
state->len = 0;
/* If we are here, space_in_chunk is one at minimum. */
if (!state->single_one_delivered) {
*chunk++ = 0x80;
space_in_chunk -= 1;
state->single_one_delivered = 1;
}
/*
* Now:
* - either there is enough space left for the total length, and we can conclude,
* - or there is too little space left, and we have to pad the rest of this chunk with zeroes.
* In the latter case, we will conclude at the next invokation of this function.
*/
if (space_in_chunk >= TOTAL_LEN_LEN) {
const size_t left = space_in_chunk - TOTAL_LEN_LEN;
size_t len = state->total_len;
int i;
std::memset(chunk, 0x00, left);
chunk += left;
/* Storing of len * 8 as a big endian 64-bit without overflow. */
chunk[7] = (uint8_t) (len << 3);
len >>= 5;
for (i = 6; i >= 0; i--) {
chunk[i] = (uint8_t) len;
len >>= 8;
}
state->total_len_delivered = 1;
} else {
std::memset(chunk, 0x00, space_in_chunk);
}
return 1;
}
/*
* Limitations:
* - Since input is a pointer in RAM, the data to hash should be in RAM, which could be a problem
* for large data sizes.
* - SHA algorithms theoretically operate on bit strings. However, this implementation has no support
* for bit string lengths that are not multiples of eight, and it really operates on arrays of bytes.
* In particular, the len parameter is a number of bytes.
*/
void calc_sha_256(uint8_t hash[32], const void * input, size_t len)
{
/*
* Note 1: All integers (expect indexes) are 32-bit unsigned integers and addition is calculated modulo 2^32.
* Note 2: For each round, there is one round constant k[i] and one entry in the message schedule array w[i], 0 = i = 63
* Note 3: The compression function uses 8 working variables, a through h
* Note 4: Big-endian convention is used when expressing the constants in this pseudocode,
* and when parsing message block data from bytes to words, for example,
* the first word of the input message "abc" after padding is 0x61626380
*/
/*
* Initialize hash values:
* (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19):
*/
uint32_t h[] = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };
int i, j;
/* 512-bit chunks is what we will operate on. */
uint8_t chunk[64];
struct buffer_state state;
init_buf_state(&state, input, len);
while (calc_chunk(chunk, &state)) {
uint32_t ah[8];
/*
* create a 64-entry message schedule array w[0..63] of 32-bit words
* (The initial values in w[0..63] don't matter, so many implementations zero them here)
* copy chunk into first 16 words w[0..15] of the message schedule array
*/
uint32_t w[64];
const uint8_t *p = chunk;
std::memset(w, 0x00, sizeof w);
for (i = 0; i < 16; i++) {
w[i] = (uint32_t) p[0] << 24 | (uint32_t) p[1] << 16 |
(uint32_t) p[2] << 8 | (uint32_t) p[3];
p += 4;
}
/* Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array: */
for (i = 16; i < 64; i++) {
const uint32_t s0 = right_rot(w[i - 15], 7) ^ right_rot(w[i - 15], 18) ^ (w[i - 15] >> 3);
const uint32_t s1 = right_rot(w[i - 2], 17) ^ right_rot(w[i - 2], 19) ^ (w[i - 2] >> 10);
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
}
/* Initialize working variables to current hash value: */
for (i = 0; i < 8; i++)
ah[i] = h[i];
/* Compression function main loop: */
for (i = 0; i < 64; i++) {
const uint32_t s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25);
const uint32_t ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]);
const uint32_t temp1 = ah[7] + s1 + ch + k[i] + w[i];
const uint32_t s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22);
const uint32_t maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]);
const uint32_t temp2 = s0 + maj;
ah[7] = ah[6];
ah[6] = ah[5];
ah[5] = ah[4];
ah[4] = ah[3] + temp1;
ah[3] = ah[2];
ah[2] = ah[1];
ah[1] = ah[0];
ah[0] = temp1 + temp2;
}
/* Add the compressed chunk to the current hash value: */
for (i = 0; i < 8; i++)
h[i] += ah[i];
}
/* Produce the final hash value (big-endian): */
for (i = 0, j = 0; i < 8; i++)
{
hash[j++] = (uint8_t) (h[i] >> 24);
hash[j++] = (uint8_t) (h[i] >> 16);
hash[j++] = (uint8_t) (h[i] >> 8);
hash[j++] = (uint8_t) h[i];
}
}

View File

@@ -0,0 +1,19 @@
#ifndef SHA_256_H
#define SHA_256_H
// https://github.com/notepad-plus-plus/notepad-plus-plus/blob/master/PowerEditor/src/MISC/sha2
#include <cstddef>
#include <cstdint>
#ifdef __cplusplus
extern "C" {
#endif
void calc_sha_256(uint8_t hash[32], const void *input, size_t len);
#ifdef __cplusplus
}
#endif
#endif // SHA_256_H

View File

@@ -0,0 +1,31 @@
#include "utils.h"
namespace fs = std::filesystem;
#ifdef _WIN32
# define _TSTR(X) L##X
# define tstrcmp wcscmp
#else
# define _TSTR(X) X
# define tstrcmp strcmp
#endif
namespace Utils {
std::filesystem::path cleanPath(const std::filesystem::path &path) {
fs::path result;
for (const auto &part : path) {
if (part == _TSTR("..")) {
if (!result.empty() && result.filename() != _TSTR("..")) {
result = result.parent_path();
} else {
result /= part;
}
} else if (part != _TSTR(".")) {
result /= part;
}
}
return result;
}
}

View File

@@ -0,0 +1,158 @@
#ifndef UTILS_H
#define UTILS_H
#include <string>
#include <vector>
#include <filesystem>
#include <chrono>
#include <algorithm>
#include <cctype>
#include <vector>
namespace Utils {
// Filesystem Utils
struct FileTime {
std::chrono::system_clock::time_point accessTime;
std::chrono::system_clock::time_point modifyTime;
std::chrono::system_clock::time_point statusChangeTime; // Creation time on Windows
};
FileTime fileTime(const std::filesystem::path &path);
void setFileTime(const std::filesystem::path &path, const FileTime &times);
inline void syncFileTime(const std::filesystem::path &dest, const std::filesystem::path &src) {
setFileTime(dest, fileTime(src));
}
std::vector<std::filesystem::path> getPathsFromEnv();
std::filesystem::path cleanPath(const std::filesystem::path &path);
// String Utils
template <class T>
std::vector<std::basic_string<T>> split(const std::basic_string<T> &s,
const std::basic_string<T> &delimiter) {
std::vector<std::basic_string<T>> tokens;
typename std::basic_string<T>::size_type start = 0;
typename std::basic_string<T>::size_type end = s.find(delimiter);
while (end != std::basic_string<T>::npos) {
tokens.push_back(s.substr(start, end - start));
start = end + delimiter.size();
end = s.find(delimiter, start);
}
tokens.push_back(s.substr(start));
return tokens;
}
template <class T>
std::basic_string<T> trim(const std::basic_string<T> &str) {
auto start = str.begin();
while (start != str.end() && std::isspace(*start)) {
start++;
}
auto end = str.end();
do {
end--;
} while (std::distance(start, end) > 0 && std::isspace(*end));
return {start, end + 1};
}
template <class T>
void replaceString(std::basic_string<T> &s, const std::basic_string<T> &pattern,
const std::basic_string<T> &text) {
size_t idx = 0;
while ((idx = s.find(pattern, idx)) != std::basic_string<T>::npos) {
s.replace(idx, pattern.size(), text);
idx += text.size();
}
};
template <class T>
std::basic_string<T> join(const std::vector<std::basic_string<T>> &v,
const std::basic_string<T> &delimiter) {
if (v.empty())
return {};
std::basic_string<T> res;
for (int i = 0; i < v.size() - 1; ++i) {
res.append(v[i]);
res.append(delimiter);
}
res.append(v.back());
return res;
}
template <class T>
std::basic_string<T> toLower(std::basic_string<T> s) {
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
return s;
}
template <class T>
std::basic_string<T> toUpper(std::basic_string<T> s) {
std::transform(s.begin(), s.end(), s.begin(), ::toupper);
return s;
}
inline bool starts_with(const std::string_view &s, const std::string_view &prefix) {
#if __cplusplus >= 202002L
return s.starts_with(prefix);
#else
return s.size() >= prefix.size() && s.substr(0, prefix.size()) == prefix;
#endif
}
inline bool starts_with(const std::wstring_view &s, const std::wstring_view &prefix) {
#if __cplusplus >= 202002L
return s.starts_with(prefix);
#else
return s.size() >= prefix.size() && s.substr(0, prefix.size()) == prefix;
#endif
}
template <class Container, class T>
inline bool contains(const Container &container, const T &key) {
#if __cplusplus >= 202002L
return container.contains(key);
#else
return container.count(key) != 0;
#endif
}
// OS Utils
#ifdef _WIN32
std::wstring executeCommand(const std::wstring &command, const std::vector<std::wstring> &args);
std::vector<std::wstring>
resolveWinBinaryDependencies(const std::filesystem::path &path,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed);
#else
std::string executeCommand(const std::string &command, const std::vector<std::string> &args);
void setFileRPaths(const std::string &file, const std::vector<std::string> &paths);
std::vector<std::string>
resolveUnixBinaryDependencies(const std::filesystem::path &path,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed = nullptr);
#endif
#ifdef __APPLE__
std::vector<std::string> getMacAbsoluteDependencies(const std::string &file);
void replaceMacFileDependencies(
const std::string &file, const std::vector<std::pair<std::string, std::string>> &depPairs);
#elif defined(__linux__)
std::string getInterpreter(const std::string &file);
bool setFileInterpreter(const std::string &file, const std::string &interpreter);
#endif
}
#endif // UTILS_H

View File

@@ -0,0 +1,500 @@
#include "utils.h"
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <utime.h>
#include <cstring>
#include <filesystem>
#include <regex>
#include <set>
#include <sstream>
namespace fs = std::filesystem;
namespace Utils {
std::string executeCommand(const std::string &command, const std::vector<std::string> &args) {
// printf("Executing command: %s", command.data());
// for (const auto &arg : args) {
// printf(" %s", arg.data());
// }
// printf("\n");
static const auto &dupStr = [](char *&dest, const char *src, size_t size) {
dest = new char[size + 1];
memcpy(dest, src, size);
dest[size] = '\0';
};
enum PipeNum {
PN_Read,
PN_Write,
};
// Create pipe
int pipefd[2];
if (pipe(pipefd) == -1) {
throw std::runtime_error("failed to call \"pipe\"");
}
pid_t pid = fork();
if (pid < 0) {
// Close pipes right away
close(pipefd[PN_Read]);
close(pipefd[PN_Write]);
throw std::runtime_error("failed to call \"fork\"");
}
if (pid == 0) {
// --------
// Child process
// --------
// Close read pipe
close(pipefd[PN_Read]);
// Redirect "stdout" and "stderr" to write pipe
dup2(pipefd[PN_Write], STDOUT_FILENO);
dup2(pipefd[PN_Write], STDERR_FILENO);
// Prepare arguments
auto argv = new char *[args.size() + 2]; // +2 for command and nullptr
dupStr(argv[0], command.data(), command.size());
for (size_t i = 0; i < args.size(); ++i) {
dupStr(argv[i + 1], args[i].data(), args[i].size());
}
argv[args.size() + 1] = nullptr;
// Call "exec"
execvp(argv[0], argv);
// If the control flow reaches here, there must be a mistake
// Clean allocated memory
for (size_t i = 0; i < args.size() + 2; ++i) {
delete[] argv[i];
}
delete[] argv;
// Fail
printf("exec \"%s\" failed: %s\n", command.data(), strerror(errno));
// No need to close write pipe, let the System handle everything
// Simply exit
exit(EXIT_FAILURE);
}
// --------
// Parent process
// --------
// Close write pipe
close(pipefd[PN_Write]);
// Read child process output
char buffer[256];
std::string output;
ssize_t bytesRead;
while ((bytesRead = read(pipefd[0], buffer, sizeof(buffer) - 1)) > 0) {
buffer[bytesRead] = '\0';
output += buffer;
}
// Close read pipe
close(pipefd[PN_Read]);
// Wait for child process to terminate
int status;
waitpid(pid, &status, 0);
// Check exit status
if (WIFEXITED(status)) {
auto exitCode = WEXITSTATUS(status);
if (exitCode == 0)
return output;
// Throw error
throw std::runtime_error(trim(output));
}
if (WIFSIGNALED(status)) {
auto sig = WTERMSIG(status);
throw std::runtime_error("command \"" + command + "\" was terminated by signal " +
std::to_string(sig));
}
throw std::runtime_error("command \"" + command + "\" terminated abnormally with status " +
std::to_string(status));
}
FileTime fileTime(const fs::path &path) {
struct stat sb;
if (stat(path.c_str(), &sb) == -1) {
throw std::runtime_error("failed to get file time: \"" + path.string() + "\"");
}
FileTime times;
times.accessTime = std::chrono::system_clock::from_time_t(sb.st_atime);
times.modifyTime = std::chrono::system_clock::from_time_t(sb.st_mtime);
times.statusChangeTime = std::chrono::system_clock::from_time_t(sb.st_ctime);
return times;
}
void setFileTime(const fs::path &path, const FileTime &times) {
struct utimbuf new_times;
new_times.actime = std::chrono::system_clock::to_time_t(times.accessTime);
new_times.modtime = std::chrono::system_clock::to_time_t(times.modifyTime);
if (utime(path.c_str(), &new_times) != 0) {
throw std::runtime_error("failed to set file time: \"" + path.string() + "\"");
}
}
std::vector<fs::path> getPathsFromEnv() {
const char *pathEnv = std::getenv("PATH");
if (pathEnv == nullptr) {
return {};
}
std::string pathStr = pathEnv;
std::stringstream ss(pathStr);
std::string item;
std::vector<fs::path> paths;
while (std::getline(ss, item, ':')) {
if (!item.empty()) {
paths.push_back(fs::path(item));
}
}
return paths;
}
#ifdef __APPLE__
// Mac
// Use `otool` and `install_name_tool`
static std::vector<std::string> readMacBinaryRPaths(const std::string &path) {
std::vector<std::string> rpaths;
std::string output;
try {
output = executeCommand("otool", {"-l", path});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to get RPATHs: " + std::string(e.what()));
}
static const std::regex rpathRegex(R"(\s*path\s+(.*)\s+\(offset.*)");
std::istringstream iss(output);
std::string line;
std::smatch match;
while (std::getline(iss, line)) {
if (line.find("cmd LC_RPATH") != std::string::npos) {
// skip 2 lines
std::getline(iss, line);
std::getline(iss, line);
if (std::regex_match(line, match, rpathRegex) && match.size() >= 2) {
rpaths.emplace_back(match[1].str());
}
}
}
return rpaths;
}
static std::vector<std::string> readMacBinaryDependencies(const std::string &path) {
std::vector<std::string> dependencies;
std::string output;
// Get dependencies
try {
output = executeCommand("otool", {"-L", path});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to get dependencies: " + std::string(e.what()));
}
static const std::regex depRegex(
R"(^\t(.+) \(compatibility version (\d+\.\d+\.\d+), current version (\d+\.\d+\.\d+)(, weak)?\)$)");
std::istringstream iss(output);
std::string line;
std::smatch match;
// skip first line
std::getline(iss, line);
const std::string &loaderPath = path;
while (std::getline(iss, line)) {
if (std::regex_search(line, match, depRegex) && match.size() >= 2) {
std::string dep = match[1].str();
dependencies.emplace_back(dep);
}
}
return dependencies;
}
std::vector<std::string>
resolveUnixBinaryDependencies(const std::filesystem::path &path,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed) {
auto rpaths = readMacBinaryRPaths(path);
for (const auto &item : searchingPaths) {
rpaths.push_back(item);
}
auto dependencies = readMacBinaryDependencies(path);
const std::string &loaderPath = fs::canonical(path).parent_path();
std::vector<std::string> res;
for (auto dep : std::as_const(dependencies)) {
// Replace @executable_path and @loader_path
replaceString(dep, std::string("@executable_path"), loaderPath);
replaceString(dep, std::string("@loader_path"), loaderPath);
// Find dependency
std::string target = dep;
if (dep.find("@rpath") != std::string::npos) {
for (auto rpath : rpaths) {
// Replace again
replaceString(rpath, std::string("@executable_path"), loaderPath);
replaceString(rpath, std::string("@loader_path"), loaderPath);
std::string fullPath = dep;
replaceString(fullPath, std::string("@rpath"), rpath);
if (fs::exists(fullPath)) {
target = fullPath;
break;
}
}
}
target = cleanPath(target);
if (fs::exists(target)) {
if (fs::canonical(target).filename() == fs::canonical(path).filename())
continue;
res.push_back(target);
} else if (unparsed) {
unparsed->push_back(target);
}
}
return res;
}
void setFileRPaths(const std::string &file, const std::vector<std::string> &paths) {
// Remove rpaths
do {
auto rpaths = readMacBinaryRPaths(file);
if (rpaths.empty())
break;
std::vector<std::string> args;
args.reserve(rpaths.size() * 2 + 1);
for (const auto &rpath : std::as_const(rpaths)) {
args.push_back("-delete_rpath");
args.push_back(rpath);
}
args.push_back(file);
try {
std::ignore = executeCommand("install_name_tool", args);
} catch (const std::exception &e) {
throw std::runtime_error("Failed to remove rpaths: " + std::string(e.what()));
}
} while (false);
// Add rpaths
if (!paths.empty()) {
std::set<std::string> visited;
std::vector<std::string> args;
args.reserve(paths.size() * 2 + 1);
for (const auto &rpath : std::as_const(paths)) {
if (Utils::contains(visited, rpath))
continue;
visited.insert(rpath);
args.push_back("-add_rpath");
args.push_back(rpath);
}
args.push_back(file);
try {
std::ignore = executeCommand("install_name_tool", args);
} catch (const std::exception &e) {
throw std::runtime_error("Failed to add rpaths: " + std::string(e.what()));
}
}
// Remove code signature if it exists
try {
std::ignore = executeCommand("codesign", {"--remove-signature", file});
std::ignore = executeCommand("codesign", {"-s", "-", file});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to resign: " + std::string(e.what()));
}
}
std::vector<std::string> getMacAbsoluteDependencies(const std::string &file) {
auto deps = readMacBinaryDependencies(file);
std::vector<std::string> res;
for (const auto &dep : std::as_const(deps)) {
if (fs::path(dep).is_absolute()) {
res.push_back(dep);
}
}
return res;
}
void replaceMacFileDependencies(
const std::string &file, const std::vector<std::pair<std::string, std::string>> &depPairs) {
std::string output;
std::vector<std::string> args;
args.reserve(depPairs.size() * 3 + 1);
std::string id;
for (const auto &pair : depPairs) {
if (fs::exists(pair.first) &&
fs::canonical(pair.first).filename() == fs::canonical(file).filename()) {
id = pair.second;
continue;
}
args.push_back("-change");
args.push_back(pair.first);
args.push_back(pair.second);
}
if (!id.empty()) {
args.push_back("-id");
args.push_back(id);
}
args.push_back(file);
try {
output = executeCommand("install_name_tool", args);
} catch (const std::exception &e) {
throw std::runtime_error("Failed to replace dependency: " + std::string(e.what()));
}
}
#else
// Linux
// Use `ldd` and `patchelf`
static std::vector<std::string>
readLddOutput(const std::string &fileName,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed) {
std::string output;
try {
output = executeCommand("ldd", {fileName});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to get dependencies: " + std::string(e.what()));
}
std::istringstream iss(output);
std::string line;
static const std::regex regexp("^\\s*.+ => (.+) \\(.*");
static const std::regex regexp2("^\\s*(.+) => not found");
std::vector<std::string> dependencies;
while (std::getline(iss, line)) {
std::smatch match;
if (std::regex_match(line, match, regexp) && match.size() >= 2) {
dependencies.push_back(cleanPath(match[1].str()));
} else if (std::regex_match(line, match, regexp2) && match.size() >= 2) {
// Search in search paths
fs::path target;
for (const auto &item : searchingPaths) {
auto fullPath = item / match[1].str();
if (fs::exists(fullPath)) {
target = fullPath;
break;
}
}
if (!target.empty()) {
dependencies.push_back(target);
} else {
unparsed->push_back(cleanPath(match[1].str()));
}
}
}
return dependencies;
}
std::vector<std::string>
resolveUnixBinaryDependencies(const std::filesystem::path &path,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed) {
return readLddOutput(path, searchingPaths, unparsed);
}
void setFileRPaths(const std::string &file, const std::vector<std::string> &paths) {
if (paths.empty()) {
try {
std::ignore = executeCommand("patchelf", {
"--remove-rpath",
file,
});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to remove rpaths: " + std::string(e.what()));
}
return;
}
try {
std::ignore = executeCommand("patchelf", {
"--set-rpath",
join(paths, std::string(":")),
file,
});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to replace rpaths: " + std::string(e.what()));
}
}
#endif
#ifdef __linux__
static inline bool errorIsInterpNotFound(const std::string &what) {
return what.find("cannot find section '.interp'") != std::string::npos;
};
std::string getInterpreter(const std::string &file) {
std::string output;
try {
output = executeCommand("patchelf", {
"--print-interpreter",
file,
});
} catch (const std::exception &e) {
if (errorIsInterpNotFound(e.what()))
return {};
throw std::runtime_error("Failed to get interpreter: " + std::string(e.what()));
}
output = trim(output);
replaceString(output, std::string("$ORIGIN"), fs::canonical(file).parent_path().string());
return output;
}
bool setFileInterpreter(const std::string &file, const std::string &interpreter) {
try {
std::ignore = executeCommand("patchelf", {
"--set-interpreter",
interpreter,
file,
});
} catch (const std::exception &e) {
if (errorIsInterpNotFound(e.what()))
return false;
throw std::runtime_error("Failed to set interpreter: " + std::string(e.what()));
}
return true;
}
#endif
}

View File

@@ -0,0 +1,500 @@
#include "utils.h"
#include <shlwapi.h>
#include <delayimp.h>
#include <windows.h>
#include <algorithm>
#include <sstream>
#include <filesystem>
#include <stdexcept>
#include <utility>
#include <syscmdline/system.h>
namespace SCL = SysCmdLine;
namespace fs = std::filesystem;
namespace Utils {
static constexpr const DWORD g_EnglishLangId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
static std::wstring winErrorMessage(uint32_t error, bool nativeLanguage = true) {
std::wstring rc;
wchar_t *lpMsgBuf;
DWORD len = ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, error, nativeLanguage ? 0 : g_EnglishLangId,
reinterpret_cast<LPWSTR>(&lpMsgBuf), 0, NULL);
if (len) {
// Remove tail line breaks
if (lpMsgBuf[len - 1] == L'\n') {
lpMsgBuf[len - 1] = L'\0';
if (len > 2 && lpMsgBuf[len - 2] == L'\r') {
lpMsgBuf[len - 2] = L'\0';
}
}
rc = std::wstring(lpMsgBuf, int(len));
::LocalFree(lpMsgBuf);
} else {
rc += L"unknown error";
}
return rc;
}
static std::wstring quoteAndEscapeArg(const std::wstring &arg) {
if (arg.find_first_of(L" \t\"") == std::wstring::npos) {
// No need to quote or escape
return arg;
}
std::wstring escapedArg = L"\"";
for (auto ch : arg) {
if (ch == L'"') {
// Escape double quotes
escapedArg += L"\"\"";
} else {
escapedArg += ch;
}
}
escapedArg += L"\"";
return escapedArg;
}
std::wstring executeCommand(const std::wstring &command,
const std::vector<std::wstring> &args) {
SECURITY_ATTRIBUTES sa;
HANDLE hReadPipe, hWritePipe;
STARTUPINFOW si;
PROCESS_INFORMATION pi;
DWORD read;
wchar_t buffer[4096]; // For captured output
// Initialize security attributes to allow pipe handles to be inherited.
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT.
if (!::CreatePipe(&hReadPipe, &hWritePipe, &sa, 0)) {
throw std::runtime_error("failed to call \"CreatePipe\": " +
SCL::wideToUtf8(winErrorMessage(::GetLastError())));
}
// Ensure the read handle to the pipe for STDOUT is not inherited.
if (!::SetHandleInformation(hReadPipe, HANDLE_FLAG_INHERIT, 0)) {
::CloseHandle(hWritePipe);
::CloseHandle(hReadPipe);
throw std::runtime_error("failed to call \"SetHandleInformation\": " +
SCL::wideToUtf8(winErrorMessage(::GetLastError())));
}
// Set up members of the STARTUPINFO structure.
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.hStdError = hWritePipe;
si.hStdOutput = hWritePipe;
si.dwFlags |= STARTF_USESTDHANDLES;
// Construct command line
std::wstringstream cmdLineStream;
cmdLineStream << quoteAndEscapeArg(command) << L" ";
for (const auto &arg : args) {
cmdLineStream << quoteAndEscapeArg(arg) << L" ";
}
std::wstring cmdLine = cmdLineStream.str();
// Start the child process.
if (!::CreateProcessW(NULL, cmdLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
::CloseHandle(hWritePipe);
::CloseHandle(hReadPipe);
throw std::runtime_error("failed to call \"CreateProcess\": " +
SCL::wideToUtf8(winErrorMessage(::GetLastError())));
}
// Close the write end of the pipe before reading from the read end of the pipe.
::CloseHandle(hWritePipe);
// Read output from the child process's pipe for STDOUT
// and write to the parent process's STDOUT.
std::wstring output;
while (true) {
if (!::ReadFile(hReadPipe, buffer, sizeof(buffer) - sizeof(wchar_t), &read, NULL) ||
read == 0)
break;
buffer[read / sizeof(wchar_t)] = L'\0';
output += buffer;
}
// Wait until child process exits.
::WaitForSingleObject(pi.hProcess, INFINITE);
DWORD exitCode;
::GetExitCodeProcess(pi.hProcess, &exitCode);
// Close process and thread handles and the read end of the pipe.
::CloseHandle(pi.hProcess);
::CloseHandle(pi.hThread);
::CloseHandle(hReadPipe);
if (exitCode == 0)
return output;
throw std::runtime_error(SCL::wideToUtf8(output));
}
// Helper functions to convert between FILETIME and std::chrono::system_clock::time_point
static std::chrono::system_clock::time_point filetime_to_timepoint(const FILETIME &ft) {
// Windows file time starts from January 1, 1601
// std::chrono::system_clock starts from January 1, 1970
static constexpr const long long WIN_EPOCH =
116444736000000000LL; // in hundreds of nanoseconds
long long duration = (static_cast<long long>(ft.dwHighDateTime) << 32) + ft.dwLowDateTime;
duration -= WIN_EPOCH; // convert to Unix epoch
return std::chrono::system_clock::from_time_t(duration / 10000000LL);
}
static FILETIME timepoint_to_filetime(const std::chrono::system_clock::time_point &tp) {
FILETIME ft;
static constexpr const long long WIN_EPOCH =
116444736000000000LL; // in hundreds of nanoseconds
long long duration =
std::chrono::duration_cast<std::chrono::microseconds>(tp.time_since_epoch()).count();
duration = duration * 10 + WIN_EPOCH;
ft.dwLowDateTime = static_cast<DWORD>(duration & 0xFFFFFFFF);
ft.dwHighDateTime = static_cast<DWORD>((duration >> 32) & 0xFFFFFFFF);
return ft;
}
FileTime fileTime(const fs::path &path) {
HANDLE hFile = ::CreateFileW(path.wstring().data(), GENERIC_READ, FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile == INVALID_HANDLE_VALUE) {
throw std::runtime_error("invalid path: \"" + SCL::wideToUtf8(path.wstring()) + "\"");
}
FILETIME creationTime, lastAccessTime, lastWriteTime;
if (!::GetFileTime(hFile, &creationTime, &lastAccessTime, &lastWriteTime)) {
::CloseHandle(hFile);
throw std::runtime_error("failed to get file time: \"" +
SCL::wideToUtf8(path.wstring()) + "\"");
}
::CloseHandle(hFile);
FileTime times;
// ... (convert FILETIMEs to std::chrono::system_clock::time_point and store in times)
times.accessTime = filetime_to_timepoint(lastAccessTime);
times.modifyTime = filetime_to_timepoint(lastWriteTime);
times.statusChangeTime = filetime_to_timepoint(creationTime);
return times;
}
void setFileTime(const fs::path &path, const FileTime &times) {
HANDLE hFile = ::CreateFileW(path.wstring().data(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_WRITE,
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile == INVALID_HANDLE_VALUE) {
throw std::runtime_error("invalid path: \"" + SCL::wideToUtf8(path.wstring()) + "\"");
}
FILETIME creationTime, lastAccessTime, lastWriteTime;
lastAccessTime = timepoint_to_filetime(times.accessTime);
lastWriteTime = timepoint_to_filetime(times.modifyTime);
creationTime = timepoint_to_filetime(times.statusChangeTime);
if (!::SetFileTime(hFile, &creationTime, &lastAccessTime, &lastWriteTime)) {
::CloseHandle(hFile);
throw std::runtime_error("failed to set file time: \"" +
SCL::wideToUtf8(path.wstring()) + "\"");
}
::CloseHandle(hFile);
}
std::vector<std::filesystem::path> getPathsFromEnv() {
DWORD length = ::GetEnvironmentVariableW(L"PATH", nullptr, 0);
if (length == 0) {
return {};
}
auto buf = new wchar_t[length];
::GetEnvironmentVariableW(L"PATH", buf, length);
std::wstring pathStr = buf;
delete[] buf;
std::wstringstream wss(pathStr);
std::wstring item;
std::vector<fs::path> paths;
while (std::getline(wss, item, L';')) {
if (!item.empty()) {
paths.push_back(item);
}
}
return paths;
}
// ================================================================================
// Modified from windeployqt 5.15.2(Copyright Qt company)
// ================================================================================
namespace WindowsDeployQt {
static inline std::string stringFromRvaPtr(const void *rvaPtr) {
return static_cast<const char *>(rvaPtr);
}
// Helper for reading out PE executable files: Find a section header for an RVA
// (IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32).
template <class ImageNtHeader>
static const IMAGE_SECTION_HEADER *findSectionHeader(DWORD rva,
const ImageNtHeader *nTHeader) {
const IMAGE_SECTION_HEADER *section = IMAGE_FIRST_SECTION(nTHeader);
const IMAGE_SECTION_HEADER *sectionEnd =
section + nTHeader->FileHeader.NumberOfSections;
for (; section < sectionEnd; ++section)
if (rva >= section->VirtualAddress &&
rva < (section->VirtualAddress + section->Misc.VirtualSize))
return section;
return 0;
}
// Helper for reading out PE executable files: convert RVA to pointer (IMAGE_NT_HEADERS64,
// IMAGE_NT_HEADERS32).
template <class ImageNtHeader>
inline const void *rvaToPtr(DWORD rva, const ImageNtHeader *nTHeader,
const void *imageBase) {
const IMAGE_SECTION_HEADER *sectionHdr = findSectionHeader(rva, nTHeader);
if (!sectionHdr)
return 0;
const DWORD delta = sectionHdr->VirtualAddress - sectionHdr->PointerToRawData;
return static_cast<const char *>(imageBase) + rva - delta;
}
// Helper for reading out PE executable files: return word size of a IMAGE_NT_HEADERS64,
// IMAGE_NT_HEADERS32
template <class ImageNtHeader>
static unsigned ntHeaderWordSize(const ImageNtHeader *header) {
// defines IMAGE_NT_OPTIONAL_HDR32_MAGIC, IMAGE_NT_OPTIONAL_HDR64_MAGIC
enum { imageNtOptionlHeader32Magic = 0x10b, imageNtOptionlHeader64Magic = 0x20b };
if (header->OptionalHeader.Magic == imageNtOptionlHeader32Magic)
return 32;
if (header->OptionalHeader.Magic == imageNtOptionlHeader64Magic)
return 64;
return 0;
}
// Helper for reading out PE executable files: Retrieve the NT image header of an
// executable via the legacy DOS header.
static IMAGE_NT_HEADERS *getNtHeader(void *fileMemory, std::wstring *errorMessage) {
IMAGE_DOS_HEADER *dosHeader = static_cast<PIMAGE_DOS_HEADER>(fileMemory);
// Check DOS header consistency
if (IsBadReadPtr(dosHeader, sizeof(IMAGE_DOS_HEADER)) ||
dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
*errorMessage = L"DOS header check failed.";
return 0;
}
// Retrieve NT header
char *ntHeaderC = static_cast<char *>(fileMemory) + dosHeader->e_lfanew;
IMAGE_NT_HEADERS *ntHeaders = reinterpret_cast<IMAGE_NT_HEADERS *>(ntHeaderC);
// check NT header consistency
if (IsBadReadPtr(ntHeaders, sizeof(ntHeaders->Signature)) ||
ntHeaders->Signature != IMAGE_NT_SIGNATURE ||
IsBadReadPtr(&ntHeaders->FileHeader, sizeof(IMAGE_FILE_HEADER))) {
*errorMessage = L"NT header check failed.";
return 0;
}
// Check magic
if (!ntHeaderWordSize(ntHeaders)) {
std::wostringstream ss;
ss << "NT header check failed; magic " << ntHeaders->OptionalHeader.Magic
<< " is invalid.";
*errorMessage = ss.str();
return 0;
}
// Check section headers
IMAGE_SECTION_HEADER *sectionHeaders = IMAGE_FIRST_SECTION(ntHeaders);
if (IsBadReadPtr(sectionHeaders, ntHeaders->FileHeader.NumberOfSections *
sizeof(IMAGE_SECTION_HEADER))) {
*errorMessage = L"NT header section header check failed.";
return 0;
}
return ntHeaders;
}
// Helper for reading out PE executable files: Read out import sections from
// IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32.
template <class ImageNtHeader>
static std::vector<std::string> readImportSections(const ImageNtHeader *ntHeaders,
const void *base,
std::wstring *errorMessage) {
// Get import directory entry RVA and read out
const DWORD importsStartRVA =
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
.VirtualAddress;
if (!importsStartRVA) {
*errorMessage = L"Failed to find IMAGE_DIRECTORY_ENTRY_IMPORT entry.";
return {};
}
const IMAGE_IMPORT_DESCRIPTOR *importDesc =
static_cast<const IMAGE_IMPORT_DESCRIPTOR *>(
rvaToPtr(importsStartRVA, ntHeaders, base));
if (!importDesc) {
*errorMessage = L"Failed to find IMAGE_IMPORT_DESCRIPTOR entry.";
return {};
}
std::vector<std::string> result;
for (; importDesc->Name; ++importDesc)
result.push_back(stringFromRvaPtr(rvaToPtr(importDesc->Name, ntHeaders, base)));
// Read delay-loaded DLLs, see http://msdn.microsoft.com/en-us/magazine/cc301808.aspx .
// Check on grAttr bit 1 whether this is the format using RVA's > VS 6
if (const DWORD delayedImportsStartRVA =
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT]
.VirtualAddress) {
const ImgDelayDescr *delayedImportDesc = static_cast<const ImgDelayDescr *>(
rvaToPtr(delayedImportsStartRVA, ntHeaders, base));
for (; delayedImportDesc->rvaDLLName && (delayedImportDesc->grAttrs & 1);
++delayedImportDesc)
result.push_back(
stringFromRvaPtr(rvaToPtr(delayedImportDesc->rvaDLLName, ntHeaders, base)));
}
return result;
}
template <class ImageNtHeader>
static void determineDependentLibs(const ImageNtHeader *nth, const void *fileMemory,
bool isMinGW,
std::vector<std::string> *dependentLibrariesIn,
std::wstring *errorMessage) {
std::vector<std::string> dependentLibraries;
if (dependentLibrariesIn)
dependentLibraries = readImportSections(nth, fileMemory, errorMessage);
if (dependentLibrariesIn)
*dependentLibrariesIn = dependentLibraries;
}
// Read a PE executable and determine dependent libraries, word size.
bool readPeExecutable(const std::wstring &peExecutableFileName, std::wstring *errorMessage,
std::vector<std::string> *dependentLibrariesIn, unsigned *wordSizeIn,
bool isMinGW, unsigned short *machineArchIn) {
bool result = false;
HANDLE hFile = NULL;
HANDLE hFileMap = NULL;
void *fileMemory = 0;
if (dependentLibrariesIn)
dependentLibrariesIn->clear();
if (wordSizeIn)
*wordSizeIn = 0;
do {
// Create a memory mapping of the file
hFile = CreateFileW(peExecutableFileName.data(), GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE || hFile == NULL) {
std::wostringstream ss;
ss << L"Cannot open \"" << peExecutableFileName << L"\": "
<< winErrorMessage(::GetLastError());
*errorMessage = ss.str();
break;
}
hFileMap = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (hFileMap == NULL) {
std::wostringstream ss;
ss << L"Cannot create file mapping of \"" << peExecutableFileName << L"\": "
<< winErrorMessage(::GetLastError());
*errorMessage = ss.str();
break;
}
fileMemory = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
if (!fileMemory) {
std::wostringstream ss;
ss << L"Cannot map \"" << peExecutableFileName << L"\": "
<< winErrorMessage(::GetLastError());
*errorMessage = ss.str();
break;
}
const IMAGE_NT_HEADERS *ntHeaders = getNtHeader(fileMemory, errorMessage);
if (!ntHeaders)
break;
const unsigned wordSize = ntHeaderWordSize(ntHeaders);
if (wordSizeIn)
*wordSizeIn = wordSize;
if (wordSize == 32) {
determineDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS32 *>(ntHeaders),
fileMemory, isMinGW, dependentLibrariesIn, errorMessage);
} else {
determineDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS64 *>(ntHeaders),
fileMemory, isMinGW, dependentLibrariesIn, errorMessage);
}
if (machineArchIn)
*machineArchIn = ntHeaders->FileHeader.Machine;
result = true;
} while (false);
if (fileMemory)
UnmapViewOfFile(fileMemory);
if (hFileMap != NULL)
CloseHandle(hFileMap);
if (hFile != NULL && hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
return result;
}
}
std::vector<std::wstring>
resolveWinBinaryDependencies(const std::filesystem::path &path,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed) {
std::wstring errorMessage;
std::vector<std::string> dependentLibrariesIn;
unsigned wordSizeIn;
bool isMinGW = false;
unsigned short machineArchIn;
if (!WindowsDeployQt::readPeExecutable(path, &errorMessage, &dependentLibrariesIn,
&wordSizeIn, isMinGW, &machineArchIn)) {
throw std::runtime_error(SCL::wideToUtf8(errorMessage));
}
// Search
std::vector<std::wstring> result;
for (const auto &item : std::as_const(dependentLibrariesIn)) {
fs::path fullPath;
for (const auto &dir : std::as_const(searchingPaths)) {
fs::path targetPath = dir / item;
if (fs::exists(targetPath)) {
fullPath = targetPath;
break;
}
}
if (!fullPath.empty()) {
result.push_back(fullPath);
continue;
}
if (unparsed) {
unparsed->push_back(item);
}
}
return result;
}
}

View File

@@ -0,0 +1,13 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
include("${CMAKE_CURRENT_LIST_DIR}/qmsetupTargets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/cmake/QMSetupAPI.cmake")
if ("All" IN_LIST qmsetup_FIND_COMPONENTS)
qm_import_all()
else()
qm_import(${qmsetup_FIND_COMPONENTS})
endif()

View File

@@ -0,0 +1,130 @@
# References:
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
# https://code.qt.io/cgit/qt/qt5.git/tree/_clang-format
BasedOnStyle: LLVM
Standard: c++17
# 指针和引用的对齐方式。
# 可能的值有:
# PAS_Left (在配置中: Left) 指针左对齐。
# PAS_Right (在配置中: Right) 指针右对齐。
# PAS_Middle (在配置中: Middle) 指针中间对齐。
PointerAlignment: Right
# public/protected/private 等访问修饰符偏移量
AccessModifierOffset: -4
# 缩进长度
IndentWidth: 4
# 连续空行的最大数
MaxEmptyLinesToKeep: 999
# 在OC中的@property后面添加一个空格。例如使用“@property (readonly)”而不是“@property(readonly)”
ObjCSpaceAfterProperty: true
# OC块中所拍的字符数
ObjCBlockIndentWidth: 4
# 取决于值, 语句“int f() { return 0; }”可以被放到一个单行。
# 可能的值有:
# SFS_None (在配置中: None) 从不合并方法或函数到单独的一行。
# SFS_Empty (在配置中: Empty) 仅合并空的函数。
# SFS_Inline (在配置中: Inline) 仅合并类中定义的方法或函数. 意味着 “empty”.
# SFS_All (在配置中: All) 合并所有的方法适应单行.
AllowShortFunctionsOnASingleLine: None
# 如果为真true, 语句“if (a) return;” 能被放到单行。
AllowShortIfStatementsOnASingleLine: false
# 如果为真true, 对齐注释。
AlignTrailingComments: true
# 如果为真,对齐连续的宏定义
AlignConsecutiveMacros: true
# 如果为真true,将会在“[”之后和“]”之前插入空格。
SpacesInSquareBrackets: false
# 如果为真true, 将会在“(”之后和“)”之前插入空格。
SpacesInParentheses : false
# 如果为真true, 校准连续的声明。
# 这将会校准连续多行的声明的名字。这将会导致像下面这样的格式:
# int aaaa = 12;
# float b = 23;
# std::string ccc = 23;
AlignConsecutiveDeclarations: false
# 如果为真true连续调整多行
# 这将会调整连续行中的分配操作符。这将会导致像下面这样的格式:
# int aaaa = 12;
# int b = 23;
# int ccc = 23;
AlignConsecutiveAssignments: false
# 如果为假false移除分配操作符=)前空格。
SpaceBeforeAssignmentOperators: true
# 如果为真true, 将会在字面量容器中插入空格(例如 OC和Javascript的数组和字典字面量)。
SpacesInContainerLiterals: false
# 缩进case标签
IndentCaseLabels: true
# 如果表达式中包含函数调用,并且函数调用因为表达式太长被放到了下一行,是否缩进
IndentWrappedFunctionNames: true
# 如果为真true, 保持块的起始空行。
# true: false:
# if (foo) { vs. if (foo) {
# bar();
# bar(); }
# }
KeepEmptyLinesAtTheStartOfBlocks: true
# 允许所有参数都被放在下一行
AllowAllParametersOfDeclarationOnNextLine: false
# 使用C风格强制类型转换后是否在中间添加一个空格
SpaceAfterCStyleCast: true
# 在模板定义后换行
AlwaysBreakTemplateDeclarations: Yes
# Tab长度
TabWidth: 4
# 是否使用Tab
UseTab: Never
# 在括号后对齐参数
# someLongFunction(argument1,
# argument2);
AlignAfterOpenBracket: Align
# 名字空间内部缩进
NamespaceIndentation: All
# 一行最长列数
ColumnLimit: 100
# 按层次缩进宏定义
IndentPPDirectives: AfterHash
# 预处理语句缩进为 2
PPIndentWidth: 2
# 数组元素对齐
AlignArrayOfStructures: Left
# 不对头文件排序
SortIncludes: Never
FixNamespaceComments: false
StatementMacros: ['__qas_attr__', '__qas_exclude__', '__qas_include__']
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE ]

View File

@@ -0,0 +1,98 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.log
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
# *.res
# *.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
__pycache__
*.pyc
# Binaries
# --------
*.dll
*.exe
.DS_Store
*/.DS_Store
build-src*
build/
cmake-build*
*.user
*.lnk
_workingDir*
.vscode
.idea
.cache
cache
.vs
out/
CMakeSettings.json
# /vcpkg
/data
/*.natvis
*.sublime-*
setup-vcpkg.json
setup-vcpkg-temp*

View File

@@ -0,0 +1,165 @@
cmake_minimum_required(VERSION 3.17)
project(syscmdline VERSION 1.0.0.0 LANGUAGES C CXX)
# ----------------------------------
# Build Options
# ----------------------------------
option(SYSCMDLINE_BUILD_STATIC "Build static library" ON)
option(SYSCMDLINE_BUILD_EXAMPLES "Build examples" OFF)
option(SYSCMDLINE_BUILD_TESTS "Build test cases" OFF)
option(SYSCMDLINE_FORCE_VALIDITY_CHECK "Force to enable validity check" OFF)
option(SYSCMDLINE_INSTALL "Install library" ON)
# ----------------------------------
# CMake Settings
# ----------------------------------
if(NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
endif()
if(NOT DEFINED CMAKE_LIBRARY_OUTPUT_DIRECTORY)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
endif()
if(NOT DEFINED CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
endif()
if(NOT DEFINED CMAKE_BUILD_SHARE_DIR)
set(CMAKE_BUILD_SHARE_DIR ${CMAKE_BINARY_DIR}/share)
endif()
if(MSVC)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /manifest:no")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /manifest:no")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /manifest:no")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8")
if(NOT DEFINED CMAKE_DEBUG_POSTFIX)
set(CMAKE_DEBUG_POSTFIX "d")
endif()
endif()
if(SYSCMDLINE_INSTALL)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
endif()
# ----------------------------------
# Project Variables
# ----------------------------------
set(SYSCMDLINE_VERSION ${PROJECT_VERSION})
set(SYSCMDLINE_INSTALL_NAME ${PROJECT_NAME})
# ----------------------------------
# Main Project
# ----------------------------------
if(TRUE)
# Add library
if(SYSCMDLINE_BUILD_STATIC)
add_library(${PROJECT_NAME} STATIC)
target_compile_definitions(${PROJECT_NAME} PUBLIC SYSCMDLINE_STATIC)
else()
add_library(${PROJECT_NAME} SHARED)
endif()
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
# Add sources
file(GLOB_RECURSE _src include/syscmdline/*.h src/*.h src/*.cpp)
target_sources(${PROJECT_NAME} PRIVATE ${_src})
# Add features
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20 cxx_std_17)
# Add options
target_compile_definitions(${PROJECT_NAME} PRIVATE SYSCMDLINE_LIBRARY)
if(SYSCMDLINE_FORCE_VALIDITY_CHECK)
target_compile_definitions(${PROJECT_NAME} PRIVATE SYSCMDLINE_ENABLE_VALIDITY_CHECK)
else()
target_compile_definitions(${PROJECT_NAME} PRIVATE
"$<$<CONFIG:Debug>:SYSCMDLINE_ENABLE_VALIDITY_CHECK>"
)
endif()
# Include directories
target_include_directories(${PROJECT_NAME} PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
)
target_include_directories(${PROJECT_NAME} PRIVATE include/syscmdline)
# Add platform specific
if(WIN32)
set(RC_DESCRIPTION "Another C++ Command Line Parser")
set(RC_COPYRIGHT "Copyright 2023 SineStriker")
include("cmake/winrc.cmake")
endif()
# if(MSVC)
# if(${CMAKE_BUILD_TYPE} STREQUAL "Release")
# target_compile_options(${PROJECT_NAME} PRIVATE /GL /Os)
# target_link_options(${PROJECT_NAME} PRIVATE /OPT:REF /OPT:ICF /OPT:LBR)
# endif()
# endif()
if(SYSCMDLINE_INSTALL)
target_include_directories(${PROJECT_NAME} PUBLIC
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
install(TARGETS ${PROJECT_NAME}
EXPORT ${SYSCMDLINE_INSTALL_NAME}Targets
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL
)
endif()
endif()
if(SYSCMDLINE_BUILD_TESTS)
add_subdirectory(tests)
endif()
if(SYSCMDLINE_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
if(SYSCMDLINE_INSTALL)
# Add install target
set(_install_dir ${CMAKE_INSTALL_LIBDIR}/cmake/${SYSCMDLINE_INSTALL_NAME})
# Add version file
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${SYSCMDLINE_INSTALL_NAME}ConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)
# Add configuration file
configure_package_config_file(
${CMAKE_CURRENT_LIST_DIR}/${SYSCMDLINE_INSTALL_NAME}Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/${SYSCMDLINE_INSTALL_NAME}Config.cmake"
INSTALL_DESTINATION ${_install_dir}
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
# Install cmake files
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/${SYSCMDLINE_INSTALL_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${SYSCMDLINE_INSTALL_NAME}ConfigVersion.cmake"
DESTINATION ${_install_dir}
)
# Install cmake targets files
install(EXPORT ${SYSCMDLINE_INSTALL_NAME}Targets
FILE "${SYSCMDLINE_INSTALL_NAME}Targets.cmake"
NAMESPACE syscmdline::
DESTINATION ${_install_dir}
)
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
FILES_MATCHING PATTERN "*.h"
)
endif()

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 SineStriker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Some files were not shown because too many files have changed in this diff Show More