├── .clangd
├── .gitattributes
├── .github
└── workflows
│ └── xmake.yml
├── .gitignore
├── .gitmodules
├── .vscode
└── settings.json
├── CONFIG.md
├── DONATE.md
├── LICENSE
├── README.md
├── README_zh.md
├── dependencies
├── blook.lua
├── glfw.lua
├── quickjs-ng.lua
└── reflect-cpp.lua
├── resources
├── animated-preview1.webp
├── bloom.webp
├── breeze.html
├── code.webp
├── icon-small.png
├── icon.7z
├── icon.png
├── icon.webp
├── inject-en.webp
├── inject.webp
├── preview1.webp
└── schema.json
├── scripts
├── bindgen
│ ├── .gitignore
│ ├── c-type-parser.ts
│ ├── clang-ast.d.ts
│ ├── generate-bindings.bat
│ ├── index.ts
│ ├── package.json
│ ├── quickjs-types.txt
│ ├── typegen.ts
│ └── yarn.lock
├── debug.cmd
├── rebuild.ps1
└── right_click.ahk
├── src
├── inject
│ ├── data_directory.inc
│ └── inject.cc
├── shell
│ ├── build_info.h.in
│ ├── config.cc
│ ├── config.h
│ ├── contextmenu
│ │ ├── contextmenu.cc
│ │ ├── contextmenu.h
│ │ ├── menu_render.cc
│ │ ├── menu_render.h
│ │ ├── menu_widget.cc
│ │ └── menu_widget.h
│ ├── entry.cc
│ ├── entry.h
│ ├── error_handler.cc
│ ├── error_handler.h
│ ├── fix_win11_menu.cc
│ ├── fix_win11_menu.h
│ ├── logger.cc
│ ├── logger.h
│ ├── res_string_loader.cc
│ ├── res_string_loader.h
│ ├── script
│ │ ├── FileWatch.hpp
│ │ ├── binding_qjs.h
│ │ ├── binding_types.cc
│ │ ├── binding_types.d.ts
│ │ ├── binding_types.h
│ │ ├── binding_types_com.cc
│ │ ├── quickjspp.cc
│ │ ├── quickjspp.hpp
│ │ ├── script.cc
│ │ ├── script.h
│ │ └── script.js
│ ├── utils.cc
│ ├── utils.h
│ ├── window_proc_hook.cc
│ └── window_proc_hook.h
├── ui
│ ├── animator.cc
│ ├── animator.h
│ ├── extra_widgets.cc
│ ├── extra_widgets.h
│ ├── hbitmap_utils.cc
│ ├── hbitmap_utils.h
│ ├── nanosvg.cc
│ ├── nanovg_wrapper.h
│ ├── swcadef.h
│ ├── ui.cc
│ ├── ui.h
│ ├── widget.cc
│ └── widget.h
└── ui_test
│ └── test.cc
└── xmake.lua
/.clangd:
--------------------------------------------------------------------------------
1 | CompileFlags:
2 | Add: ["-std:latest", "/clang:-std=c++23"]
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.h linguist-vendored
2 | *.c linguist-vendored
--------------------------------------------------------------------------------
/.github/workflows/xmake.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | permissions: write-all
3 |
4 | on:
5 | push:
6 | branches: [ "master" ]
7 | pull_request:
8 | branches: [ "master" ]
9 | release:
10 | types: [created]
11 |
12 | jobs:
13 | build:
14 | runs-on: windows-2025
15 |
16 | steps:
17 | - uses: actions/checkout@v4
18 | with:
19 | submodules: recursive
20 |
21 | - uses: xmake-io/github-action-setup-xmake@v1
22 | with:
23 | xmake-version: latest
24 | actions-cache-folder: '.xmake-cache'
25 | actions-cache-key: 'ci'
26 | package-cache: true
27 | package-cache-key: windows-2025
28 | # build-cache: true
29 | # build-cache-key: ${{ matrix.os }}-${{ matrix.build_type }}
30 |
31 | - name: Xmake configure
32 | run: |
33 | xmake config --yes --toolchain=clang-cl --mode=releasedbg
34 |
35 | - name: build-releasedbg
36 | run: |
37 | xmake b --yes --verbose inject
38 | xmake b --yes --verbose shell
39 |
40 | - name: Upload Artifacts
41 | uses: actions/upload-artifact@v4.6.0
42 | with:
43 | path: ./build/windows/x64/
44 | name: windows-build
45 |
46 | - name: Create Archive
47 | if: github.event_name == 'release'
48 | run: |
49 | Compress-Archive -Path ./build/windows/* -DestinationPath windows-build-pdb.zip
50 | Remove-Item -Path ./build/windows/x64/releasedbg/*.pdb -Force
51 | Remove-Item -Path ./build/windows/x64/releasedbg/ui.lib -Force
52 | Compress-Archive -Path ./build/windows/* -DestinationPath windows-build.zip
53 |
54 | - name: Upload Release Assets
55 | if: github.event_name == 'release'
56 | uses: softprops/action-gh-release@v1
57 | with:
58 | files: |
59 | windows-build.zip
60 | windows-build-pdb.zip
61 | token: ${{ secrets.GITHUB_TOKEN }}
62 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | compile_commands.json
3 | .xmake
4 | .cache
5 | build_info.h
6 | /test
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "blook"]
2 | path = dependencies/blook
3 | url = https://github.com/std-microblock/blook
4 | [submodule "dependencies/glfw"]
5 | path = dependencies/glfw
6 | url = https://github.com/breeze-shell/glfw
7 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cmake.ignoreCMakeListsMissing": true,
3 | "files.associations": {
4 | "xstring": "cpp"
5 | }
6 | }
--------------------------------------------------------------------------------
/CONFIG.md:
--------------------------------------------------------------------------------
1 | # 配置文件格式说明
2 |
3 | 本项目的配置文件采用 JSON 格式,推荐使用 VSCode 进行编辑。
4 |
5 | 本项目配置文件默认位于 `%USERPROFILE%/.breeze-shell/config.json`。
6 |
7 | 编辑配置文件并保存后,插件将会自动重载配置,无需重新启动。
8 |
9 | **如果保存后弹出了黑窗口,这大概是因为你的配置文件有错误,请阅读黑窗口内的报错信息并修复错误**
10 |
11 | ## Schema
12 |
13 | Breeze Shell 配置文件的 JSON Schema 位于
14 | [resources/schema.json](./resources/schema.json),在配置文件内写入
15 |
16 | ```json
17 | {
18 | "$schema": "https://raw.githubusercontent.com/std-microblock/breeze-shell/refs/heads/master/resources/schema.json"
19 | }
20 | ```
21 |
22 | 即可在 VSCode 中看到配置文件类型检查及补全。
23 |
24 | ## 配置文件结构
25 |
26 | 以下为一份带有注释的完整默认 JSON 配置,注意其**不能**直接填入 config.json
27 | 当中,因为配置文件解析当前不支持注释
28 |
29 | ```json5
30 | {
31 | "context_menu": {
32 | "theme": {
33 | // 在 Windows 11 下使用 DWM 圆角而不是 SetWindowRgn 圆角
34 | "use_dwm_if_available": true,
35 | // 启用亚克力背景效果
36 | "acrylic": true,
37 | // 圆角大小,仅在不使用 DWM 圆角时生效
38 | "radius": 6.0,
39 | // 字体大小,可调整此项以对齐缩放后的整数倍率字体大小以避免模糊
40 | "font_size": 14.0,
41 | // 项高度
42 | "item_height": 23.0,
43 | // 项间距
44 | "item_gap": 3.0,
45 | // 项圆角大小
46 | "item_radius": 5.0,
47 | // 外边距
48 | "margin": 5.0,
49 | // 内边距
50 | "padding": 6.0,
51 | // 文笔内边距
52 | "text_padding": 8.0,
53 | // 图标内边距
54 | "icon_padding": 4.0,
55 | // 右侧图标(展开图标)边距
56 | "right_icon_padding": 20.0,
57 | // 横排按钮间距(此处为负值以抵消项边距的效果)
58 | "multibutton_line_gap": -6.0,
59 | // 在亮色主题下的亚克力背景颜色
60 | "acrylic_color_light": "#fefefe00",
61 | // 在暗色主题下的亚克力背景颜色
62 | "acrylic_color_dark": "#28282800",
63 | // 背景透明度
64 | "background_opacity":1.0,
65 | // 动画相关
66 | "animation": {
67 | // 菜单项动画
68 | "item": {
69 | // animated_float_conf: 通用动画配置
70 | "opacity": {
71 | // 持续时长
72 | "duration": 200.0,
73 | // 动画曲线
74 | // 可为:
75 | // mutation (关闭动画)
76 | // linear (线性)
77 | // ease_in, ease_out, ease_in_out (三种缓动曲线)
78 | "easing": "ease_in_out",
79 | // 对延迟时间的缩放
80 | // 即:如果本来是在开始总动画 50ms 后显示该动画,
81 | // 若 delay_scale 为 2 则在 100ms 后才显示
82 | "delay_scale": 1.0
83 | },
84 | // 同 opacity,以下均省略
85 | "x": animated_float_conf,
86 | "y": animated_float_conf,
87 | "width": animated_float_conf
88 | },
89 | // 主菜单的背景
90 | "main_bg": {
91 | "opacity": animated_float_conf,
92 | "x": animated_float_conf,
93 | "y": animated_float_conf,
94 | "w": animated_float_conf,
95 | "h": animated_float_conf
96 | },
97 | // 子菜单的背景,同主菜单
98 | "submenu_bg": {
99 | ...
100 | }
101 | }
102 | },
103 | // 启用垂直同步
104 | "vsync": true,
105 | // 不替换 owner draw 的菜单
106 | "ignore_owner_draw": true,
107 | // 在向上展开时将所有项反向
108 | "reverse_if_open_to_up": true,
109 | // 调试选项,搜索更大范围的图标,不建议打开
110 | "search_large_dwItemData_range": false,
111 | // 定位相关
112 | "position": {
113 | // 竖直边距
114 | "padding_vertical": 20,
115 | // 水平边距
116 | "padding_horizontal": 0
117 | }
118 | },
119 |
120 | // 开启调试窗口
121 | "debug_console": false,
122 |
123 | // 主字体
124 | "font_path_main": "C:\\WINDOWS\\Fonts\\segoeui.ttf",
125 | // 副字体
126 | "font_path_fallback": "C:\\WINDOWS\\Fonts\\msyh.ttc",
127 | // 使用 hook 方式加载更多 resid
128 | "res_string_loader_use_hook": false,
129 | // 调试选项,避免更改 UI 窗口大小
130 | "avoid_resize_ui": false,
131 | // 插件加载顺序,在越前面的越先加载
132 | // 格式为插件的无拓展名文件名
133 | // 如:Windows 11 Icon Pack
134 | "plugin_load_order": [],
135 | // 全局默认动画效果
136 | "default_animation": animated_float_conf
137 | }
138 | ```
139 |
140 | ## 示例配置文件
141 |
142 | #### 禁用所有动画
143 |
144 | ```json
145 | {
146 | "default_animation": {
147 | "easing": "mutation"
148 | }
149 | }
150 | ```
151 |
--------------------------------------------------------------------------------
/DONATE.md:
--------------------------------------------------------------------------------
1 | ### Donate to this project
2 | Donate to me if you like the project~
3 |
4 | #### 中国大陆
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > [!WARNING]
2 | > This project is still in active development. File a bug report if you meet
3 | > any!\
4 | > 此项目仍在开发阶段,如果遇到问题请发送 Issue
5 | >
6 | > Both English and Chinese issues are accepted.
7 | > Issue 中使用中文或英文均可
8 |
9 | [中文](./README_zh.md) [Donate Me](./DONATE.md) [Discord](https://discord.gg/MgpHk8pa3d)
10 |
11 |
12 |
13 |
Breeze Shell
14 |
Bring fluency & delication back to Windows
15 |
16 |
17 |
18 |
19 |
20 | Breeze is an **alternative context menu** for Windows 10 and Windows 11.
21 |
22 | ## Fluent
23 | Breeze is designed with animations in mind.
24 |
25 |
26 |
27 | All animations are configurable and can be scaled and disabled as you want.
28 | ## Extensible
29 |
30 | Empowered by the embedded JavaScript script api, Breeze enables you to extend
31 | the functionalities of your context menu in a few lines of code.
32 |
33 | ```javascript
34 | shell.menu_controller.add_menu_listener((e) => {
35 | e.menu.add({
36 | type: "button",
37 | name: "Shuffle Buttons",
38 | action: () => {
39 | for (const item of menus) {
40 | item.set_position(Math.floor(menus.length * Math.random()));
41 | }
42 | },
43 | }, 0);
44 | });
45 | ```
46 |
47 | [See full bindings →](./src/shell/script/binding_types.d.ts)
48 |
49 | Send pull requests to [this repo](https://github.com/breeze-shell/plugins) to add your script to the plugin market!
50 |
51 | ## Configurable
52 | Breeze shell exposed a bunch of configurations ranging from item height to background radius method. Customize them as you need.
53 |
54 | [Configuration Document →](./CONFIG.md)
55 |
56 | The config menu of breeze-shell can be found as you open your `Data Folder` and right-click anywhere inside it.
57 |
58 | ## Lightweight & Fast
59 |
60 | Breeze uses breeze-ui, which is implemented to be a cross-platform, simple,
61 | animation-friendly and fast ui library for modern C++, with the support of both
62 | NanoVG and ThorVG render context. This allowed Breeze to have a delicated user
63 | interface in ~2MiB.
64 |
65 | # Try it out!
66 |
67 | Download and extract the zip, and Run `breeze.exe`.
68 |
69 | 
70 |
71 |
72 | # Building
73 |
74 | Breeze uses xmake. You'd have to install xmake in your computer first. Then,
75 | type `xmake` in the project dir and follow the instructions. Both clang-cl and
76 | MSVC 2019+ can build this project.
77 |
78 | # Developing
79 |
80 | After building successfully once, you can oprn the project dir in VSCode for
81 | development. Install clangd plugin for full intellisense.
82 |
83 | # Credits
84 |
85 | #### Third-party libraries
86 | - https://github.com/std-microblock/blook
87 | - https://github.com/quickjs-ng/quickjs
88 | - https://github.com/ftk/quickjspp
89 | - https://github.com/getml/reflect-cpp
90 | - https://github.com/glfw/glfw
91 | - https://github.com/Dav1dde/glad
92 | - https://github.com/memononen/nanovg
93 | - https://github.com/memononen/nanosvg
94 | - https://github.com/freetype/freetype
95 |
96 | #### Others
97 | - [@lipraty](https://github.com/lipraty) - Icon Design
98 | - [moudey/Shell](https://github.com/moudey/Shell) - Inspiration
99 |
--------------------------------------------------------------------------------
/README_zh.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Breeze Shell
4 |
为 Windows 重新带来流畅与精致体验
5 |
6 |
7 |
8 |
9 |
10 | Breeze 是专为 Windows 10/11 设计的现代化**次世代右键菜单解决方案**。
11 |
12 | ## 丝滑流畅
13 |
14 | Breeze 以交互动画为核心设计理念。
15 |
16 |
17 |
18 | ## 无限扩展
19 |
20 | 通过嵌入式 JavaScript 脚本 API,您可以用寥寥数行代码为右键菜单增添全新功能。
21 |
22 | ```javascript
23 | shell.menu_controller.add_menu_listener((e) => {
24 | e.menu.add({
25 | type: "button",
26 | name: "Shuffle Buttons",
27 | action: () => {
28 | for (const item of menus) {
29 | item.set_position(Math.floor(menus.length * Math.random()));
30 | }
31 | },
32 | }, 0);
33 | });
34 | ```
35 |
36 | [查看完整 API 文档 →](./src/shell/script/binding_types.d.ts)
37 |
38 | ## 轻量高速
39 |
40 | Breeze 基于自研 breeze-ui 框架实现,这是一个跨平台、简洁优雅、动画友好的现代 C++ UI 库,支持 NanoVG 和 ThorVG 双渲染后端。这使得 Breeze 能在约 2MB 的体积下实现精致的视觉体验。
41 |
42 | # 立即体验
43 |
44 | 从 Releases 下载,然后解压压缩包并运行 `breeze.exe`
45 |
46 | # 构建指南
47 |
48 | 本项目使用 xmake 构建系统。请先安装 xmake,在项目根目录执行 `xmake` 命令并按提示操作。支持 clang-cl 和 MSVC 2019+ 编译器。
49 |
50 | # 开发指南
51 |
52 | 首次构建成功后,可使用 VSCode 打开项目进行开发。建议安装 clangd 插件以获得完整的代码智能提示。
53 |
--------------------------------------------------------------------------------
/dependencies/blook.lua:
--------------------------------------------------------------------------------
1 | package("blook")
2 | add_deps("cmake")
3 | add_syslinks("advapi32")
4 | set_sourcedir(path.join(os.scriptdir(), "blook"))
5 | on_install(function (package)
6 | local fcdir = package:cachedir() .. "/fetchcontent"
7 | import("package.tools.cmake").install(package, {
8 | "-DCMAKE_INSTALL_PREFIX=" .. package:installdir(),
9 | "-DCMAKE_PREFIX_PATH=" .. package:installdir(),
10 | "-DFETCHCONTENT_QUIET=OFF",
11 | "-DFETCHCONTENT_BASE_DIR=" .. fcdir,
12 | })
13 |
14 | os.cp("include/blook/**", package:installdir("include/blook/"))
15 | os.cp("external/zasm/zasm/include/**", package:installdir("include/zasm/"))
16 | os.cp(fcdir .. "/zydis-src/dependencies/zycore/include/**", package:installdir("include/zycore/"))
17 | os.cp(package:buildir() .. "/blook.lib", package:installdir("lib"))
18 | os.cp(package:buildir() .. "/external/zasm/zasm.lib", package:installdir("lib"))
19 | end)
20 | package_end()
--------------------------------------------------------------------------------
/dependencies/glfw.lua:
--------------------------------------------------------------------------------
1 | package("breeze-glfw")
2 | set_base("glfw")
3 |
4 | set_urls("https://github.com/breeze-shell/glfw.git")
5 |
6 | add_versions("2025.04.13", "71513247ae3cc7eca66ca88f0789c64b5e693115")
7 |
--------------------------------------------------------------------------------
/dependencies/quickjs-ng.lua:
--------------------------------------------------------------------------------
1 | package("quickjs-ng")
2 | set_homepage("https://github.com/quickjs-ng/quickjs")
3 | set_description("QuickJS, the Next Generation: a mighty JavaScript engine")
4 | set_license("MIT")
5 |
6 | add_urls("https://github.com/quickjs-ng/quickjs/archive/refs/tags/$(version).tar.gz",
7 | "https://github.com/quickjs-ng/quickjs.git", {submodules = false})
8 |
9 | add_versions("v0.8.0", "7e60e1e0dcd07d25664331308a2f4aee2a88d60d85896e828d25df7c3d40204e")
10 |
11 | add_configs("libc", {description = "Build standard library modules as part of the library", default = false, type = "boolean"})
12 |
13 | if is_plat("linux", "bsd") then
14 | add_syslinks("m", "pthread")
15 | end
16 |
17 | add_deps("cmake")
18 |
19 | if on_check then
20 | on_check("windows", function (package)
21 | local vs_toolset = package:toolchain("msvc"):config("vs_toolset")
22 | if vs_toolset then
23 | local vs_toolset_ver = import("core.base.semver").new(vs_toolset)
24 | local minor = vs_toolset_ver:minor()
25 | assert(minor and minor >= 30, "package(quickjs-ng) require vs_toolset >= 14.3")
26 | end
27 | end)
28 | on_check("wasm", "cross", function (package)
29 | if package:version():eq("0.8.0") then
30 | raise("package(quickjs-ng v0.8.0) unsupported platform")
31 | end
32 | end)
33 | end
34 |
35 | on_install("!iphoneos and (!windows or windows|!x86)", function (package)
36 | io.replace("CMakeLists.txt", "xcheck_add_c_compiler_flag(-Werror)", "", {plain = true})
37 | io.replace("CMakeLists.txt", "if(NOT WIN32 AND NOT EMSCRIPTEN)", "if(0)", {plain = true})
38 |
39 | local configs = {}
40 | table.insert(configs, "-DCMAKE_BUILD_TYPE=" .. (package:is_debug() and "Debug" or "Release"))
41 | if package:is_plat("windows") then
42 | if false then
43 | -- add /debug to link flags
44 | table.insert(configs, "-DCMAKE_EXE_LINKER_FLAGS_RELEASE=\"/DEBUG\"")
45 | table.insert(configs, "-DCMAKE_SHARED_LINKER_FLAGS_RELEASE=\"/DEBUG\"")
46 |
47 | -- also add /DEBUG to cflags, and /Zi to cxxflags
48 | table.insert(configs, "-DCMAKE_C_FLAGS_RELEASE=/Zi /DEBUG")
49 | table.insert(configs, "-DCMAKE_CXX_FLAGS_RELEASE=/Zi /DEBUG")
50 | end
51 | end
52 |
53 | table.insert(configs, "-DBUILD_SHARED_LIBS=" .. (package:config("shared") and "ON" or "OFF"))
54 | table.insert(configs, "-DCONFIG_ASAN=" .. (package:config("asan") and "ON" or "OFF"))
55 | table.insert(configs, "-DCONFIG_MSAN=" .. (package:config("msan") and "ON" or "OFF"))
56 | table.insert(configs, "-DCONFIG_UBSAN=" .. (package:config("ubsan") and "ON" or "OFF"))
57 | table.insert(configs, "-DBUILD_QJS_LIBC=" .. (package:config("libc") and "ON" or "OFF"))
58 | if package:config("shared") and package:is_plat("windows") then
59 | table.insert(configs, "-DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=ON")
60 | end
61 | import("package.tools.cmake").install(package, configs)
62 |
63 | if package:is_plat("windows") and package:is_debug() then
64 | local dir = package:installdir(package:config("shared") and "bin" or "lib")
65 | os.vcp(path.join(package:buildir(), "*.pdb"), dir)
66 | end
67 | end)
68 |
69 | on_test(function (package)
70 | assert(package:has_cfuncs("JS_NewRuntime", {includes = "quickjs.h"}))
71 | end)
--------------------------------------------------------------------------------
/dependencies/reflect-cpp.lua:
--------------------------------------------------------------------------------
1 | package("reflect-cpp")
2 | set_homepage("https://github.com/getml/reflect-cpp")
3 | set_description("A C++20 library for fast serialization, deserialization and validation using reflection. Supports JSON, BSON, CBOR, flexbuffers, msgpack, TOML, XML, YAML / msgpack.org[C++20]")
4 | set_license("MIT")
5 |
6 | add_urls("https://github.com/getml/reflect-cpp/archive/refs/tags/$(version).tar.gz",
7 | "https://github.com/getml/reflect-cpp.git", {submodules = false})
8 |
9 | add_versions("v0.17.0", "08b6406cbe4c6c14ff1a619fe93a94f92f6d9eb22213d93529ad975993945e45")
10 | add_versions("v0.16.0", "a84d94dbd353d788926d6e54507b44c046863f7bc4ecb35afe0338374a68a77d")
11 | add_versions("v0.14.1", "639aec9d33025703a58d32c231ab1ab474c0cc4fb0ff90eadcaffb49271c41cd")
12 | add_versions("v0.14.0", "ea92a2460a71184b7d4fa4e9baad9910efad092df78b114459a7d6b0ee558d3c")
13 | add_versions("v0.13.0", "a7a31832fe8bbaa7f7299da46dfd4ccc8b99a13242e16a1d93f8669de1fca9c6")
14 | add_versions("v0.12.0", "13d448dd5eaee13ecb7ab5cb61cb263c7111ba75230503adc823a888f68e1eaa")
15 | add_versions("v0.11.1", "e45f112fb3f14507a4aa53b99ae2d4ab6a4e7b2d5f04dd06fec00bf7faa7bbdc")
16 | add_versions("v0.10.0", "d2c8876d993ddc8c57c5804e767786bdb46a2bdf1a6cd81f4b14f57b1552dfd7")
17 |
18 | add_patches("0.16.0", "patches/0.16.0/cmake.patch", "1b2a6e0ed81dd0bd373bd1daaf52010de965f3829e5e19406c53e8ebf0a5b9fc")
19 | add_patches("0.11.1", "patches/0.11.1/cmake.patch", "a43ae2c6de455054ab860adfb309da7bd376c31c493c8bab0ebe07aae0805205")
20 | add_patches("0.10.0", "patches/0.10.0/cmake.patch", "b8929c0a13bd4045cbdeea0127e08a784e2dc8c43209ca9f056fff4a3ab5c4d3")
21 |
22 | add_configs("bson", {description = "Enable Bson Support.", default = false, type = "boolean", readonly = true})
23 | add_configs("yyjson", {description = "Enable yyjson Support.", default = true, type = "boolean"})
24 | add_configs("cbor", {description = "Enable Cbor Support.", default = false, type = "boolean"})
25 | add_configs("flatbuffers", {description = "Enable Flexbuffers Support.", default = false, type = "boolean"})
26 | add_configs("msgpack", {description = "Enable Msgpack Support.", default = false, type = "boolean"})
27 | add_configs("xml", {description = "Enable Xml Support.", default = false, type = "boolean"})
28 | add_configs("toml", {description = "Enable Toml Support.", default = false, type = "boolean"})
29 | add_configs("yaml", {description = "Enable Yaml Support.", default = false, type = "boolean"})
30 | add_configs("ubjson", {description = "Enable UBJSON Support.", default = false, type = "boolean"})
31 | if is_plat("windows") then
32 | add_configs("shared", {description = "Build shared library.", default = false, type = "boolean", readonly = true})
33 | end
34 |
35 | on_check(function (package)
36 | if package:is_plat("windows") then
37 | local vs = package:toolchain("msvc"):config("vs")
38 | assert(vs and tonumber(vs) >= 2022, "package(reflect-cpp): need vs >= 2022")
39 | else
40 | assert(package:check_cxxsnippets({test = [[
41 | #include
42 | #include
43 | #include
44 | void test() {
45 | constexpr std::string_view message = "Hello, C++20!";
46 | for (char c : std::views::filter(message, [](char c) { return std::islower(c); }))
47 | std::cout << std::source_location::current().line() << ": " << c << '\n';
48 | }
49 | ]]}, {configs = {languages = "c++20"}}), "package(reflect-cpp) Require at least C++20.")
50 | end
51 | end)
52 |
53 | on_load(function (package)
54 | if package:config("yyjson") then
55 | package:add("deps", "yyjson")
56 | end
57 |
58 | if package:config("cbor") then
59 | package:add("deps", "tinycbor")
60 | end
61 |
62 | if package:config("flatbuffers") then
63 | package:add("deps", "flatbuffers")
64 | end
65 |
66 | if package:config("msgpack") then
67 | package:add("deps", "msgpack-c")
68 | end
69 |
70 | if package:config("xml") then
71 | package:add("deps", "pugixml")
72 | end
73 |
74 | if package:config("toml") then
75 | package:add("deps", "toml++")
76 | end
77 |
78 | if package:config("yaml") then
79 | package:add("deps", "yaml-cpp")
80 | end
81 |
82 | if package:config("ubjson") then
83 | package:add("deps", "jsoncons")
84 | end
85 |
86 | local version = package:version()
87 | if version then
88 | if version:lt("0.13.0") then
89 | package:set("kind", "library", {headeronly = true})
90 | end
91 |
92 | if version:ge("0.11.1") then
93 | package:add("deps", "ctre", {configs = {cmake = true}})
94 | if version:eq("0.11.1") then
95 | package:add("defines", "REFLECTCPP_NO_BUNDLED_DEPENDENCIES")
96 | end
97 | end
98 | end
99 |
100 | if package:gitref() or version:lt("0.11.1") or version:ge("0.13.0") then
101 | package:add("deps", "cmake")
102 | end
103 | end)
104 |
105 | on_install(function (package)
106 | local version = package:version()
107 | if package:gitref() or version:lt("0.11.1") or version:ge("0.13.0") then
108 | local configs = {
109 | "-DREFLECTCPP_USE_BUNDLED_DEPENDENCIES=OFF",
110 | "-DREFLECTCPP_USE_VCPKG=OFF",
111 | }
112 | table.insert(configs, "-DCMAKE_BUILD_TYPE=" .. (package:is_debug() and "Debug" or "Release"))
113 | table.insert(configs, "-DBUILD_SHARED_LIBS=" .. (package:config("shared") and "ON" or "OFF"))
114 | table.insert(configs, "-DREFLECTCPP_BSON=" .. (package:config("bson") and "ON" or "OFF"))
115 | table.insert(configs, "-DREFLECTCPP_CBOR=" .. (package:config("cbor") and "ON" or "OFF"))
116 | table.insert(configs, "-DREFLECTCPP_FLEXBUFFERS=" .. (package:config("flatbuffers") and "ON" or "OFF"))
117 | table.insert(configs, "-DREFLECTCPP_MSGPACK=" .. (package:config("msgpack") and "ON" or "OFF"))
118 | table.insert(configs, "-DREFLECTCPP_XML=" .. (package:config("xml") and "ON" or "OFF"))
119 | table.insert(configs, "-DREFLECTCPP_TOML=" .. (package:config("toml") and "ON" or "OFF"))
120 | table.insert(configs, "-DREFLECTCPP_UBJSON=" .. (package:config("ubjson") and "ON" or "OFF"))
121 | table.insert(configs, "-DREFLECTCPP_YAML=" .. (package:config("yaml") and "ON" or "OFF"))
122 | import("package.tools.cmake").install(package, configs)
123 |
124 | os.rm("include/thirdparty")
125 | os.cp("include", package:installdir())
126 | else
127 | os.rm("include/thirdparty")
128 | os.cp("include", package:installdir())
129 | end
130 | end)
131 |
132 | on_test(function (package)
133 | assert(package:check_cxxsnippets({test = [[
134 | #include
135 | #include
136 | struct Person {
137 | std::string first_name;
138 | std::string last_name;
139 | int age;
140 | };
141 | const auto homer = Person{.first_name = "Homer",
142 | .last_name = "Simpson",
143 | .age = 45};
144 | void test() {
145 | const std::string json_string = rfl::json::write(homer);
146 | auto homer2 = rfl::json::read(json_string).value();
147 | }
148 | ]]}, {configs = {languages = "c++20"}}))
149 | if package:config("msgpack") then
150 | assert(package:check_cxxsnippets({test = [[
151 | #include
152 | #include
153 | struct Person {
154 | std::string first_name;
155 | std::string last_name;
156 | int age;
157 | };
158 | const auto homer = Person{.first_name = "Homer",
159 | .last_name = "Simpson",
160 | .age = 45};
161 | void test() {
162 | std::vector msgpack_str_vec = rfl::msgpack::write(homer);
163 | auto homer2 = rfl::msgpack::read(msgpack_str_vec).value();
164 | }
165 | ]]}, {configs = {languages = "c++20"}}))
166 | end
167 | end)
--------------------------------------------------------------------------------
/resources/animated-preview1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/std-microblock/breeze-shell/2cd7dda1029b6a414be7ff6ba723f10bf0a75fe4/resources/animated-preview1.webp
--------------------------------------------------------------------------------
/resources/bloom.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/std-microblock/breeze-shell/2cd7dda1029b6a414be7ff6ba723f10bf0a75fe4/resources/bloom.webp
--------------------------------------------------------------------------------
/resources/breeze.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | breeze-shell | MicroBlock
8 |
9 |
10 |
11 |
208 |
209 |
210 |
211 |
212 |
213 |
214 | breeze-shell
215 |
216 |
217 | Bring fluency & dedication back to Windows
218 |
219 |
220 |
233 |
234 |
235 |
236 |
237 |
238 | Flexible
239 |
240 |
241 | Try once or inject consistently, it's your choice.
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 | Extensible
253 |
254 |
255 | Extend your context menu in a few lines of code.
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 | Fluent
266 |
267 |
268 | Beautiful and customizable UI with fluent animation.
269 |
270 |
271 |
272 |
273 |
274 |
275 |
283 |
284 |
285 |
305 |
306 |
307 |
--------------------------------------------------------------------------------
/resources/code.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/std-microblock/breeze-shell/2cd7dda1029b6a414be7ff6ba723f10bf0a75fe4/resources/code.webp
--------------------------------------------------------------------------------
/resources/icon-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/std-microblock/breeze-shell/2cd7dda1029b6a414be7ff6ba723f10bf0a75fe4/resources/icon-small.png
--------------------------------------------------------------------------------
/resources/icon.7z:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/std-microblock/breeze-shell/2cd7dda1029b6a414be7ff6ba723f10bf0a75fe4/resources/icon.7z
--------------------------------------------------------------------------------
/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/std-microblock/breeze-shell/2cd7dda1029b6a414be7ff6ba723f10bf0a75fe4/resources/icon.png
--------------------------------------------------------------------------------
/resources/icon.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/std-microblock/breeze-shell/2cd7dda1029b6a414be7ff6ba723f10bf0a75fe4/resources/icon.webp
--------------------------------------------------------------------------------
/resources/inject-en.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/std-microblock/breeze-shell/2cd7dda1029b6a414be7ff6ba723f10bf0a75fe4/resources/inject-en.webp
--------------------------------------------------------------------------------
/resources/inject.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/std-microblock/breeze-shell/2cd7dda1029b6a414be7ff6ba723f10bf0a75fe4/resources/inject.webp
--------------------------------------------------------------------------------
/resources/preview1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/std-microblock/breeze-shell/2cd7dda1029b6a414be7ff6ba723f10bf0a75fe4/resources/preview1.webp
--------------------------------------------------------------------------------
/resources/schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-06/schema#",
3 | "$ref": "#/definitions/Config",
4 | "definitions": {
5 | "Config": {
6 | "type": "object",
7 | "additionalProperties": false,
8 | "properties": {
9 | "context_menu": {
10 | "$ref": "#/definitions/ContextMenu"
11 | },
12 | "debug_console": {
13 | "type": "boolean"
14 | },
15 | "font_path_main": {
16 | "type": "string"
17 | },
18 | "font_path_fallback": {
19 | "type": "string"
20 | },
21 | "res_string_loader_use_hook": {
22 | "type": "boolean"
23 | },
24 | "avoid_resize_ui": {
25 | "type": "boolean"
26 | },
27 | "plugin_load_order": {
28 | "type": "array",
29 | "items": {}
30 | },
31 | "$schema": {
32 | "type": "string"
33 | },
34 | "default_animation": {
35 | "$ref": "#/definitions/AnimFloatConfig"
36 | }
37 | },
38 | "required": [],
39 | "title": "Config"
40 | },
41 | "ContextMenu": {
42 | "type": "object",
43 | "additionalProperties": false,
44 | "properties": {
45 | "theme": {
46 | "$ref": "#/definitions/Theme"
47 | },
48 | "vsync": {
49 | "type": "boolean"
50 | },
51 | "ignore_owner_draw": {
52 | "type": "boolean"
53 | },
54 | "reverse_if_open_to_up": {
55 | "type": "boolean"
56 | },
57 | "search_large_dwItemData_range": {
58 | "type": "boolean"
59 | },
60 | "position": {
61 | "$ref": "#/definitions/Position"
62 | }
63 | },
64 | "required": [],
65 | "title": "ContextMenu"
66 | },
67 | "Position": {
68 | "type": "object",
69 | "additionalProperties": false,
70 | "properties": {
71 | "padding_vertical": {
72 | "type": "integer"
73 | },
74 | "padding_horizontal": {
75 | "type": "integer"
76 | }
77 | },
78 | "required": [],
79 | "title": "Position"
80 | },
81 | "Theme": {
82 | "type": "object",
83 | "additionalProperties": false,
84 | "properties": {
85 | "use_dwm_if_available": {
86 | "type": "boolean"
87 | },
88 | "background_opacity": {
89 | "type": "integer"
90 | },
91 | "acrylic": {
92 | "type": "boolean"
93 | },
94 | "radius": {
95 | "type": "integer"
96 | },
97 | "font_size": {
98 | "type": "integer"
99 | },
100 | "item_height": {
101 | "type": "integer"
102 | },
103 | "item_gap": {
104 | "type": "integer"
105 | },
106 | "item_radius": {
107 | "type": "integer"
108 | },
109 | "margin": {
110 | "type": "integer"
111 | },
112 | "padding": {
113 | "type": "integer"
114 | },
115 | "text_padding": {
116 | "type": "integer"
117 | },
118 | "icon_padding": {
119 | "type": "integer"
120 | },
121 | "right_icon_padding": {
122 | "type": "integer"
123 | },
124 | "multibutton_line_gap": {
125 | "type": "integer"
126 | },
127 | "acrylic_color_light": {
128 | "type": "string"
129 | },
130 | "acrylic_color_dark": {
131 | "type": "string"
132 | },
133 | "acrylic_opacity": {
134 | "type": "number"
135 | },
136 | "animation": {
137 | "$ref": "#/definitions/Animation"
138 | }
139 | },
140 | "required": [],
141 | "title": "Theme"
142 | },
143 | "Animation": {
144 | "type": "object",
145 | "additionalProperties": false,
146 | "properties": {
147 | "item": {
148 | "$ref": "#/definitions/Item"
149 | },
150 | "main_bg": {
151 | "$ref": "#/definitions/Bg"
152 | },
153 | "submenu_bg": {
154 | "$ref": "#/definitions/Bg"
155 | }
156 | },
157 | "required": [],
158 | "title": "Animation"
159 | },
160 | "Item": {
161 | "type": "object",
162 | "additionalProperties": false,
163 | "properties": {
164 | "opacity": {
165 | "$ref": "#/definitions/AnimFloatConfig"
166 | },
167 | "x": {
168 | "$ref": "#/definitions/AnimFloatConfig"
169 | },
170 | "y": {
171 | "$ref": "#/definitions/AnimFloatConfig"
172 | },
173 | "width": {
174 | "$ref": "#/definitions/AnimFloatConfig"
175 | }
176 | },
177 | "required": [],
178 | "title": "Item"
179 | },
180 | "AnimFloatConfig": {
181 | "type": "object",
182 | "additionalProperties": false,
183 | "properties": {
184 | "duration": {
185 | "type": "integer"
186 | },
187 | "easing": {
188 | "type": "string"
189 | },
190 | "delay_scale": {
191 | "type": "integer"
192 | }
193 | },
194 | "required": [],
195 | "title": "AnimFloatConfig"
196 | },
197 | "Bg": {
198 | "type": "object",
199 | "additionalProperties": false,
200 | "properties": {
201 | "opacity": {
202 | "$ref": "#/definitions/AnimFloatConfig"
203 | },
204 | "x": {
205 | "$ref": "#/definitions/AnimFloatConfig"
206 | },
207 | "y": {
208 | "$ref": "#/definitions/AnimFloatConfig"
209 | },
210 | "w": {
211 | "$ref": "#/definitions/AnimFloatConfig"
212 | },
213 | "h": {
214 | "$ref": "#/definitions/AnimFloatConfig"
215 | }
216 | },
217 | "required": [],
218 | "title": "Bg"
219 | }
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/scripts/bindgen/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/scripts/bindgen/c-type-parser.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface CTypeNode {
3 | function: boolean;
4 | template: boolean;
5 | type: string;
6 | argsTemplate: CTypeNode[];
7 | argsFunc: CTypeNode[];
8 | }
9 |
10 | export class CTypeParser {
11 | tokens: string[] = [];
12 | cursor = 0;
13 |
14 | lex(str: string) {
15 | this.tokens = []
16 | let current = ''
17 |
18 | const BREAK_TOKENS = [' ', '(', ')', ',', '<', '>'];
19 |
20 | for (let i = 0; i < str.length; i++) {
21 | const c = str[i];
22 | if (BREAK_TOKENS.includes(c)) {
23 | if (current.length > 0) {
24 | this.tokens.push(current);
25 | current = '';
26 | }
27 | if (c !== ' ') {
28 | this.tokens.push(c);
29 | }
30 | } else {
31 | current += c;
32 | }
33 | }
34 |
35 | if (current.length > 0) {
36 | this.tokens.push(current);
37 | }
38 | }
39 |
40 | parse(str: string | null = null) {
41 | if (str) {
42 | this.lex(str)
43 | this.cursor = 0
44 | }
45 |
46 | const type: CTypeNode = {
47 | argsFunc: [],
48 | argsTemplate: [],
49 | function: false,
50 | template: false,
51 | type: '',
52 | }
53 |
54 | do {
55 | const parseCommaList = (arr) => {
56 | do {
57 | const res = this.parse();
58 | if (res)
59 | arr.push(res)
60 | } while (this.eat(','));
61 | }
62 |
63 | if (this.eat('(')) {
64 | type.function = true;
65 | if (!this.next(')'))
66 | parseCommaList(type.argsFunc);
67 | this.eat(')', true)
68 | } else if (this.eat('<')) {
69 | type.template = true;
70 | if (!this.next('>'))
71 | parseCommaList(type.argsTemplate)
72 | this.eat('>', true)
73 | } else {
74 | type.type = this.tokens[this.cursor]
75 | this.cursor++;
76 | }
77 |
78 | if (this.next('(') || this.next('<')) {
79 | continue;
80 | }
81 |
82 | break;
83 | } while (true);
84 |
85 | return type
86 | }
87 |
88 | eat(token, force = false) {
89 | if (this.next(token)) {
90 | this.cursor++;
91 | return true;
92 | } else {
93 | if (force) throw new Error(`Excepted: ${token}, found ${this.next()} in ${this.tokens.join(' ')
94 | }`)
95 | }
96 | return false
97 | }
98 |
99 | next(token: string | null = null) {
100 | if (this.tokens[this.cursor] === token || !token) {
101 | return this.tokens[this.cursor];
102 | }
103 | return false
104 | }
105 |
106 | formatToC(node: CTypeNode) {
107 | let str = node.type;
108 | if (node.template) {
109 | str += '<' + node.argsTemplate.map(a => this.formatToC(a)).join(', ') + '>'
110 | }
111 | if (node.function) {
112 | str += '(' + node.argsFunc.map(a => this.formatToC(a)).join(', ') + ')'
113 | }
114 | return str
115 | }
116 |
117 | formatToTypeScript(node: CTypeNode) {
118 | node.type = node.type.split('::').pop() ?? node.type
119 | const typeMap = {
120 | 'int': 'number',
121 | 'float': 'number',
122 | 'double': 'number',
123 | 'string': 'string',
124 | 'vector': 'Array',
125 | 'bool': 'boolean',
126 | }
127 |
128 | let tsBasicType = (typeMap[node.type] ?? node.type) + (node.template ? '<' + node.argsTemplate.map(a => this.formatToTypeScript(a)).join(', ') + '>' : '')
129 |
130 | const ignoreTypes = ['variant', 'shared_ptr', 'function']
131 | if (
132 | ignoreTypes.includes(node.type)
133 | ) {
134 | tsBasicType = node.argsTemplate.map(a => this.formatToTypeScript(a)).join(' | ')
135 | } else if (node.type === 'optional') {
136 | tsBasicType = `${this.formatToTypeScript(node.argsTemplate[0])} | undefined`
137 | }
138 |
139 | if (node.function) {
140 | return `((${node.argsFunc.map(a => this.formatToTypeScript(a)).map((v, i) => `arg${i + 1}: ${v}`).join(', ')}) => ${tsBasicType})`
141 | }
142 |
143 | return tsBasicType;
144 | }
145 | }
146 |
147 | export const cTypeToTypeScript = (str: string) => {
148 | const parser = new CTypeParser();
149 | parser.lex(str);
150 | const res = parser.parse()
151 | return parser.formatToTypeScript(res);
152 | }
153 |
154 | const parser = new CTypeParser();
155 |
156 | const res = parser.parse('std::function(std::function)')
157 | console.log(JSON.stringify(res, null, 2), parser.formatToTypeScript(res))
--------------------------------------------------------------------------------
/scripts/bindgen/clang-ast.d.ts:
--------------------------------------------------------------------------------
1 | export interface inner {
2 | id?: string;
3 | kind?: string;
4 | loc?: loc;
5 | range?: range;
6 | inner?: inner[];
7 | isImplicit?: boolean;
8 | name?: string;
9 | tagUsed?: string;
10 | implicit?: boolean;
11 | type?: type;
12 | decl?: decl;
13 | language?: string;
14 | hasBraces?: boolean;
15 | isReferenced?: boolean;
16 | previousDecl?: string;
17 | mangledName?: string;
18 | variadic?: boolean;
19 | inherited?: boolean;
20 | storageClass?: string;
21 | depth?: number;
22 | index?: number;
23 | completeDefinition?: boolean;
24 | definitionData?: definitionData;
25 | fixedUnderlyingType?: fixedUnderlyingType;
26 | valueCategory?: string;
27 | value?: boolean | string | number;
28 | isDependent?: boolean;
29 | isInstantiationDependent?: boolean;
30 | isPostfix?: boolean;
31 | opcode?: string;
32 | canOverflow?: boolean;
33 | isUsed?: boolean;
34 | ownedTagDecl?: ownedTagDecl;
35 | parentDeclContextId?: string;
36 | scopedEnumTag?: string;
37 | originalNamespace?: originalNamespace;
38 | constexpr?: boolean;
39 | explicitlyDefaulted?: string;
40 | inline?: boolean;
41 | visibility?: string;
42 | castKind?: string;
43 | referencedDecl?: referencedDecl;
44 | cc?: string;
45 | qualifiers?: string;
46 | access?: string;
47 | explicitlyDeleted?: boolean;
48 | message?: string;
49 | init?: string;
50 | isPartOfExplicitCast?: boolean;
51 | target?: target;
52 | argType?: argType;
53 | isArrow?: boolean;
54 | referencedMemberDecl?: string;
55 | templateName?: string;
56 | isExpr?: boolean;
57 | isAlias?: boolean;
58 | defaultArg?: defaultArg;
59 | bases?: bases[];
60 | isParameterPack?: boolean;
61 | isPack?: boolean;
62 | containsUnexpandedPack?: boolean;
63 | usesADL?: boolean;
64 | lookups?: lookups[] | any[];
65 | nonOdrUseReason?: string;
66 | foundReferencedDecl?: foundReferencedDecl;
67 | numElements?: number;
68 | hasElse?: boolean;
69 | computeLHSType?: computeLHSType;
70 | computeResultType?: computeResultType;
71 | storageDuration?: string;
72 | size?: number;
73 | qualifier?: string;
74 | isFunction?: boolean;
75 | const?: boolean;
76 | volatile?: boolean;
77 | refQualifier?: string;
78 | exceptionSpec?: string;
79 | isData?: boolean;
80 | transformKind?: string;
81 | member?: string;
82 | isConstexpr?: boolean;
83 | hasInClassInitializer?: boolean;
84 | list?: boolean;
85 | boundToLValueRef?: boolean;
86 | anyInit?: anyInit;
87 | ctorType?: ctorType;
88 | zeroing?: boolean;
89 | hadMultipleCandidates?: boolean;
90 | constructionKind?: string;
91 | virtual?: boolean;
92 | baseInit?: baseInit;
93 | path?: path[];
94 | nrvo?: boolean;
95 | conversionFunc?: conversionFunc;
96 | cleanupsHaveSideEffects?: boolean;
97 | temp?: string;
98 | dtor?: dtor;
99 | isGlobal?: boolean;
100 | isPlacement?: boolean;
101 | isInline?: boolean;
102 | immediate?: boolean;
103 | adl?: boolean;
104 | ['inherited from']?: InheritedFrom;
105 | hasInit?: boolean;
106 | initStyle?: string;
107 | operatorNewDecl?: operatorNewDecl;
108 | operatorDeleteDecl?: operatorDeleteDecl;
109 | pack_index?: number;
110 | mutable?: boolean;
111 | pure?: boolean;
112 | array_filler?: array_filler[];
113 | isInvalid?: boolean;
114 | }
115 |
116 | export interface loc {
117 | offset?: number;
118 | file?: string;
119 | line?: number;
120 | col?: number;
121 | tokLen?: number;
122 | includedFrom?: includedFrom;
123 | spellingLoc?: spellingLoc;
124 | expansionLoc?: expansionLoc;
125 | }
126 |
127 | export interface range {
128 | begin: begin;
129 | end: end;
130 | }
131 |
132 | export interface begin {
133 | offset?: number;
134 | col?: number;
135 | tokLen?: number;
136 | includedFrom?: includedFrom;
137 | line?: number;
138 | spellingLoc?: spellingLoc;
139 | expansionLoc?: expansionLoc;
140 | file?: string;
141 | }
142 |
143 | export interface end {
144 | offset?: number;
145 | line?: number;
146 | col?: number;
147 | tokLen?: number;
148 | includedFrom?: includedFrom;
149 | spellingLoc?: spellingLoc;
150 | expansionLoc?: expansionLoc;
151 | }
152 |
153 | export interface type {
154 | qualType: string;
155 | desugaredQualType?: string;
156 | typeAliasDeclId?: string;
157 | }
158 |
159 | export interface decl {
160 | id: string;
161 | kind?: string;
162 | name?: string;
163 | }
164 |
165 | export interface includedFrom {
166 | file: string;
167 | }
168 |
169 | export interface definitionData {
170 | canConstDefaultInit?: boolean;
171 | copyAssign: copyAssign;
172 | copyCtor: copyCtor;
173 | defaultCtor: defaultCtor;
174 | dtor: dtor;
175 | hasConstexprNonCopyMoveConstructor?: boolean;
176 | isAggregate?: boolean;
177 | isEmpty?: boolean;
178 | isLiteral?: boolean;
179 | isPOD?: boolean;
180 | isStandardLayout?: boolean;
181 | isTrivial?: boolean;
182 | isTriviallyCopyable?: boolean;
183 | moveAssign: moveAssign;
184 | moveCtor: moveCtor;
185 | canPassInRegisters?: boolean;
186 | hasUserDeclaredConstructor?: boolean;
187 | hasVariantMembers?: boolean;
188 | isPolymorphic?: boolean;
189 | hasMutableFields?: boolean;
190 | isGenericLambda?: boolean;
191 | isLambda?: boolean;
192 | isAbstract?: boolean;
193 | }
194 |
195 | export interface copyAssign {
196 | hasConstParam: boolean;
197 | implicitHasConstParam: boolean;
198 | needsImplicit?: boolean;
199 | simple?: boolean;
200 | trivial?: boolean;
201 | userDeclared?: boolean;
202 | needsOverloadResolution?: boolean;
203 | nonTrivial?: boolean;
204 | }
205 |
206 | export interface copyCtor {
207 | hasConstParam: boolean;
208 | implicitHasConstParam: boolean;
209 | needsImplicit?: boolean;
210 | simple?: boolean;
211 | trivial?: boolean;
212 | userDeclared?: boolean;
213 | needsOverloadResolution?: boolean;
214 | nonTrivial?: boolean;
215 | }
216 |
217 | export interface defaultCtor {
218 | defaultedIsConstexpr?: boolean;
219 | exists?: boolean;
220 | isConstexpr?: boolean;
221 | needsImplicit?: boolean;
222 | trivial?: boolean;
223 | nonTrivial?: boolean;
224 | userProvided?: boolean;
225 | }
226 |
227 | export interface dtor {
228 | irrelevant?: boolean;
229 | needsImplicit?: boolean;
230 | simple?: boolean;
231 | trivial?: boolean;
232 | nonTrivial?: boolean;
233 | userDeclared?: boolean;
234 | needsOverloadResolution?: boolean;
235 | id?: string;
236 | kind?: string;
237 | name?: string;
238 | type?: type;
239 | }
240 |
241 | export interface moveAssign {
242 | exists?: boolean;
243 | needsImplicit?: boolean;
244 | simple?: boolean;
245 | trivial?: boolean;
246 | userDeclared?: boolean;
247 | needsOverloadResolution?: boolean;
248 | nonTrivial?: boolean;
249 | }
250 |
251 | export interface moveCtor {
252 | exists?: boolean;
253 | needsImplicit?: boolean;
254 | simple?: boolean;
255 | trivial?: boolean;
256 | userDeclared?: boolean;
257 | needsOverloadResolution?: boolean;
258 | nonTrivial?: boolean;
259 | }
260 |
261 | export interface fixedUnderlyingType {
262 | qualType: string;
263 | desugaredQualType?: string;
264 | typeAliasDeclId?: string;
265 | }
266 |
267 | export interface spellingLoc {
268 | offset: number;
269 | file?: string;
270 | line?: number;
271 | col: number;
272 | tokLen: number;
273 | includedFrom?: includedFrom;
274 | presumedLine?: number;
275 | }
276 |
277 | export interface expansionLoc {
278 | offset: number;
279 | line?: number;
280 | col: number;
281 | tokLen: number;
282 | includedFrom: includedFrom;
283 | file?: string;
284 | isMacroArgExpansion?: boolean;
285 | }
286 |
287 | export interface ownedTagDecl {
288 | id: string;
289 | kind: string;
290 | name?: string;
291 | }
292 |
293 | export interface originalNamespace {
294 | id: string;
295 | kind: string;
296 | name: string;
297 | }
298 |
299 | export interface referencedDecl {
300 | id: string;
301 | kind: string;
302 | name?: string;
303 | type: type;
304 | }
305 |
306 | export interface target {
307 | id: string;
308 | kind: string;
309 | name: string;
310 | type?: type;
311 | }
312 |
313 | export interface argType {
314 | desugaredQualType?: string;
315 | qualType: string;
316 | typeAliasDeclId?: string;
317 | }
318 |
319 | export interface defaultArg {
320 | kind: string;
321 | type?: type;
322 | isExpr?: boolean;
323 | ['inherited from']?: InheritedFrom;
324 | }
325 |
326 | export interface bases {
327 | access: string;
328 | type: type;
329 | writtenAccess: string;
330 | }
331 |
332 | export interface lookups {
333 | id: string;
334 | kind: string;
335 | name: string;
336 | type?: type;
337 | }
338 |
339 | export interface foundReferencedDecl {
340 | id: string;
341 | kind: string;
342 | name: string;
343 | }
344 |
345 | export interface computeLHSType {
346 | qualType: string;
347 | desugaredQualType?: string;
348 | typeAliasDeclId?: string;
349 | }
350 |
351 | export interface computeResultType {
352 | qualType: string;
353 | desugaredQualType?: string;
354 | typeAliasDeclId?: string;
355 | }
356 |
357 | export interface anyInit {
358 | id: string;
359 | kind: string;
360 | name: string;
361 | type: type;
362 | }
363 |
364 | export interface ctorType {
365 | qualType: string;
366 | desugaredQualType?: string;
367 | }
368 |
369 | export interface baseInit {
370 | desugaredQualType?: string;
371 | qualType: string;
372 | typeAliasDeclId?: string;
373 | }
374 |
375 | export interface path {
376 | name: string;
377 | }
378 |
379 | export interface conversionFunc {
380 | id: string;
381 | kind: string;
382 | name: string;
383 | type: type;
384 | }
385 |
386 | export interface InheritedFrom {
387 | id: string;
388 | kind: string;
389 | name?: string;
390 | type?: type;
391 | }
392 |
393 | export interface operatorNewDecl {
394 | id: string;
395 | kind: string;
396 | name: string;
397 | type: type;
398 | }
399 |
400 | export interface operatorDeleteDecl {
401 | id: string;
402 | kind: string;
403 | name: string;
404 | type: type;
405 | }
406 |
407 | export interface array_filler {
408 | id: string;
409 | kind: string;
410 | range: range;
411 | type: type;
412 | valueCategory: string;
413 | }
414 |
415 |
416 |
417 | export type ClangASTD = inner;
--------------------------------------------------------------------------------
/scripts/bindgen/generate-bindings.bat:
--------------------------------------------------------------------------------
1 | cd scripts
2 | cd bindgen
3 | clang++ -Xclang -ast-dump=json -fsyntax-only -Xclang -ast-dump-filter=mb_shell::js -std=c++2c ..\..\src\shell\script\binding_types.h > .\ast.json
4 | yarn && yarn gen
5 | echo Done
--------------------------------------------------------------------------------
/scripts/bindgen/index.ts:
--------------------------------------------------------------------------------
1 | import { ClangASTD } from "./clang-ast";
2 | import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3 | import { join } from "node:path";
4 | import { cTypeToTypeScript } from "./c-type-parser";
5 |
6 | const ast = JSON.parse(readFileSync(join(__dirname, "ast.json"), "utf-8")) as ClangASTD;
7 |
8 | const targetFile = 'binding_types.h'
9 | const outputFile = 'binding_qjs.h'
10 | // filter out loc.file contains targetFile
11 |
12 | const origFile = readFileSync(join(__dirname, '../../src/shell/script/binding_types.h'), 'utf-8').split('\n').map(v => v.trim())
13 |
14 | let binding =
15 | `// This file is generated by scripts/bindgen/index.ts
16 | // Do not modify this file manually!
17 |
18 | #pragma once
19 | #include "binding_types.h"
20 | #include "quickjs.h"
21 | #include "quickjspp.hpp"
22 |
23 | template
24 | struct js_bind {
25 | static void bind(qjs::Context::Module &mod) {}
26 | };
27 | `
28 |
29 | let typescriptDef = `// This file is generated by scripts/bindgen/index.ts
30 | // Do not modify this file manually!
31 |
32 | declare module 'mshell' {
33 |
34 | `
35 |
36 | const parseFunctionQualType = (type: string) => {
37 | // std::variant (std::string, std::string)
38 | // std::function (int, std::string)
39 |
40 | enum State {
41 | ReturnType,
42 | Args,
43 | Done
44 | }
45 |
46 | let state = State.ReturnType;
47 | let returnType = '';
48 | let currentArg = '';
49 | let args: string[] = [];
50 | let depth = 0;
51 | let angleBracketDepth = 0;
52 |
53 | for (let i = 0; i < type.length; i++) {
54 | const char = type[i];
55 |
56 | if (char === '<') {
57 | angleBracketDepth++;
58 | } else if (char === '>') {
59 | angleBracketDepth--;
60 | }
61 |
62 | switch (state) {
63 | case State.ReturnType:
64 | if (char === '(' && angleBracketDepth === 0) {
65 | state = State.Args;
66 | returnType = returnType.trim();
67 | } else {
68 | returnType += char;
69 | }
70 | break;
71 |
72 | case State.Args:
73 | if (char === '(') depth++;
74 | if (char === ')') {
75 | if (depth === 0 && angleBracketDepth === 0) {
76 | if (currentArg.trim()) args.push(currentArg.trim());
77 | state = State.Done;
78 | break;
79 | }
80 | depth--;
81 | }
82 | if (char === ',' && depth === 0 && angleBracketDepth === 0) {
83 | args.push(currentArg.trim());
84 | currentArg = '';
85 | } else {
86 | currentArg += char;
87 | }
88 | break;
89 | }
90 | }
91 |
92 | if (state !== State.Done) {
93 | throw new Error('Invalid function type');
94 | }
95 |
96 | return {
97 | returnType,
98 | args
99 | };
100 | }
101 |
102 | if (ast.kind !== 'NamespaceDecl') {
103 | throw new Error('Root node is not a NamespaceDecl');
104 | }
105 |
106 | let currentNamespace = 'mb_shell::js'
107 |
108 | const generateForRecordDecl = (node_struct: ClangASTD) => {
109 | if (node_struct.kind !== 'CXXRecordDecl') {
110 | throw new Error('Node is not a RecordDecl');
111 | }
112 |
113 | const structName = node_struct.name;
114 |
115 | const fields: {
116 | name: string;
117 | type: string;
118 | comment?: string;
119 | }[] = [];
120 |
121 | const methods: {
122 | name: string;
123 | returnType: string;
124 | args: string[];
125 | static: boolean;
126 | comment?: string;
127 | argNames?: string[];
128 | }[] = [];
129 |
130 | if (!node_struct.inner) return;
131 |
132 | for (const node of node_struct.inner) {
133 | if (node.name?.startsWith('$')) continue;
134 |
135 | const lineNum = node.loc?.line;
136 | // find comment above
137 | let comment = '';
138 |
139 | if (lineNum) {
140 | let rangeCommentCnt = 0;
141 | for (let i = lineNum - 2; i >= 0; i--) {
142 | const line = origFile[i];
143 | if (!line) continue;
144 | if (line.startsWith('//')) {
145 | comment = line.substring(2) + '\n' + comment;
146 | continue;
147 | }
148 | if (line.startsWith('/*')) {
149 | rangeCommentCnt++;
150 | continue;
151 | }
152 | if (line.endsWith('*/')) {
153 | rangeCommentCnt--;
154 | continue;
155 | }
156 |
157 | if (rangeCommentCnt === 0) break;
158 | else comment = line + '\n' + comment;
159 | }
160 | }
161 |
162 | if (node.kind === 'FieldDecl') {
163 | fields.push({
164 | name: node.name!,
165 | type: node.type!.qualType,
166 | comment: comment.length > 0 ? comment : undefined
167 | });
168 | }
169 |
170 | if (node.kind === 'CXXMethodDecl') {
171 | const parsed = parseFunctionQualType(node.type!.qualType);
172 |
173 | if (
174 | ['operator='].includes(node.name!)
175 | ) continue;
176 |
177 | const argNames: string[] = [];
178 | if (node.inner) {
179 | for (const arg of node.inner) {
180 | if (arg.kind === 'ParmVarDecl') {
181 | argNames.push(arg.name!);
182 | }
183 | }
184 | }
185 |
186 | methods.push({
187 | name: node.name!,
188 | returnType: parsed.returnType,
189 | args: parsed.args,
190 | static: node.storageClass === 'static',
191 | comment: comment.length > 0 ? comment : undefined,
192 | argNames
193 | });
194 | }
195 | }
196 |
197 | // console.log({
198 | // structName, fields, methods
199 | // });
200 |
201 | binding += `
202 | template <> struct qjs::js_traits<${currentNamespace}::${structName}> {
203 | static ${currentNamespace}::${structName} unwrap(JSContext *ctx, JSValueConst v) {
204 | ${currentNamespace}::${structName} obj;
205 | `;
206 |
207 | for (const field of fields) {
208 | binding += `
209 | obj.${field.name} = js_traits<${field.type}>::unwrap(ctx, JS_GetPropertyStr(ctx, v, "${field.name}"));
210 | `;
211 | }
212 |
213 | binding += `
214 | return obj;
215 | }
216 |
217 | static JSValue wrap(JSContext *ctx, const ${currentNamespace}::${structName} &val) noexcept {
218 | JSValue obj = JS_NewObject(ctx);
219 | `;
220 |
221 | for (const field of fields) {
222 | binding += `
223 | JS_SetPropertyStr(ctx, obj, "${field.name}", js_traits<${field.type}>::wrap(ctx, val.${field.name}));
224 | `;
225 | }
226 |
227 | binding += `
228 | return obj;
229 | }
230 | };`;
231 |
232 | /**
233 | *
234 | * module.class_("MyClass")
235 | .constructor<>()
236 | .constructor>("MyClassA")
237 | .fun<&MyClass::member_variable>("member_variable")
238 | .fun<&MyClass::member_function>("member_function")
239 | .static_fun<&MyClass::static_function>("static_function")
240 | */
241 | binding += `
242 | template<> struct js_bind<${currentNamespace}::${structName}> {
243 | static void bind(qjs::Context::Module &mod) {
244 | mod.class_<${currentNamespace}::${structName}>("${structName}")
245 | .constructor<>()`;
246 | for (const method of methods) {
247 | if (method.static) {
248 | binding += `
249 | .static_fun<&${currentNamespace}::${structName}::${method.name}>("${method.name}")`;
250 | } else {
251 | binding += `
252 | .fun<&${currentNamespace}::${structName}::${method.name}>("${method.name}")`;
253 | }
254 | }
255 |
256 | for (const field of fields) {
257 | binding += `
258 | .fun<&${currentNamespace}::${structName}::${field.name}>("${field.name}")`;
259 | }
260 |
261 | binding += `
262 | ;
263 | }
264 |
265 | };
266 | `;
267 |
268 | typescriptDef += `
269 | export class ${structName} {
270 | \t${fields.map(field => {
271 | let fieldDef = `${field.name}${field.type.startsWith('std::optional') ? '?' : ''
272 | }: ${cTypeToTypeScript(field.type)}`;
273 |
274 | if (field.comment) {
275 | fieldDef = `
276 | /**
277 | * ${field.comment.trim().split('\n').join('\n * ')}
278 | */
279 | ${fieldDef}
280 | `
281 | }
282 |
283 | return fieldDef;
284 | }).join('\n\t')}
285 | \t${methods.map(method => {
286 | let methodDef = `${method.static ? 'static ' : ''}${method.name}: ${cTypeToTypeScript(`${method.returnType}(${method.args.join(', ')})`)}`
287 |
288 | // if (method.comment) {
289 | // methodDef = method.comment.trim().split('\n').map(v => `// ${v}`).join('\n\t')
290 | // + '\n\t' + methodDef;
291 | // }
292 |
293 | // if (method.argNames) {
294 | // methodDef = '// Args: ' + method.argNames.join(', ') + '\n\t' + methodDef;
295 | // }
296 | let comments = '';
297 | if (method.comment) {
298 | comments += method.comment.trim();
299 | }
300 | methodDef = `
301 | /**
302 | * ${comments.split('\n').join('\n * ')}
303 | * @param ${method.args.map((arg , i) => `${method.argNames![i]}: ${cTypeToTypeScript(arg)}`).join(', ')}
304 | * @returns ${cTypeToTypeScript(method.returnType)}
305 | */
306 | ${methodDef}
307 | `
308 | return methodDef;
309 | }).join('\n\t')}
310 | }
311 | `;
312 |
313 | }
314 |
315 | const structNames: string[] = []
316 | for (const node of ast.inner!) {
317 | if (node.kind === 'CXXRecordDecl') {
318 | generateForRecordDecl(node);
319 | if (node.name && node.inner)
320 | structNames.push(node.name);
321 | }
322 | }
323 |
324 | binding += `
325 | inline void bindAll(qjs::Context::Module &mod) {
326 | `
327 |
328 | for (const structName of structNames) {
329 | binding += `
330 | js_bind<${currentNamespace}::${structName}>::bind(mod);
331 | `
332 | }
333 |
334 | binding += `
335 | }
336 | `
337 |
338 | typescriptDef += `
339 | }
340 | `
341 |
342 | const paths = [
343 | join(__dirname, 'src/shell/script'),
344 | join(__dirname, '../src/shell/script'),
345 | join(__dirname, '../../src/shell/script')
346 | ]
347 |
348 | typescriptDef += readFileSync(join(__dirname, 'quickjs-types.txt'), 'utf-8');
349 |
350 | for (const path of paths) {
351 | try {
352 | if (existsSync(join(path, targetFile))) {
353 | writeFileSync(join(path, outputFile), binding);
354 | writeFileSync(join(path, 'binding_types.d.ts'), typescriptDef);
355 | break;
356 | }
357 | } catch (e) {
358 | console.error(e);
359 | }
360 | }
361 |
362 | rmSync(join(__dirname, 'ast.json'));
--------------------------------------------------------------------------------
/scripts/bindgen/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
3 | "name": "bindgen",
4 | "version": "1.0.0",
5 | "main": "index.js",
6 | "author": "MicroBlock <66859419+std-microblock@users.noreply.github.com>",
7 | "license": "MIT",
8 | "dependencies": {
9 | "@types/node": "^22.10.5",
10 | "esbuild": "^0.24.2",
11 | "esbuild-register": "^3.6.0"
12 | },
13 | "scripts": {
14 | "gen": "node -r esbuild-register index.ts",
15 | "typegen": "node -r esbuild-register typegen.ts",
16 | "parser-test": "node -r esbuild-register c-type-parser.ts"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/scripts/bindgen/quickjs-types.txt:
--------------------------------------------------------------------------------
1 | declare module "mshell" {
2 | export function println(...args: any[]);
3 | type size_t = number;
4 | type uint8_t = number;
5 | type uint16_t = number;
6 | type uint32_t = number;
7 | type uint64_t = number;
8 | type int8_t = number;
9 | type int16_t = number;
10 | type int32_t = number;
11 | type int64_t = number;
12 | type intptr_t = number;
13 | type uintptr_t = number;
14 | type ssize_t = number;
15 | }
16 |
17 | // helper to access field based on dot path
18 | type FieldPath = K extends keyof T ? T[K] : K extends `${infer K1}.${infer K2}` ? K1 extends keyof T ? FieldPath : never : never;
19 |
20 | declare function plugin(import_meta: { url: string }, default_config?: T): {
21 | i18n: {
22 | define(lang: string, data: { [key: string]: string }): void;
23 | t(key: string): string;
24 | };
25 | set_on_menu(callback: (m:
26 | import('mshell').menu_controller
27 | ) => void): void;
28 | config_directory: string;
29 | config: {
30 | read_config(): void;
31 | write_config(): void;
32 | get(key: K): FieldPath;
33 | set(key: K, value: FieldPath): void;
34 | all(): T;
35 | };
36 | log(...args: any[]): void;
37 | };
38 |
39 | declare type ShellPluginHelper = ReturnType;
--------------------------------------------------------------------------------
/scripts/bindgen/typegen.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync, writeFileSync } from "fs";
2 | import { types } from "util";
3 |
4 | type Type = {
5 | isArr: boolean;
6 | type: 'string' | 'number' | 'boolean' | string;
7 | }
8 | type TypeMap = {
9 | [typeName: string]: {
10 | [fieldName: string]: {
11 | types: Type[];
12 | isOptional: boolean;
13 | }
14 | };
15 | };
16 |
17 | function generateInterfaces(json: any): string {
18 | const typeMap: TypeMap = {};
19 |
20 | const traverse = (obj: any, k = "Main") => {
21 | typeMap[k] ??= {
22 | };
23 |
24 | const typeCur = typeMap[k];
25 |
26 | for (const key in typeCur) {
27 | if (!obj[key]) {
28 | typeCur[key].isOptional = true;
29 | }
30 | }
31 |
32 | for (const key in obj) {
33 | const value = obj[key];
34 |
35 | typeCur[key] ??= {
36 | types: [],
37 | isOptional: false
38 | };
39 |
40 | const type = typeCur[key];
41 |
42 | const pushIfNotExists = (t: Type) => {
43 | if (type.types.findIndex(x => x.type === t.type && x.isArr === t.isArr) === -1) {
44 | type.types.push(t);
45 | }
46 | }
47 |
48 | if (Array.isArray(value)) {
49 | if (value.length === 0) {
50 | pushIfNotExists({
51 | isArr: true,
52 | type: 'any'
53 | });
54 | } else {
55 | for (const val of value) {
56 | if (typeof val === 'object') {
57 | traverse(val, key);
58 | pushIfNotExists({
59 | isArr: true,
60 | type: key
61 | });
62 | } else {
63 | pushIfNotExists({
64 | isArr: true,
65 | type: typeof val
66 | });
67 | }
68 | }
69 | }
70 | } else if (typeof value === 'object') {
71 | pushIfNotExists({
72 | isArr: false,
73 | type: key
74 | });
75 | traverse(value, key);
76 | } else {
77 | pushIfNotExists({
78 | isArr: false,
79 | type: typeof value
80 | });
81 | }
82 | }
83 | }
84 |
85 | traverse(json);
86 |
87 | let ts = '';
88 |
89 | const escapeForType = (type: string) => {
90 | if (!type.includes(' ')) return type;
91 | return type.split(' ').map(x => x[0].toUpperCase() + x.slice(1)).join('');
92 | }
93 |
94 | const escapeForField = (field: string) => {
95 | const keywords = ['delete', 'export', 'import', 'in', 'instanceof', 'new', 'typeof', 'void', 'yield'];
96 | if (keywords.includes(field) || field.includes('-') || field.includes(' ')) {
97 | return `['${field}']`;
98 | }
99 |
100 | return field;
101 | }
102 |
103 | for (const type in typeMap) {
104 | ts += `export interface ${escapeForType(type)} {\n`;
105 |
106 | for (const field in typeMap[type]) {
107 | const { types, isOptional } = typeMap[type][field];
108 |
109 | ts += ` ${escapeForField(field)}${isOptional ? '?' : ''}: ${types.map(x => `${escapeForType(x.type)}${x.isArr ? '[]' : ''}`).join(' | ')};\n`;
110 | }
111 |
112 | ts += '}\n\n';
113 | }
114 |
115 | return ts;
116 | }
117 |
118 | const json = JSON.parse(
119 | readFileSync('ast.json', 'utf-8')
120 | )
121 |
122 | const ts = generateInterfaces({
123 | inner: json
124 | });
125 |
126 | writeFileSync('ast.d.ts', ts);
--------------------------------------------------------------------------------
/scripts/bindgen/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@esbuild/aix-ppc64@0.24.2":
6 | version "0.24.2"
7 | resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz#38848d3e25afe842a7943643cbcd387cc6e13461"
8 | integrity sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==
9 |
10 | "@esbuild/android-arm64@0.24.2":
11 | version "0.24.2"
12 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz#f592957ae8b5643129fa889c79e69cd8669bb894"
13 | integrity sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==
14 |
15 | "@esbuild/android-arm@0.24.2":
16 | version "0.24.2"
17 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.2.tgz#72d8a2063aa630308af486a7e5cbcd1e134335b3"
18 | integrity sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==
19 |
20 | "@esbuild/android-x64@0.24.2":
21 | version "0.24.2"
22 | resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.2.tgz#9a7713504d5f04792f33be9c197a882b2d88febb"
23 | integrity sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==
24 |
25 | "@esbuild/darwin-arm64@0.24.2":
26 | version "0.24.2"
27 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz#02ae04ad8ebffd6e2ea096181b3366816b2b5936"
28 | integrity sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==
29 |
30 | "@esbuild/darwin-x64@0.24.2":
31 | version "0.24.2"
32 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz#9ec312bc29c60e1b6cecadc82bd504d8adaa19e9"
33 | integrity sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==
34 |
35 | "@esbuild/freebsd-arm64@0.24.2":
36 | version "0.24.2"
37 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz#5e82f44cb4906d6aebf24497d6a068cfc152fa00"
38 | integrity sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==
39 |
40 | "@esbuild/freebsd-x64@0.24.2":
41 | version "0.24.2"
42 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz#3fb1ce92f276168b75074b4e51aa0d8141ecce7f"
43 | integrity sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==
44 |
45 | "@esbuild/linux-arm64@0.24.2":
46 | version "0.24.2"
47 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz#856b632d79eb80aec0864381efd29de8fd0b1f43"
48 | integrity sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==
49 |
50 | "@esbuild/linux-arm@0.24.2":
51 | version "0.24.2"
52 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz#c846b4694dc5a75d1444f52257ccc5659021b736"
53 | integrity sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==
54 |
55 | "@esbuild/linux-ia32@0.24.2":
56 | version "0.24.2"
57 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz#f8a16615a78826ccbb6566fab9a9606cfd4a37d5"
58 | integrity sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==
59 |
60 | "@esbuild/linux-loong64@0.24.2":
61 | version "0.24.2"
62 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz#1c451538c765bf14913512c76ed8a351e18b09fc"
63 | integrity sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==
64 |
65 | "@esbuild/linux-mips64el@0.24.2":
66 | version "0.24.2"
67 | resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz#0846edeefbc3d8d50645c51869cc64401d9239cb"
68 | integrity sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==
69 |
70 | "@esbuild/linux-ppc64@0.24.2":
71 | version "0.24.2"
72 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz#8e3fc54505671d193337a36dfd4c1a23b8a41412"
73 | integrity sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==
74 |
75 | "@esbuild/linux-riscv64@0.24.2":
76 | version "0.24.2"
77 | resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz#6a1e92096d5e68f7bb10a0d64bb5b6d1daf9a694"
78 | integrity sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==
79 |
80 | "@esbuild/linux-s390x@0.24.2":
81 | version "0.24.2"
82 | resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz#ab18e56e66f7a3c49cb97d337cd0a6fea28a8577"
83 | integrity sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==
84 |
85 | "@esbuild/linux-x64@0.24.2":
86 | version "0.24.2"
87 | resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz#8140c9b40da634d380b0b29c837a0b4267aff38f"
88 | integrity sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==
89 |
90 | "@esbuild/netbsd-arm64@0.24.2":
91 | version "0.24.2"
92 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz#65f19161432bafb3981f5f20a7ff45abb2e708e6"
93 | integrity sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==
94 |
95 | "@esbuild/netbsd-x64@0.24.2":
96 | version "0.24.2"
97 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz#7a3a97d77abfd11765a72f1c6f9b18f5396bcc40"
98 | integrity sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==
99 |
100 | "@esbuild/openbsd-arm64@0.24.2":
101 | version "0.24.2"
102 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz#58b00238dd8f123bfff68d3acc53a6ee369af89f"
103 | integrity sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==
104 |
105 | "@esbuild/openbsd-x64@0.24.2":
106 | version "0.24.2"
107 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz#0ac843fda0feb85a93e288842936c21a00a8a205"
108 | integrity sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==
109 |
110 | "@esbuild/sunos-x64@0.24.2":
111 | version "0.24.2"
112 | resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz#8b7aa895e07828d36c422a4404cc2ecf27fb15c6"
113 | integrity sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==
114 |
115 | "@esbuild/win32-arm64@0.24.2":
116 | version "0.24.2"
117 | resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz#c023afb647cabf0c3ed13f0eddfc4f1d61c66a85"
118 | integrity sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==
119 |
120 | "@esbuild/win32-ia32@0.24.2":
121 | version "0.24.2"
122 | resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz#96c356132d2dda990098c8b8b951209c3cd743c2"
123 | integrity sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==
124 |
125 | "@esbuild/win32-x64@0.24.2":
126 | version "0.24.2"
127 | resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz#34aa0b52d0fbb1a654b596acfa595f0c7b77a77b"
128 | integrity sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==
129 |
130 | "@types/node@^22.10.5":
131 | version "22.10.5"
132 | resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.5.tgz#95af89a3fb74a2bb41ef9927f206e6472026e48b"
133 | integrity sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==
134 | dependencies:
135 | undici-types "~6.20.0"
136 |
137 | debug@^4.3.4:
138 | version "4.4.0"
139 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
140 | integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
141 | dependencies:
142 | ms "^2.1.3"
143 |
144 | esbuild-register@^3.6.0:
145 | version "3.6.0"
146 | resolved "https://registry.yarnpkg.com/esbuild-register/-/esbuild-register-3.6.0.tgz#cf270cfa677baebbc0010ac024b823cbf723a36d"
147 | integrity sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==
148 | dependencies:
149 | debug "^4.3.4"
150 |
151 | esbuild@^0.24.2:
152 | version "0.24.2"
153 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.24.2.tgz#b5b55bee7de017bff5fb8a4e3e44f2ebe2c3567d"
154 | integrity sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==
155 | optionalDependencies:
156 | "@esbuild/aix-ppc64" "0.24.2"
157 | "@esbuild/android-arm" "0.24.2"
158 | "@esbuild/android-arm64" "0.24.2"
159 | "@esbuild/android-x64" "0.24.2"
160 | "@esbuild/darwin-arm64" "0.24.2"
161 | "@esbuild/darwin-x64" "0.24.2"
162 | "@esbuild/freebsd-arm64" "0.24.2"
163 | "@esbuild/freebsd-x64" "0.24.2"
164 | "@esbuild/linux-arm" "0.24.2"
165 | "@esbuild/linux-arm64" "0.24.2"
166 | "@esbuild/linux-ia32" "0.24.2"
167 | "@esbuild/linux-loong64" "0.24.2"
168 | "@esbuild/linux-mips64el" "0.24.2"
169 | "@esbuild/linux-ppc64" "0.24.2"
170 | "@esbuild/linux-riscv64" "0.24.2"
171 | "@esbuild/linux-s390x" "0.24.2"
172 | "@esbuild/linux-x64" "0.24.2"
173 | "@esbuild/netbsd-arm64" "0.24.2"
174 | "@esbuild/netbsd-x64" "0.24.2"
175 | "@esbuild/openbsd-arm64" "0.24.2"
176 | "@esbuild/openbsd-x64" "0.24.2"
177 | "@esbuild/sunos-x64" "0.24.2"
178 | "@esbuild/win32-arm64" "0.24.2"
179 | "@esbuild/win32-ia32" "0.24.2"
180 | "@esbuild/win32-x64" "0.24.2"
181 |
182 | ms@^2.1.3:
183 | version "2.1.3"
184 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
185 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
186 |
187 | undici-types@~6.20.0:
188 | version "6.20.0"
189 | resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
190 | integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
191 |
--------------------------------------------------------------------------------
/scripts/debug.cmd:
--------------------------------------------------------------------------------
1 | set LLDB_USE_NATIVE_PDB_READER=1
2 | xmake b shell_test
3 | xmake r -d shell_test
--------------------------------------------------------------------------------
/scripts/rebuild.ps1:
--------------------------------------------------------------------------------
1 | $pids = (Get-WmiObject Win32_Process -Filter "name = 'explorer.exe'" | where { $_.CommandLine -like '*/factory,{75dff2b7-6936-4c06-a8bb-676a7b00b24b}*' }).ProcessId
2 | foreach ($pidx in $pids) {
3 | Stop-Process -Id $pidx -Force
4 | }
5 |
6 | xmake b --yes inject
7 | xmake b --yes shell && xmake r inject new
--------------------------------------------------------------------------------
/scripts/right_click.ahk:
--------------------------------------------------------------------------------
1 | Sleep, 1000
2 | counter := 0
3 | #SingleInstance force
4 |
5 | Gui, Add, Edit, vLog w400 h400,
6 | Gui, Show, w400 h400
7 | stop := false
8 |
9 | Loop
10 | {
11 | Send {RButton}
12 | Sleep, 100
13 | Send {Esc}
14 | Sleep, 100
15 | counter := counter + 1
16 | GuiControl,, Log, %counter%
17 |
18 | if (stop)
19 | {
20 | break
21 | }
22 | }
23 |
24 | F10::
25 | stop := true
26 |
--------------------------------------------------------------------------------
/src/inject/data_directory.inc:
--------------------------------------------------------------------------------
1 | std::filesystem::path data_directory() {
2 | static std::optional path;
3 | static std::mutex mtx;
4 | std::lock_guard lock(mtx);
5 |
6 | if (!path) {
7 | wchar_t home_dir[MAX_PATH];
8 | GetEnvironmentVariableW(L"USERPROFILE", home_dir, MAX_PATH);
9 | path = std::filesystem::path(home_dir) / ".breeze-shell";
10 | }
11 |
12 | if (!std::filesystem::exists(*path)) {
13 | std::filesystem::create_directories(*path);
14 | }
15 |
16 | return path.value();
17 | }
--------------------------------------------------------------------------------
/src/shell/build_info.h.in:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #define BREEZE_VERSION "${VERSION}"
3 | #define BREEZE_VERSION_MAJOR ${VERSION_MAJOR}
4 | #define BREEZE_VERSION_MINOR ${VERSION_MINOR}
5 | #define BREEZE_GIT_COMMIT_HASH "${GIT_COMMIT_HASH}"
6 | #define BREEZE_GIT_BRANCH_NAME "${GIT_BRANCH_NAME}"
7 | #define BREEZE_BUILD_DATE_TIME "${BUILD_DATE_TIME}"
--------------------------------------------------------------------------------
/src/shell/config.cc:
--------------------------------------------------------------------------------
1 | #include "config.h"
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include "logger.h"
9 | #include "rfl.hpp"
10 | #include "rfl/DefaultIfMissing.hpp"
11 | #include "rfl/json.hpp"
12 |
13 | #include "utils.h"
14 | #include "windows.h"
15 |
16 | namespace mb_shell {
17 | std::unique_ptr config::current;
18 | config::animated_float_conf config::_default_animation{
19 | .duration = 150,
20 | .easing = ui::easing_type::ease_in_out,
21 | .delay_scale = 1,
22 | };
23 |
24 | void config::write_config() {
25 | auto config_file = data_directory() / "config.json";
26 | std::ofstream ofs(config_file);
27 | if (!ofs) {
28 | std::cerr << "Failed to write config file." << std::endl;
29 | return;
30 | }
31 |
32 | ofs << rfl::json::write(*config::current);
33 | }
34 | void config::read_config() {
35 | auto config_file = data_directory() / "config.json";
36 |
37 | #ifdef __llvm__
38 | std::ifstream ifs(config_file);
39 | if (!std::filesystem::exists(config_file)) {
40 | auto config_file = data_directory() / "config.json";
41 | std::ofstream ofs(config_file);
42 | if (!ofs) {
43 | std::cerr << "Failed to write config file." << std::endl;
44 | }
45 |
46 | ofs << R"({
47 | "$schema": "https://raw.githubusercontent.com/std-microblock/breeze-shell/refs/heads/master/resources/schema.json"
48 | })";
49 | }
50 | if (!ifs) {
51 | std::cerr
52 | << "Config file could not be opened. Using default config instead."
53 | << std::endl;
54 | config::current = std::make_unique();
55 | config::current->debug_console = true;
56 | } else {
57 | std::string json_str;
58 | std::copy(std::istreambuf_iterator(ifs),
59 | std::istreambuf_iterator(), std::back_inserter(json_str));
60 |
61 | if (auto json =
62 | rfl::json::read(
63 | json_str)) {
64 | // parse twice for default value
65 | _default_animation = json.value().default_animation;
66 | json = rfl::json::read(
67 | json_str);
68 | config::current = std::make_unique(json.value());
69 | std::cout << "Config reloaded." << std::endl;
70 | } else {
71 | std::cerr << "Failed to read config file: " << json.error()->what()
72 | << "\nUsing default config instead." << std::endl;
73 | config::current = std::make_unique();
74 | config::current->debug_console = true;
75 | }
76 | }
77 | #else
78 | #pragma message \
79 | "We don't support loading config file on MSVC because of a bug in MSVC."
80 | dbgout("We don't support loading config file when compiled with MSVC "
81 | "because of a bug in MSVC.");
82 | config::current = std::make_unique();
83 | config::current->debug_console = true;
84 | #endif
85 |
86 | if (config::current->debug_console) {
87 | ShowWindow(GetConsoleWindow(), SW_SHOW);
88 | } else {
89 | ShowWindow(GetConsoleWindow(), SW_HIDE);
90 | }
91 | }
92 |
93 | std::filesystem::path config::data_directory() {
94 | static std::optional path;
95 | static std::mutex mtx;
96 | std::lock_guard lock(mtx);
97 |
98 | if (!path) {
99 | path = std::filesystem::path(env("USERPROFILE").value()) / ".breeze-shell";
100 | }
101 |
102 | if (!std::filesystem::exists(*path)) {
103 | std::filesystem::create_directories(*path);
104 | }
105 |
106 | return path.value();
107 | }
108 | void config::run_config_loader() {
109 | auto config_path = config::data_directory() / "config.json";
110 | dbgout("config file: {}", config_path.string());
111 | config::read_config();
112 | std::thread([config_path]() {
113 | auto last_mod = std::filesystem::last_write_time(config_path);
114 | while (true) {
115 | if (std::filesystem::last_write_time(config_path) != last_mod) {
116 | last_mod = std::filesystem::last_write_time(config_path);
117 | config::read_config();
118 | }
119 | std::this_thread::sleep_for(std::chrono::milliseconds(100));
120 | }
121 | }).detach();
122 | }
123 | void config::animated_float_conf::apply_to(ui::sp_anim_float &anim,
124 | float delay) {
125 | anim->set_duration(duration);
126 | anim->set_easing(easing);
127 | anim->set_delay(delay * delay_scale);
128 | }
129 | void config::animated_float_conf::operator()(ui::sp_anim_float &anim,
130 | float delay) {
131 | apply_to(anim, delay);
132 | }
133 |
134 | std::filesystem::path config::default_main_font() {
135 | return std::filesystem::path(env("WINDIR").value()) / "Fonts" / "segoeui.ttf";
136 | }
137 | std::filesystem::path config::default_fallback_font() {
138 | return std::filesystem::path(env("WINDIR").value()) / "Fonts" / "msyh.ttc";
139 | }
140 | std::string config::dump_config() { return rfl::json::write(*config::current); }
141 | } // namespace mb_shell
--------------------------------------------------------------------------------
/src/shell/config.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "animator.h"
4 | #include
5 | #include
6 | #include
7 | namespace mb_shell {
8 |
9 | struct config {
10 | static std::filesystem::path default_main_font();
11 | static std::filesystem::path default_fallback_font();
12 |
13 | struct animated_float_conf {
14 | float duration = _default_animation.duration;
15 | ui::easing_type easing = _default_animation.easing;
16 | float delay_scale = _default_animation.delay_scale;
17 |
18 | void apply_to(ui::sp_anim_float &anim, float delay = 0);
19 | void operator()(ui::sp_anim_float &anim, float delay = 0);
20 | } default_animation;
21 | static animated_float_conf _default_animation;
22 | struct context_menu {
23 | struct theme {
24 | bool use_dwm_if_available = true;
25 | float background_opacity = 1;
26 | bool acrylic = true;
27 | float radius = 6;
28 | float font_size = 14;
29 | float item_height = 23;
30 | float item_gap = 3;
31 | float item_radius = 5;
32 | float margin = 5;
33 | float padding = 6;
34 | float text_padding = 8;
35 | float icon_padding = 4;
36 | float right_icon_padding = 20;
37 | float multibutton_line_gap = -6;
38 |
39 | std::string acrylic_color_light = "#fefefe00";
40 | std::string acrylic_color_dark = "#28282800";
41 |
42 | // unused, only for backward compatibility
43 | float acrylic_opacity = 0.1;
44 |
45 | struct animation {
46 | struct main {
47 | animated_float_conf y;
48 | } main;
49 | struct item {
50 | animated_float_conf opacity;
51 | animated_float_conf x, y;
52 | animated_float_conf width;
53 | } item;
54 | struct bg {
55 | animated_float_conf opacity;
56 | animated_float_conf x, y, w, h;
57 | } main_bg, submenu_bg;
58 | } animation;
59 | } theme;
60 |
61 | bool vsync = true;
62 | bool ignore_owner_draw = true;
63 | bool reverse_if_open_to_up = true;
64 |
65 | // debug purpose only
66 | bool search_large_dwItemData_range = false;
67 |
68 | struct position {
69 | int padding_vertical = 20;
70 | int padding_horizontal = 0;
71 | } position;
72 | } context_menu;
73 | bool debug_console = false;
74 | // Restart to apply font/hook changes
75 | std::filesystem::path font_path_main = default_main_font();
76 | std::filesystem::path font_path_fallback = default_fallback_font();
77 | bool res_string_loader_use_hook = false;
78 | bool avoid_resize_ui = false;
79 | std::vector plugin_load_order = {};
80 |
81 | std::string $schema;
82 | static std::unique_ptr current;
83 | static void read_config();
84 | static void write_config();
85 | static void run_config_loader();
86 | static std::string dump_config();
87 |
88 | static std::filesystem::path data_directory();
89 | };
90 | } // namespace mb_shell
--------------------------------------------------------------------------------
/src/shell/contextmenu/contextmenu.cc:
--------------------------------------------------------------------------------
1 |
2 | #include "contextmenu.h"
3 | #include "../utils.h"
4 | #include "menu_widget.h"
5 | #include "ui.h"
6 | #include
7 | #include
8 |
9 | #include "menu_render.h"
10 |
11 | #include "../config.h"
12 | #include "../res_string_loader.h"
13 |
14 | #include "../logger.h"
15 |
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | namespace mb_shell {
28 | owner_draw_menu_info getBitmapFromOwnerDraw(MENUITEMINFOW *menuItemInfo,
29 | HWND hwnd) {
30 | owner_draw_menu_info result = {{}, 0, 0};
31 | MEASUREITEMSTRUCT measureItem = {0};
32 | measureItem.CtlType = ODT_MENU;
33 | measureItem.CtlID = 0;
34 | measureItem.itemID = menuItemInfo->wID;
35 | measureItem.itemData = (ULONG_PTR)(menuItemInfo->dwItemData);
36 |
37 | SendMessageW(hwnd, WM_MEASUREITEM, 0,
38 | reinterpret_cast(&measureItem));
39 |
40 | result.width = measureItem.itemWidth;
41 | result.height = measureItem.itemHeight;
42 |
43 | if (result.width == 0 || result.height == 0) {
44 | return result;
45 | }
46 |
47 | HDC hdc = GetDC(hwnd);
48 | if (!hdc)
49 | return result;
50 |
51 | HDC memDC = CreateCompatibleDC(hdc);
52 | if (!memDC) {
53 | ReleaseDC(hwnd, hdc);
54 | return result;
55 | }
56 |
57 | RECT rcItem = {0, 0, static_cast(result.width),
58 | static_cast(result.height)};
59 | FillRect(memDC, &rcItem, (HBRUSH)GetStockObject(WHITE_BRUSH));
60 |
61 | DRAWITEMSTRUCT drawItem = {0};
62 | drawItem.CtlType = ODT_MENU;
63 | drawItem.CtlID = 0;
64 | drawItem.itemID = menuItemInfo->wID;
65 | drawItem.itemAction = ODA_DRAWENTIRE;
66 | drawItem.itemState = 0;
67 | drawItem.hwndItem = (HWND)menuItemInfo->hSubMenu;
68 | drawItem.hDC = memDC;
69 | drawItem.rcItem = rcItem;
70 | drawItem.itemData = (ULONG_PTR)(menuItemInfo->dwItemData);
71 |
72 | SendMessageW(hwnd, WM_DRAWITEM, 0,
73 | reinterpret_cast(&drawItem)); // 发送绘制消息
74 |
75 | result.bitmap = CreateCompatibleBitmap(hdc, result.width, result.height);
76 | if (!result.bitmap) {
77 | DeleteDC(memDC);
78 | ReleaseDC(hwnd, hdc);
79 | return result;
80 | }
81 | return result;
82 | }
83 |
84 | std::wstring strip_extra_infos(std::wstring_view str) {
85 | // 1. Test&C -> Test
86 | // 2. Test -> Test
87 | // 3. Test\txxx -> Test
88 | // 4. remove all unicode control characters
89 |
90 | std::wstring result;
91 |
92 | for (int i = 0; i < str.size(); i++) {
93 | if (str[i] == '(' && i + 1 < str.size() && str[i + 1] == '&') {
94 | while (str[i] != ')' && i < str.size()) {
95 | i++;
96 | }
97 |
98 | continue;
99 | }
100 |
101 | if (str[i] == '&') {
102 | continue;
103 | }
104 |
105 | if (str[i] == '\t') {
106 | break;
107 | }
108 | result.push_back(str[i]);
109 | }
110 |
111 | return result;
112 | }
113 |
114 | menu menu::construct_with_hmenu(HMENU hMenu, HWND hWnd, bool is_top) {
115 | menu m;
116 |
117 | SendMessageW(hWnd, WM_INITMENUPOPUP, reinterpret_cast(hMenu),
118 | 0xFFFFFFFF);
119 | for (int i = 0; i < GetMenuItemCount(hMenu); i++) {
120 | menu_item item;
121 | wchar_t buffer[256];
122 | MENUITEMINFOW info = {sizeof(MENUITEMINFO)};
123 | info.fMask = MIIM_STRING | MIIM_SUBMENU | MIIM_ID | MIIM_FTYPE |
124 | MIIM_STATE | MIIM_BITMAP | MIIM_CHECKMARKS | MIIM_DATA;
125 | info.dwTypeData = buffer;
126 | info.cch = 256;
127 | if (!GetMenuItemInfoW(hMenu, i, TRUE, &info)) {
128 | std::cout << "Failed to get menu item info: " << GetLastError()
129 | << std::endl;
130 | continue;
131 | }
132 |
133 | if (info.fType & MFT_OWNERDRAW) {
134 | auto od = getBitmapFromOwnerDraw(&info, hWnd);
135 | if (od.width && od.height) {
136 | item.owner_draw = od;
137 | }
138 | }
139 |
140 | if (info.fType & MFT_RADIOCHECK || info.fState & MFS_CHECKED) {
141 | auto c = is_light_mode() ? 0 : 1;
142 | if ((!item.icon_bitmap && !item.icon_svg)) {
143 | item.icon_svg = std::format(
144 | R"#( )#",
145 | c ? "white" : "black");
146 | }
147 | }
148 |
149 | if (info.hSubMenu) {
150 | PostMessageW(hWnd, WM_INITMENUPOPUP,
151 | reinterpret_cast(info.hSubMenu), 0xFFFFFFFF);
152 | item.submenu =
153 | [data = menu::construct_with_hmenu(info.hSubMenu, hWnd, false)](
154 | std::shared_ptr mw) { mw->init_from_data(data); };
155 | } else {
156 | item.action = [=]() mutable {
157 | menu_render::current.value()->selected_menu = info.wID;
158 | menu_render::current.value()->rt->hide_as_close();
159 | };
160 |
161 | item.wID = info.wID;
162 | }
163 |
164 | if (info.fType & MFT_SEPARATOR || info.fType & MFT_MENUBARBREAK ||
165 | info.fType & MFT_MENUBREAK) {
166 | item.type = menu_item::type::spacer;
167 | } else {
168 | item.name = wstring_to_utf8(strip_extra_infos(buffer));
169 | auto id_stripped = res_string_loader::string_to_id(buffer);
170 | if (std::get_if(&id_stripped)) {
171 | item.name_resid =
172 | res_string_loader::string_to_id_string(strip_extra_infos(buffer));
173 | } else {
174 | item.name_resid = res_string_loader::string_to_id_string(buffer);
175 | }
176 | }
177 |
178 | if (info.fType & MFT_BITMAP) {
179 | item.icon_bitmap = (size_t)info.hbmpItem;
180 | } else if (info.hbmpChecked || info.hbmpUnchecked) {
181 | if (info.fState & MFS_CHECKED)
182 | item.icon_bitmap = (size_t)info.hbmpChecked;
183 | else
184 | item.icon_bitmap = (size_t)info.hbmpUnchecked;
185 | }
186 |
187 | if (info.dwItemData) {
188 | HBITMAP result{};
189 |
190 | if (!IS_INTRESOURCE(info.dwItemData)) {
191 | auto offsets =
192 | config::current->context_menu.search_large_dwItemData_range
193 | ? ([]() -> std::vector {
194 | std::vector offsets;
195 | for (int i = 0; i <= 0xfff; i++) {
196 | offsets.push_back(i);
197 | }
198 | return offsets;
199 | }())
200 | : std::vector{0x018, 0x220, 0x020, 0x000};
201 | for (int offset : offsets) {
202 | auto pHBitmap = reinterpret_cast(info.dwItemData + offset);
203 | if (!is_memory_readable(pHBitmap)) {
204 | continue;
205 | }
206 |
207 | result = *pHBitmap;
208 | if (result && ::GetObjectType(result) == OBJ_BITMAP) {
209 | BITMAP bitmap{};
210 | if (::GetObjectW(result, sizeof(BITMAP), &bitmap) ==
211 | sizeof(BITMAP)) {
212 | auto bmWidthBytes =
213 | ((bitmap.bmWidth * bitmap.bmBitsPixel + 31) / 32) * 4;
214 | if (bmWidthBytes == bitmap.bmWidthBytes &&
215 | (bitmap.bmBitsPixel == 32 || bitmap.bmBitsPixel == 24 ||
216 | bitmap.bmBitsPixel == 16 || bitmap.bmBitsPixel == 8) &&
217 | bitmap.bmWidth >= 4 && bitmap.bmWidth <= 64 &&
218 | bitmap.bmHeight >= 4 && bitmap.bmHeight <= 64) {
219 | item.icon_bitmap = (size_t)result;
220 | if (config::current->context_menu
221 | .search_large_dwItemData_range) {
222 | dbgout("Found icon at offset: {}", offset);
223 | }
224 | break;
225 | }
226 | }
227 | }
228 | }
229 | }
230 | }
231 |
232 | if (info.fState & MFS_DISABLED) {
233 | item.disabled = true;
234 | }
235 |
236 | m.items.push_back(item);
237 | }
238 |
239 | m.parent_window = hWnd;
240 | m.is_top_level = is_top;
241 | return m;
242 | }
243 | } // namespace mb_shell
244 |
--------------------------------------------------------------------------------
/src/shell/contextmenu/contextmenu.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "nanovg_wrapper.h"
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #define NOMINMAX
11 | #include
12 |
13 | namespace mb_shell {
14 | struct menu_item;
15 | struct menu_widget;
16 | struct menu {
17 | std::vector items;
18 | void *parent_window = nullptr;
19 | bool is_top_level = false;
20 |
21 | static menu construct_with_hmenu(HMENU hMenu, HWND hWnd, bool is_top = true);
22 | };
23 |
24 | struct owner_draw_menu_info {
25 | HBITMAP bitmap;
26 | int width, height;
27 | };
28 |
29 | struct menu_item {
30 | enum class type {
31 | button,
32 | spacer,
33 | } type = type::button;
34 |
35 | std::optional owner_draw{};
36 | std::optional name;
37 | std::optional> action;
38 | std::optional)>> submenu;
39 | std::optional icon_bitmap;
40 | std::optional icon_svg;
41 | bool icon_updated = false;
42 | bool disabled = false;
43 |
44 | // the two below are only for information; set them changes nothing
45 | std::optional wID;
46 | std::optional name_resid;
47 | };
48 | } // namespace mb_shell
--------------------------------------------------------------------------------
/src/shell/contextmenu/menu_render.cc:
--------------------------------------------------------------------------------
1 | #include "menu_render.h"
2 | #include "Windows.h"
3 | #include "menu_widget.h"
4 |
5 | #include "../script/binding_types.h"
6 | #include "ui.h"
7 | #include
8 | #include
9 |
10 | #include "../logger.h"
11 |
12 | namespace mb_shell {
13 | std::optional menu_render::current{};
14 | menu_render menu_render::create(int x, int y, menu menu) {
15 | if (auto res = ui::render_target::init_global(); !res) {
16 | MessageBoxW(NULL, L"Failed to initialize global render target", L"Error",
17 | MB_ICONERROR);
18 | return {nullptr, std::nullopt};
19 | }
20 |
21 | constexpr int l_pad = 100, t_pad = -1;
22 | static auto rt = []() {
23 | auto rt = std::make_shared();
24 | rt->transparent = true;
25 | rt->no_focus = true;
26 | rt->capture_all_input = true;
27 | rt->decorated = false;
28 | rt->topmost = true;
29 | rt->vsync = config::current->context_menu.vsync;
30 |
31 | if (config::current->avoid_resize_ui) {
32 | rt->width = 3840;
33 | rt->height = 2159;
34 | }
35 |
36 | if (auto res = rt->init(); !res) {
37 | MessageBoxW(NULL, L"Failed to initialize render target", L"Error",
38 | MB_ICONERROR);
39 | }
40 |
41 | nvgCreateFont(rt->nvg, "main",
42 | config::current->font_path_main.string().c_str());
43 | nvgCreateFont(rt->nvg, "fallback",
44 | config::current->font_path_fallback.string().c_str());
45 | nvgAddFallbackFont(rt->nvg, "main", "fallback");
46 | return rt;
47 | }();
48 | auto render = menu_render(rt, std::nullopt);
49 | auto current_js_context = std::make_shared(
50 | js::js_menu_context::$from_window(menu.parent_window));
51 |
52 | rt->parent = menu.parent_window;
53 |
54 | // get the monitor in which the menu is being shown
55 | auto monitor = MonitorFromPoint({x, y}, MONITOR_DEFAULTTONEAREST);
56 | MONITORINFOEX monitor_info;
57 | monitor_info.cbSize = sizeof(MONITORINFOEX);
58 | GetMonitorInfo(monitor, &monitor_info);
59 |
60 | // set the position of the window to fullscreen in this monitor + padding
61 |
62 | dbgout("Monitor: {} {} {} {}", monitor_info.rcMonitor.left,
63 | monitor_info.rcMonitor.top, monitor_info.rcMonitor.right,
64 | monitor_info.rcMonitor.bottom);
65 |
66 | rt->set_position(monitor_info.rcMonitor.left, monitor_info.rcMonitor.top);
67 | if (!config::current->avoid_resize_ui)
68 | rt->resize(
69 | monitor_info.rcMonitor.right - monitor_info.rcMonitor.left + l_pad,
70 | monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top + t_pad);
71 |
72 | glfwMakeContextCurrent(rt->window);
73 | glfwSwapInterval(config::current->context_menu.vsync ? 1 : 0);
74 |
75 | rt->show();
76 | auto menu_wid = std::make_shared(
77 | menu,
78 | // convert the x and y to the window coordinates
79 | x - monitor_info.rcMonitor.left, y - monitor_info.rcMonitor.top);
80 | rt->root->children.push_back(menu_wid);
81 | js::menu_info_basic_js menu_info{
82 | .menu = std::make_shared(menu_wid->menu_wid),
83 | .context = current_js_context};
84 |
85 | dbgout("[perf] JS plugins start");
86 | auto before_js = rt->clock.now();
87 | for (auto &listener : menu_callbacks_js) {
88 | listener->operator()(menu_info);
89 | }
90 | dbgout("[perf] JS plugins costed {}ms",
91 | std::chrono::duration_cast(
92 | rt->clock.now() - before_js)
93 | .count());
94 |
95 | rt->on_focus_changed = [](bool focused) {
96 | if (!focused) {
97 | }
98 | };
99 |
100 | dbgout("Current menu: {}", menu_render::current.has_value());
101 | return render;
102 | }
103 |
104 | menu_render::menu_render(std::shared_ptr rt,
105 | std::optional selected_menu)
106 | : rt(std::move(rt)), selected_menu(selected_menu) {
107 | current = this;
108 | }
109 | menu_render::~menu_render() {
110 | if (this->rt) {
111 | current = nullptr;
112 | }
113 | }
114 | menu_render::menu_render(menu_render &&t) {
115 | current = this;
116 |
117 | rt = std::move(t.rt);
118 | selected_menu = std::move(t.selected_menu);
119 | }
120 | menu_render &menu_render::operator=(menu_render &&t) {
121 | current = this;
122 | rt = std::move(t.rt);
123 | selected_menu = std::move(t.selected_menu);
124 | return *this;
125 | }
126 | }; // namespace mb_shell
--------------------------------------------------------------------------------
/src/shell/contextmenu/menu_render.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "../utils.h"
3 | #include "contextmenu.h"
4 | #include "ui.h"
5 | #include
6 |
7 | namespace mb_shell {
8 | struct menu_render {
9 | std::shared_ptr rt;
10 | std::optional selected_menu;
11 | bool light_color = is_light_mode();
12 | static std::optional current;
13 |
14 | menu_render() = delete;
15 | menu_render(std::shared_ptr rt,
16 | std::optional selected_menu);
17 |
18 | ~menu_render();
19 | const menu_render &operator=(const menu_render &) = delete;
20 | menu_render(menu_render &&t);
21 | menu_render &operator=(menu_render &&t);
22 | static menu_render create(int x, int y, menu menu);
23 | };
24 | } // namespace mb_shell
--------------------------------------------------------------------------------
/src/shell/contextmenu/menu_widget.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "../config.h"
3 | #include "animator.h"
4 | #include "contextmenu.h"
5 | #include "extra_widgets.h"
6 | #include "nanovg_wrapper.h"
7 | #include "ui.h"
8 | #include "widget.h"
9 | #include
10 | #include
11 | #include
12 |
13 | namespace mb_shell {
14 |
15 | struct menu_widget;
16 |
17 | struct menu_item_widget : public ui::widget {
18 | using super = ui::widget;
19 | menu_item item;
20 | ui::sp_anim_float opacity = anim_float(0, 200);
21 | menu_item_widget();
22 | virtual void reset_appear_animation(float delay);
23 | };
24 |
25 | struct menu_item_ownerdraw_widget : public menu_item_widget {
26 | using super = menu_item_widget;
27 | owner_draw_menu_info owner_draw;
28 | std::optional img{};
29 | menu_item_ownerdraw_widget(menu_item item);
30 | void update(ui::update_context &ctx) override;
31 | void render(ui::nanovg_context ctx) override;
32 | void reset_appear_animation(float delay) override;
33 | };
34 |
35 | struct menu_item_parent_widget : public menu_item_widget {
36 | using super = menu_item_widget;
37 | void update(ui::update_context &ctx) override;
38 | void reset_appear_animation(float delay) override;
39 | };
40 |
41 | struct menu_item_normal_widget : public menu_item_widget {
42 | using super = menu_item_widget;
43 | ui::sp_anim_float opacity = anim_float(0, 200);
44 | float text_padding = config::current->context_menu.theme.text_padding;
45 | float margin = config::current->context_menu.theme.margin;
46 | bool has_icon_padding = false;
47 | float padding = config::current->context_menu.theme.padding;
48 | float icon_padding = config::current->context_menu.theme.icon_padding;
49 | float right_icon_padding =
50 | config::current->context_menu.theme.right_icon_padding;
51 | menu_item_normal_widget(menu_item item);
52 | void reset_appear_animation(float delay) override;
53 |
54 | std::optional icon_img{};
55 | std::optional icon_unfold_img{};
56 |
57 | std::shared_ptr submenu_wid = nullptr;
58 | float show_submenu_timer = 0.f;
59 |
60 | ui::sp_anim_float bg_opacity = anim_float(0, 200);
61 | void render(ui::nanovg_context ctx) override;
62 | void update(ui::update_context &ctx) override;
63 | float measure_width(ui::update_context &ctx) override;
64 | bool check_hit(const ui::update_context &ctx) override;
65 |
66 | void hide_submenu();
67 | void show_submenu(ui::update_context &ctx);
68 | void reload_icon_img(ui::nanovg_context ctx);
69 | };
70 |
71 | enum class popup_direction {
72 | // 第一象限 ~ 第四象限
73 | top_left,
74 | top_right,
75 | bottom_left,
76 | bottom_right,
77 | };
78 |
79 | struct menu_widget : public ui::widget_flex {
80 | using super = ui::widget_flex;
81 | float bg_padding_vertical = 6;
82 |
83 | float max_height = 99999;
84 | float actual_height = 0;
85 | ui::sp_anim_float scroll_top =
86 | anim_float(0, 200, ui::easing_type::ease_in_out);
87 | std::shared_ptr bg;
88 |
89 | std::shared_ptr bg_submenu;
90 | std::shared_ptr current_submenu;
91 | std::vector> rendering_submenus;
92 | std::vector> item_widgets;
93 | menu_widget *parent_menu = nullptr;
94 |
95 | std::shared_ptr create_bg(bool is_main);
96 | menu menu_data;
97 | menu_widget();
98 | popup_direction direction = popup_direction::bottom_right;
99 | void init_from_data(menu menu_data);
100 | bool animate_appear_started = false;
101 | void reset_animation(bool reverse = false);
102 | void update(ui::update_context &ctx) override;
103 |
104 | void update_icon_width();
105 |
106 | void render(ui::nanovg_context ctx) override;
107 |
108 | bool check_hit(const ui::update_context &ctx) override;
109 | void close();
110 | };
111 |
112 | struct mouse_menu_widget_main : public ui::widget {
113 | float anchor_x = 0, anchor_y = 0;
114 | mouse_menu_widget_main(menu menu_data, float x, float y);
115 | bool position_calibrated = false, direction_calibrated = false;
116 | popup_direction direction;
117 | std::shared_ptr menu_wid;
118 |
119 | void update(ui::update_context &ctx);
120 |
121 | void render(ui::nanovg_context ctx);
122 |
123 | static std::pair
124 | calculate_position(menu_widget *menu_wid, ui::update_context &ctx,
125 | float anchor_x, float anchor_y, popup_direction direction);
126 |
127 | static popup_direction calculate_direction(
128 | menu_widget *menu_wid, ui::update_context &ctx, float anchor_x,
129 | float anchor_y,
130 | popup_direction prefer_direction = popup_direction::bottom_right);
131 |
132 | void calibrate_position(ui::update_context &ctx, bool animated = true);
133 | void calibrate_direction(ui::update_context &ctx);
134 | };
135 |
136 | } // namespace mb_shell
--------------------------------------------------------------------------------
/src/shell/entry.cc:
--------------------------------------------------------------------------------
1 |
2 | #include "GLFW/glfw3.h"
3 | #include "blook/blook.h"
4 |
5 | #include "config.h"
6 | #include "entry.h"
7 | #include "error_handler.h"
8 | #include "res_string_loader.h"
9 | #include "script/binding_types.h"
10 | #include "script/quickjspp.hpp"
11 | #include "script/script.h"
12 | #include "ui.h"
13 | #include "utils.h"
14 |
15 | #include "./contextmenu/contextmenu.h"
16 | #include "./contextmenu/menu_render.h"
17 | #include "./contextmenu/menu_widget.h"
18 |
19 | #include "fix_win11_menu.h"
20 |
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include
36 | #include
37 |
38 | #include
39 | #include
40 | #include
41 | #include
42 | #include
43 |
44 | #define NOMINMAX
45 | #include
46 |
47 | namespace mb_shell {
48 | window_proc_hook entry::main_window_loop_hook{};
49 | void main() {
50 | set_thread_locale_utf8();
51 |
52 | AllocConsole();
53 | freopen("CONOUT$", "w", stdout);
54 | freopen("CONOUT$", "w", stderr);
55 | freopen("CONIN$", "r", stdin);
56 | ShowWindow(GetConsoleWindow(), SW_HIDE);
57 |
58 | install_error_handlers();
59 | config::run_config_loader();
60 |
61 | res_string_loader::init();
62 | fix_win11_menu::install();
63 |
64 | static std::atomic_bool has_active_menu = false;
65 | std::thread([]() {
66 | script_context ctx;
67 |
68 | auto data_dir = config::data_directory();
69 | auto script_dir = data_dir / "scripts";
70 |
71 | if (!std::filesystem::exists(script_dir))
72 | std::filesystem::create_directories(script_dir);
73 |
74 | ctx.watch_folder(script_dir, [&]() { return !has_active_menu.load(); });
75 | }).detach();
76 |
77 | auto proc = blook::Process::self();
78 | auto win32u = proc->module("win32u.dll");
79 | auto user32 = proc->module("user32.dll");
80 |
81 | auto NtUserTrackPopupMenu = win32u.value()->exports("NtUserTrackPopupMenuEx");
82 | static auto NtUserTrackHook = NtUserTrackPopupMenu->inline_hook();
83 |
84 | std::set_terminate([]() {
85 | auto eptr = std::current_exception();
86 | if (eptr) {
87 | try {
88 | std::rethrow_exception(eptr);
89 | } catch (const std::exception &e) {
90 | std::cerr << "Uncaught exception: " << e.what() << std::endl;
91 | } catch (...) {
92 | std::cerr << "Uncaught exception of unknown type" << std::endl;
93 | }
94 |
95 | ShowWindow(GetConsoleWindow(), SW_SHOW);
96 | std::getchar();
97 | }
98 | std::abort();
99 | });
100 |
101 | std::thread([]() {
102 | if (auto res = ui::render_target::init_global(); !res) {
103 | MessageBoxW(NULL, L"Failed to initialize global render target", L"Error",
104 | MB_ICONERROR);
105 | return;
106 | }
107 | }).detach();
108 |
109 | NtUserTrackHook->install(+[](HMENU hMenu, int64_t uFlags, int64_t x,
110 | int64_t y, HWND hWnd, int64_t lptpm) {
111 | if (GetPropW(hWnd, L"COwnerDrawPopupMenu_This") &&
112 | config::current->context_menu.ignore_owner_draw) {
113 | return NtUserTrackHook->call_trampoline(hMenu, uFlags, x, y,
114 | hWnd, lptpm);
115 | }
116 |
117 | entry::main_window_loop_hook.install(hWnd);
118 |
119 | has_active_menu = true;
120 |
121 | perf_counter perf("TrackPopupMenuEx");
122 | menu menu = menu::construct_with_hmenu(hMenu, hWnd);
123 | perf.end("construct_with_hmenu");
124 | auto menu_render = menu_render::create(x, y, menu);
125 | menu_render.rt->last_time = menu_render.rt->clock.now();
126 | perf.end("menu_render::create");
127 | menu_render.rt->start_loop();
128 |
129 | has_active_menu = false;
130 |
131 | if (menu_render.selected_menu && !(uFlags & TPM_NONOTIFY)) {
132 | PostMessageW(hWnd, WM_COMMAND, *menu_render.selected_menu, 0);
133 | PostMessageW(hWnd, WM_NULL, 0, 0);
134 | }
135 |
136 | return (int32_t)menu_render.selected_menu.value_or(0);
137 | });
138 | }
139 | } // namespace mb_shell
140 |
141 | int APIENTRY DllMain(HINSTANCE hInstance, DWORD fdwReason, LPVOID lpvReserved) {
142 | switch (fdwReason) {
143 | case DLL_PROCESS_ATTACH: {
144 | auto cmdline = std::string(GetCommandLineA());
145 |
146 | std::ranges::transform(cmdline, cmdline.begin(), tolower);
147 |
148 | mb_shell::main();
149 | break;
150 | }
151 | }
152 | return 1;
153 | }
--------------------------------------------------------------------------------
/src/shell/entry.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "window_proc_hook.h"
4 |
5 | namespace mb_shell {
6 | struct entry {
7 | static window_proc_hook main_window_loop_hook;
8 | };
9 | } // namespace mb_shell
--------------------------------------------------------------------------------
/src/shell/error_handler.cc:
--------------------------------------------------------------------------------
1 | #include "error_handler.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #include "build_info.h"
12 | #include "config.h"
13 |
14 | #include "utils.h"
15 |
16 | #include "Windows.h"
17 | #include
18 |
19 | void show_console() {
20 | if (!GetConsoleWindow()) {
21 | AllocConsole();
22 | freopen("CONOUT$", "w", stdout);
23 | freopen("CONOUT$", "w", stderr);
24 | }
25 | ShowWindow(GetConsoleWindow(), SW_SHOW);
26 | SetForegroundWindow(GetConsoleWindow());
27 | }
28 |
29 | inline void output_crash_header(std::stringstream &ss) {
30 | ss << "Breeze Shell " << BREEZE_VERSION << " crash report" << std::endl;
31 | ss << "----------------------------------------" << std::endl;
32 | ss << "Build date: " << BREEZE_BUILD_DATE_TIME << std::endl;
33 | ss << "Git commit hash: " << BREEZE_GIT_COMMIT_HASH << std::endl;
34 | ss << "Commit page: https://github.com/std-microblock/breeze-shell/commit/"
35 | << BREEZE_GIT_COMMIT_HASH << std::endl;
36 | ss << "Git branch: " << BREEZE_GIT_BRANCH_NAME << std::endl;
37 | ss << "Data directory: " << mb_shell::config::data_directory() << std::endl;
38 | ss << "Windows version: "
39 | << (mb_shell::is_win11_or_later() ? "11 or later" : "10 or earlier")
40 | << std::endl;
41 | ss << "----------------------------------------" << std::endl;
42 | ss << "Config:" << std::endl;
43 | ss << mb_shell::config::dump_config() << std::endl;
44 | ss << "----------------------------------------" << std::endl;
45 | }
46 |
47 | std::wstring GetBacktrace(CONTEXT *contextRecord) {
48 | std::wstring info;
49 | wchar_t symbol_mem[sizeof(IMAGEHLP_SYMBOL64) + 256];
50 | auto symbol = (IMAGEHLP_SYMBOL64 *)symbol_mem;
51 |
52 | std::string current_module_path = std::string(MAX_PATH, '\0');
53 | GetModuleFileNameA(nullptr, current_module_path.data(),
54 | current_module_path.size());
55 |
56 | std::filesystem::path current_module_path_fs = current_module_path;
57 | auto current_module_folder = current_module_path_fs.parent_path();
58 |
59 | // Initialize the symbol handler
60 | SymInitializeW(GetCurrentProcess(),
61 | current_module_folder.c_str(), TRUE);
62 |
63 | // Initialize the stack frame
64 | STACKFRAME64 stackFrame = {0};
65 |
66 | #ifdef _WIN64
67 | stackFrame.AddrPC.Offset = contextRecord->Rip;
68 | stackFrame.AddrPC.Mode = AddrModeFlat;
69 | stackFrame.AddrFrame.Offset = contextRecord->Rbp;
70 | stackFrame.AddrFrame.Mode = AddrModeFlat;
71 | stackFrame.AddrStack.Offset = contextRecord->Rsp;
72 | stackFrame.AddrStack.Mode = AddrModeFlat;
73 | #else
74 | stackFrame.AddrPC.Offset = contextRecord->Eip;
75 | stackFrame.AddrPC.Mode = AddrModeFlat;
76 | stackFrame.AddrFrame.Offset = contextRecord->Ebp;
77 | stackFrame.AddrFrame.Mode = AddrModeFlat;
78 | stackFrame.AddrStack.Offset = contextRecord->Esp;
79 | stackFrame.AddrStack.Mode = AddrModeFlat;
80 | #endif
81 |
82 | DWORD64 displacement = 0;
83 | // Walk the call stack and append each frame to the string
84 | while (StackWalk64(
85 | #ifdef _WIN64
86 | IMAGE_FILE_MACHINE_AMD64
87 | #else
88 | IMAGE_FILE_MACHINE_I386
89 | #endif
90 |
91 | ,
92 | GetCurrentProcess(), GetCurrentThread(), &stackFrame, contextRecord,
93 | nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) {
94 | // Get the module name and offset for this frame
95 | DWORD64 moduleBase =
96 | SymGetModuleBase64(GetCurrentProcess(), stackFrame.AddrPC.Offset);
97 | wchar_t moduleName[MAX_PATH];
98 | GetModuleFileNameW((HMODULE)moduleBase, moduleName, MAX_PATH);
99 | DWORD64 offset = stackFrame.AddrPC.Offset - moduleBase;
100 |
101 | symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
102 | symbol->MaxNameLength = 255;
103 |
104 | wchar_t name[256] = {};
105 | SymGetSymFromAddr64(GetCurrentProcess(), stackFrame.AddrPC.Offset,
106 | &displacement, symbol);
107 |
108 | DWORD dwDisplacement;
109 | IMAGEHLP_LINE64 line;
110 |
111 | SymGetLineFromAddr64(GetCurrentProcess(), stackFrame.AddrPC.Offset,
112 | &dwDisplacement, &line);
113 |
114 | UnDecorateSymbolNameW(mb_shell::utf8_to_wstring(symbol->Name).c_str(), name,
115 | 256, UNDNAME_COMPLETE);
116 |
117 | // Append the frame to the string
118 | wchar_t frameInfo[1024];
119 | swprintf_s(frameInfo, L"%s + %I64X", moduleName, offset);
120 | info += std::wstring(frameInfo) + std::wstring(L"(") + std::wstring(name) +
121 | std::wstring(L") at line ") + std::to_wstring(line.LineNumber) + L"\n";
122 | }
123 |
124 | // Cleanup the symbol handler
125 | SymCleanup(GetCurrentProcess());
126 |
127 | // Return the backtrace string
128 | return info;
129 | }
130 |
131 | std::string GetExceptionName(EXCEPTION_POINTERS *ExceptionInfo) {
132 | std::string exceptionName;
133 | switch (ExceptionInfo->ExceptionRecord->ExceptionCode) {
134 | case EXCEPTION_ACCESS_VIOLATION:
135 | exceptionName = "ACCESS_VIOLATION";
136 | break;
137 | case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
138 | exceptionName = "ARRAY_BOUNDS_EXCEEDED";
139 | break;
140 | case EXCEPTION_BREAKPOINT:
141 | exceptionName = "BREAKPOINT";
142 | break;
143 | case EXCEPTION_DATATYPE_MISALIGNMENT:
144 | exceptionName = "DATATYPE_MISALIGNMENT";
145 | break;
146 | case EXCEPTION_FLT_DENORMAL_OPERAND:
147 | exceptionName = "FLT_DENORMAL_OPERAND";
148 | break;
149 | case EXCEPTION_FLT_DIVIDE_BY_ZERO:
150 | exceptionName = "FLT_DIVIDE_BY_ZERO";
151 | break;
152 | case EXCEPTION_FLT_INEXACT_RESULT:
153 | exceptionName = "FLT_INEXACT_RESULT";
154 | break;
155 | case EXCEPTION_FLT_INVALID_OPERATION:
156 | exceptionName = "FLT_INVALID_OPERATION";
157 | break;
158 | case EXCEPTION_FLT_OVERFLOW:
159 | exceptionName = "FLT_OVERFLOW";
160 | break;
161 | case EXCEPTION_FLT_STACK_CHECK:
162 | exceptionName = "FLT_STACK_CHECK";
163 | break;
164 | case EXCEPTION_FLT_UNDERFLOW:
165 | exceptionName = "FLT_UNDERFLOW";
166 | break;
167 | case EXCEPTION_ILLEGAL_INSTRUCTION:
168 | exceptionName = "ILLEGAL_INSTRUCTION";
169 | break;
170 | case EXCEPTION_IN_PAGE_ERROR:
171 | exceptionName = "IN_PAGE_ERROR";
172 | break;
173 | case EXCEPTION_INT_DIVIDE_BY_ZERO:
174 | exceptionName = "INT_DIVIDE_BY_ZERO";
175 | break;
176 | case EXCEPTION_INT_OVERFLOW:
177 | exceptionName = "INT_OVERFLOW";
178 | break;
179 | case EXCEPTION_INVALID_DISPOSITION:
180 | exceptionName = "INVALID_DISPOSITION";
181 | break;
182 | case EXCEPTION_NONCONTINUABLE_EXCEPTION:
183 | exceptionName = "NONCONTINUABLE_EXCEPTION";
184 | break;
185 | case EXCEPTION_PRIV_INSTRUCTION:
186 | exceptionName = "PRIV_INSTRUCTION";
187 | break;
188 | case EXCEPTION_SINGLE_STEP:
189 | exceptionName = "SINGLE_STEP";
190 | break;
191 | case EXCEPTION_STACK_OVERFLOW:
192 | exceptionName = "STACK_OVERFLOW";
193 | break;
194 | default:
195 | exceptionName = "UNKNOWN";
196 | break;
197 | }
198 | return exceptionName;
199 | }
200 |
201 | void mb_shell::install_error_handlers() {
202 | SetUnhandledExceptionFilter([](PEXCEPTION_POINTERS ex) -> LONG {
203 | show_console();
204 |
205 | std::ofstream file(config::data_directory().string() +
206 | "\\crash_report.txt");
207 |
208 | std::stringstream ss;
209 | output_crash_header(ss);
210 | ss << "Exception code: " << std::hex << ex->ExceptionRecord->ExceptionCode
211 | << "(" << GetExceptionName(ex) << ")" << std::endl;
212 | ss << "Exception flags: " << std::hex << ex->ExceptionRecord->ExceptionFlags
213 | << std::endl;
214 | ss << "Exception address: " << std::hex
215 | << ex->ExceptionRecord->ExceptionAddress << std::endl;
216 | ss << "Registers:" << std::endl;
217 |
218 | ss << "RAX: " << std::hex << ex->ContextRecord->Rax << std::endl;
219 | ss << "RBX: " << std::hex << ex->ContextRecord->Rbx << std::endl;
220 | ss << "RCX: " << std::hex << ex->ContextRecord->Rcx << std::endl;
221 | ss << "RDX: " << std::hex << ex->ContextRecord->Rdx << std::endl;
222 | ss << "R8: " << std::hex << ex->ContextRecord->R8 << std::endl;
223 | ss << "R9: " << std::hex << ex->ContextRecord->R9 << std::endl;
224 | ss << "R10: " << std::hex << ex->ContextRecord->R10 << std::endl;
225 | ss << "R11: " << std::hex << ex->ContextRecord->R11 << std::endl;
226 | ss << "R12: " << std::hex << ex->ContextRecord->R12 << std::endl;
227 | ss << "R13: " << std::hex << ex->ContextRecord->R13 << std::endl;
228 | ss << "R14: " << std::hex << ex->ContextRecord->R14 << std::endl;
229 | ss << "R15: " << std::hex << ex->ContextRecord->R15 << std::endl;
230 | ss << "RDI: " << std::hex << ex->ContextRecord->Rdi << std::endl;
231 | ss << "RSI: " << std::hex << ex->ContextRecord->Rsi << std::endl;
232 | ss << "RBP: " << std::hex << ex->ContextRecord->Rbp << std::endl;
233 | ss << "RSP: " << std::hex << ex->ContextRecord->Rsp << std::endl;
234 | ss << "RIP: " << std::hex << ex->ContextRecord->Rip << std::endl;
235 |
236 | ss << "Stack trace:" << std::endl;
237 | ss << wstring_to_utf8(GetBacktrace(ex->ContextRecord)) << std::endl;
238 |
239 | if (file.is_open()) {
240 | file << ss.str();
241 | file.flush();
242 | file.close();
243 | }
244 | std::cerr << ss.str();
245 |
246 | Sleep(5000);
247 | return EXCEPTION_CONTINUE_EXECUTION;
248 | });
249 |
250 | std::set_terminate([]() {
251 | show_console();
252 |
253 | std::stringstream ss;
254 | output_crash_header(ss);
255 | try {
256 | throw std::current_exception();
257 | } catch (const std::exception &e) {
258 | ss << "Uncaught exception: " << e.what() << std::endl;
259 | } catch (...) {
260 | ss << "Uncaught exception of unknown type" << std::endl;
261 | }
262 |
263 | CONTEXT ctx;
264 | RtlCaptureContext(&ctx);
265 | ss << "Stack trace:" << std::endl;
266 | ss << wstring_to_utf8(GetBacktrace(&ctx)) << std::endl;
267 |
268 | std::ofstream file(config::data_directory().string() +
269 | "\\crash_report.txt");
270 |
271 | if (file.is_open()) {
272 | file << ss.str();
273 | file.flush();
274 | file.close();
275 | }
276 |
277 | std::cerr << ss.str();
278 |
279 | Sleep(5000);
280 | });
281 | }
282 |
--------------------------------------------------------------------------------
/src/shell/error_handler.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | namespace mb_shell {
3 | void install_error_handlers();
4 | }
--------------------------------------------------------------------------------
/src/shell/fix_win11_menu.cc:
--------------------------------------------------------------------------------
1 | #include "fix_win11_menu.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "blook/blook.h"
10 | #include "blook/memo.h"
11 | #include "utils.h"
12 | #include "zasm/base/immediate.hpp"
13 | #include "zasm/x86/mnemonic.hpp"
14 | #include "zasm/x86/register.hpp"
15 |
16 | // https://stackoverflow.com/questions/937044/determine-path-to-registry-key-from-hkey-handle-in-c
17 | #include
18 | #define WIN32_NO_STATUS
19 | #include
20 | #include
21 | #pragma comment(lib, "ntdll")
22 | #include
23 | #include
24 |
25 | #define REG_KEY_PATH_LENGTH 1024
26 |
27 | typedef enum _KEY_INFORMATION_CLASS {
28 | KeyBasicInformation,
29 | KeyNodeInformation,
30 | KeyFullInformation,
31 | KeyNameInformation,
32 | KeyCachedInformation,
33 | KeyFlagsInformation,
34 | KeyVirtualizationInformation,
35 | KeyHandleTagsInformation,
36 | KeyTrustInformation,
37 | KeyLayerInformation,
38 | MaxKeyInfoClass
39 | } KEY_INFORMATION_CLASS;
40 |
41 | typedef struct _KEY_NAME_INFORMATION {
42 | ULONG NameLength;
43 | WCHAR Name[1];
44 | } KEY_NAME_INFORMATION, *PKEY_NAME_INFORMATION;
45 |
46 | EXTERN_C NTSYSAPI NTSTATUS NTAPI NtQueryKey(
47 | __in HANDLE /* KeyHandle */,
48 | __in KEY_INFORMATION_CLASS /* KeyInformationClass */,
49 | __out_opt PVOID /* KeyInformation */, __in ULONG /* Length */,
50 | __out ULONG * /* ResultLength */
51 | );
52 |
53 | std::wstring RegQueryKeyPath(HKEY hKey) {
54 | std::wstring keyPath;
55 |
56 | NTSTATUS Status;
57 |
58 | std::vector Buffer(FIELD_OFFSET(KEY_NAME_INFORMATION, Name) +
59 | sizeof(WCHAR) * REG_KEY_PATH_LENGTH);
60 | KEY_NAME_INFORMATION *pkni;
61 | ULONG Length;
62 |
63 | TryAgain:
64 | Status = NtQueryKey(hKey, KeyNameInformation, Buffer.data(), Buffer.size(),
65 | &Length);
66 | switch (Status) {
67 | case STATUS_BUFFER_TOO_SMALL:
68 | case STATUS_BUFFER_OVERFLOW:
69 | Buffer.resize(Length);
70 | goto TryAgain;
71 | case STATUS_SUCCESS:
72 | pkni = reinterpret_cast(Buffer.data());
73 | keyPath.assign(pkni->Name, pkni->NameLength / sizeof(WCHAR));
74 | default:
75 | break;
76 | }
77 |
78 | return keyPath;
79 | }
80 | void mb_shell::fix_win11_menu::install() {
81 | auto proc = blook::Process::self();
82 |
83 | // approch 1: simulated reg edit
84 | auto advapi32 = proc->module("kernelbase.dll");
85 |
86 | auto RegGetValueW = advapi32.value()->exports("RegGetValueW");
87 | static auto RegGetValueHook = RegGetValueW->inline_hook();
88 |
89 | RegGetValueHook->install(
90 | (void *)+[](HKEY hkey, LPCWSTR lpSubKey, LPCWSTR lpValue, DWORD dwFlags,
91 | LPDWORD pdwType, PVOID pvData, LPDWORD pcbData) {
92 | // simulate
93 | // reg.exe add
94 | //
95 | //
96 | // "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32"
97 | // /f /ve
98 | auto path = wstring_to_utf8(RegQueryKeyPath(hkey));
99 | if (path.ends_with("\\CLSID\\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}"
100 | "\\InprocServer32")) {
101 | if (pvData != nullptr && pcbData != nullptr) {
102 | *pcbData = 0;
103 | }
104 | if (pdwType != nullptr) {
105 | *pdwType = REG_SZ;
106 | }
107 | return ERROR_SUCCESS;
108 | } else
109 | return RegGetValueHook->call_trampoline(
110 | hkey, lpSubKey, lpValue, dwFlags, pdwType, pvData, pcbData);
111 | });
112 |
113 | // approch 2: patch the shell32.dll to predent the shift key is pressed
114 | std::thread([=]() {
115 | if (auto shell32 = proc->module("shell32.dll")) {
116 | // mov ecx, 10
117 | // call GetKeyState/GetAsyncKeyState
118 | auto disasm = shell32.value()->section(".text")->disassembly();
119 |
120 | auto patch_area = [&](auto mem) {
121 | for (auto it = mem.begin(); it != mem.end(); ++it) {
122 | auto &insn = *it;
123 | if (insn->getMnemonic() == zasm::x86::Mnemonic::Mov) {
124 | if (insn->getOperand(0) == zasm::x86::ecx &&
125 | insn->getOperand(1).template holds() &&
126 | insn->getOperand(1)
127 | .template get()
128 | .template value() == 0x10) {
129 | auto &next = *std::next(it);
130 | if (next->getMnemonic() == zasm::x86::Mnemonic::Call &&
131 | next->getOperand(0).template holds()) {
132 | insn.ptr()
133 | .reassembly([](auto a) {
134 | a.mov(zasm::x86::eax, 0x0);
135 | a.bts(zasm::x86::edi, 0x8);
136 | a.nop();
137 | a.nop();
138 | a.nop();
139 | })
140 | .patch();
141 |
142 | return true;
143 | }
144 | }
145 | }
146 | }
147 |
148 | return false;
149 | };
150 |
151 | // the function to determine if show win10 menu or win11 menu calls
152 | // SetMessageExtraInfo, so we use it as a hint
153 | auto extraInfo =
154 | GetProcAddress(LoadLibraryA("user32.dll"), "SetMessageExtraInfo");
155 | for (auto &ins : disasm) {
156 | if (ins->getMnemonic() == zasm::x86::Mnemonic::Call) {
157 | auto xrefs = ins.xrefs();
158 | if (xrefs.empty())
159 | continue;
160 | if (auto ptr = xrefs[0].try_read();
161 | ptr.has_value() && ptr.value() == extraInfo) {
162 | if (patch_area(ins.ptr()
163 | .find_upwards({0x40, 0x55})
164 | ->range_size(0x150)
165 | .disassembly()))
166 | break;
167 | }
168 | }
169 | }
170 | }
171 | }).detach();
172 | }
173 |
--------------------------------------------------------------------------------
/src/shell/fix_win11_menu.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | namespace mb_shell {
3 | struct fix_win11_menu {
4 | static void install();
5 | };
6 | }
--------------------------------------------------------------------------------
/src/shell/logger.cc:
--------------------------------------------------------------------------------
1 | #include "logger.h"
2 | #include "config.h"
3 | #include "utils.h"
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | #include
10 |
11 | namespace mb_shell {
12 | void append_debug_string(const std::string &str) {
13 | static std::mutex mutex;
14 | static std::ofstream file(config::data_directory() / "debug.log",
15 | std::ios::app);
16 |
17 | std::lock_guard lock(mutex);
18 |
19 | file << str;
20 | file.flush();
21 |
22 | printf("%s", str.c_str());
23 | OutputDebugStringA(str.c_str());
24 | }
25 | } // namespace mb_shell
--------------------------------------------------------------------------------
/src/shell/logger.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 | #include
4 | #include
5 |
6 | namespace mb_shell {
7 | void append_debug_string(const std::string &str);
8 | template
9 | void dbgout(const std::format_string fmt, types&&... args) {
10 | std::string str = std::format(fmt, std::forward(args)...);
11 | append_debug_string(str + "\n");
12 | }
13 | }
--------------------------------------------------------------------------------
/src/shell/res_string_loader.cc:
--------------------------------------------------------------------------------
1 | #include "res_string_loader.h"
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #include "config.h"
11 | #include "utils.h"
12 |
13 | #include "blook/blook.h"
14 |
15 | #include "logger.h"
16 |
17 | #include "Windows.h"
18 |
19 | namespace mb_shell {
20 | static std::mutex lock_str_data;
21 | static std::unordered_map
23 | str_data;
24 | static std::unordered_map module_name_cache;
25 |
26 | res_string_loader::string_id res_string_loader::string_to_id(std::wstring str) {
27 | std::lock_guard lock(lock_str_data);
28 | auto it = str_data.find(str);
29 | if (it != str_data.end()) {
30 | return it->second;
31 | }
32 | return std::hash{}(str);
33 | }
34 |
35 | std::string get_module_name_from_instance(HINSTANCE hInstance) {
36 | char buffer[MAX_PATH];
37 | GetModuleFileNameA(hInstance, buffer, MAX_PATH);
38 | return std::filesystem::path(buffer).filename().string();
39 | }
40 |
41 | size_t store_module_name(HINSTANCE hInstance) {
42 | std::string module_name = get_module_name_from_instance(hInstance);
43 | auto mod_hash = std::hash{}(module_name);
44 | module_name_cache[mod_hash] = module_name;
45 | return mod_hash;
46 | }
47 |
48 | void res_string_loader::init_hook() {
49 | static std::atomic_bool inited = false;
50 | if (inited.exchange(true)) {
51 | return;
52 | }
53 |
54 | auto proc = blook::Process::self();
55 | auto kernelbase = proc->module("kernelbase.dll").value();
56 |
57 | static auto LoadStringWHook =
58 | kernelbase->exports("LoadStringW")->inline_hook();
59 | LoadStringWHook->install(+[](HINSTANCE hInstance, UINT uID, LPWSTR lpBuffer,
60 | int cchBufferMax) -> int {
61 | auto res = LoadStringWHook->call_trampoline(hInstance, uID, lpBuffer,
62 | cchBufferMax);
63 | if (res > 0) {
64 | std::wstring str(lpBuffer, res);
65 | std::lock_guard lock(lock_str_data);
66 | if (str_data.find(str) != str_data.end())
67 | return res;
68 | str_data[str] = {uID, store_module_name(hInstance)};
69 | }
70 | return res;
71 | });
72 |
73 | static auto LoadStringAHook =
74 | kernelbase->exports("LoadStringA")->inline_hook();
75 |
76 | LoadStringAHook->install(+[](HINSTANCE hInstance, UINT uID, LPSTR lpBuffer,
77 | int cchBufferMax) -> int {
78 | auto res = LoadStringAHook->call_trampoline(hInstance, uID, lpBuffer,
79 | cchBufferMax);
80 | if (res > 0) {
81 | std::string str(lpBuffer, res);
82 | std::lock_guard lock(lock_str_data);
83 | auto s = utf8_to_wstring(str);
84 | if (str_data.find(s) != str_data.end())
85 | return res;
86 | str_data[s] = {uID, store_module_name(hInstance)};
87 | }
88 |
89 | return res;
90 | });
91 | }
92 | std::string res_string_loader::string_to_id_string(std::wstring str) {
93 | auto id = string_to_id(str);
94 | if (auto *p = std::get_if(&id)) {
95 | return std::to_string(p->id) + "@" + module_name_cache[p->module];
96 | } else {
97 | return std::to_string(std::get(id)) + "@0";
98 | }
99 | }
100 | void res_string_loader::init() {
101 | std::thread([]() {
102 | init_known_strings();
103 | if (config::current->res_string_loader_use_hook)
104 | init_hook();
105 | }).detach();
106 | }
107 |
108 | void EnumerateStringResources(
109 | HMODULE mod, std::function callback) {
110 | EnumResourceNamesW(
111 | mod, MAKEINTRESOURCEW(6),
112 | +[](HMODULE hModule, LPCWSTR /*lpType*/, LPWSTR lpName,
113 | LONG_PTR lParam) -> BOOL {
114 | auto &cb =
115 | *reinterpret_cast *>(
116 | lParam);
117 |
118 | if (!IS_INTRESOURCE(lpName))
119 | return TRUE;
120 |
121 | HRSRC hRes = FindResourceW(hModule, lpName, MAKEINTRESOURCEW(6));
122 | if (!hRes)
123 | return TRUE;
124 |
125 | HGLOBAL hData = LoadResource(hModule, hRes);
126 | if (!hData)
127 | return TRUE;
128 |
129 | const BYTE *pData = static_cast(LockResource(hData));
130 | if (!pData)
131 | return TRUE;
132 |
133 | DWORD dwSize = SizeofResource(hModule, hRes);
134 | DWORD pos = 0;
135 |
136 | for (int i = 0; i < 16 && pos < dwSize; ++i) {
137 | WORD len = *reinterpret_cast(pData + pos);
138 | pos += sizeof(WORD);
139 |
140 | if (len == 0)
141 | continue;
142 |
143 | if (pos + len * sizeof(WCHAR) > dwSize)
144 | break;
145 |
146 | std::wstring_view strView(
147 | reinterpret_cast(pData + pos), len);
148 | cb(strView,
149 | static_cast(reinterpret_cast(lpName)) * 16 + i);
150 |
151 | pos += len * sizeof(WCHAR);
152 | }
153 |
154 | return TRUE;
155 | },
156 | reinterpret_cast(&callback));
157 | }
158 |
159 | void load_all_res_strings(std::string module) {
160 | HINSTANCE hInstance = GetModuleHandleA(module.data());
161 | static std::unordered_set loaded_modules;
162 | if (loaded_modules.contains(hInstance)) {
163 | return;
164 | }
165 | if (!hInstance) {
166 | return;
167 | }
168 | auto mod_hash = store_module_name(hInstance);
169 |
170 | EnumerateStringResources(hInstance, [&](std::wstring_view str, size_t id) {
171 | std::lock_guard lock(lock_str_data);
172 | if (str.length() > 60)
173 | return;
174 | auto s = std::wstring(str);
175 | if (str_data.find(s) != str_data.end())
176 | return;
177 | str_data[s] = {id, mod_hash};
178 | });
179 | }
180 |
181 | void res_string_loader::init_known_strings() {
182 | auto res_dlls = {
183 | "shell32.dll", "acppage.dll", "ntshrui.dll",
184 | "appresolver.dll", "windows.storage.dll", "explorerframe.dll",
185 | "explorer.exe", "user32.dll", "wpdshext.dll",
186 | "display.dll", "themecpl.dll", "regedit.exe",
187 | "powershell.exe", "stobject.dll", "fvecpl.dll",
188 | "twext.dll", "twinui.dll", "twinui.pcshell.dll",
189 | "isoburn.exe"};
190 |
191 | auto now = std::chrono::high_resolution_clock::now();
192 | for (auto &dll : res_dlls) {
193 | load_all_res_strings(dll);
194 | }
195 | dbgout("[perf] init_known_strings took {}ms",
196 | std::chrono::duration_cast(
197 | std::chrono::high_resolution_clock::now() - now)
198 | .count());
199 | }
200 | } // namespace mb_shell
--------------------------------------------------------------------------------
/src/shell/res_string_loader.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | namespace mb_shell {
7 | struct res_string_loader {
8 | struct res_string_identifier {
9 | size_t id;
10 | size_t module;
11 | };
12 |
13 | using string_id = std::variant;
14 | static string_id string_to_id(std::wstring str);
15 | static std::string string_to_id_string(std::wstring str);
16 | static void init_hook();
17 | static void init_known_strings();
18 |
19 | static void init();
20 | };
21 | }
--------------------------------------------------------------------------------
/src/shell/script/quickjspp.cc:
--------------------------------------------------------------------------------
1 | #include "quickjspp.hpp"
2 |
3 | namespace qjs {
4 | thread_local Context *Context::current;
5 | }
--------------------------------------------------------------------------------
/src/shell/script/script.cc:
--------------------------------------------------------------------------------
1 | #include "script.h"
2 | #include "../contextmenu/contextmenu.h"
3 | #include "binding_qjs.h"
4 |
5 | #include "../config.h"
6 | #include "../utils.h"
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 |
19 | #include "FileWatch.hpp"
20 | #include "quickjs.h"
21 | #include "quickjspp.hpp"
22 |
23 | #include "../logger.h"
24 |
25 | thread_local bool is_thread_js_main = false;
26 |
27 | static unsigned char script_js_bytes[] = {
28 | #include "script.js.h"
29 | };
30 |
31 | std::string breeze_script_js = std::string(
32 | reinterpret_cast(script_js_bytes), sizeof(script_js_bytes) - 1);
33 |
34 | namespace mb_shell {
35 |
36 | void println(qjs::rest args) {
37 | std::stringstream ss;
38 | for (auto &arg : args) {
39 | ss << arg << " ";
40 | }
41 | ss << std::endl;
42 | auto ws = utf8_to_wstring(ss.str());
43 | WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), ws.c_str(), ws.size(), nullptr,
44 | nullptr);
45 | }
46 |
47 | void script_context::bind() {
48 | auto &module = js->addModule("mshell");
49 |
50 | module.function("println", println);
51 |
52 | bindAll(module);
53 |
54 | auto g = js->global();
55 | g["console"] = js->newObject();
56 | qjs::Value println_fn =
57 | qjs::js_traits)>>::wrap(
58 | js->ctx, println);
59 | g["console"]["log"] = println_fn;
60 | g["console"]["info"] = println_fn;
61 | g["console"]["warn"] = println_fn;
62 | g["console"]["error"] = println_fn;
63 | g["console"]["debug"] = println_fn;
64 | }
65 | script_context::script_context() : rt{}, js{} {}
66 | void script_context::watch_folder(const std::filesystem::path &path,
67 | std::function on_reload) {
68 | bool has_update = false;
69 |
70 | std::optional js_thread;
71 | auto reload_all = [&]() {
72 | dbgout("Reloading all scripts");
73 |
74 | *stop_signal = true;
75 | stop_signal = std::make_shared(false);
76 |
77 | if (js_thread)
78 | js_thread->join();
79 | dbgout("Creating JS thread");
80 | menu_callbacks_js.clear();
81 |
82 | js_thread = std::thread([&, this, ss = stop_signal]() {
83 | is_thread_js_main = true;
84 | set_thread_locale_utf8();
85 | rt = std::make_shared();
86 | JS_UpdateStackTop(rt->rt);
87 | js = std::make_shared(*rt);
88 | bind();
89 | try {
90 | JS_UpdateStackTop(rt->rt);
91 | js->eval(breeze_script_js, "breeze-script.js", JS_EVAL_TYPE_MODULE);
92 | } catch (std::exception &e) {
93 | std::cerr << "Error in breeze-script.js: " << e.what() << std::endl;
94 | }
95 |
96 | std::vector files;
97 | std::ranges::copy(std::filesystem::directory_iterator(path) |
98 | std::ranges::views::filter([](auto &entry) {
99 | return entry.path().extension() == ".js";
100 | }),
101 | std::back_inserter(files));
102 |
103 | // resort files by config
104 | auto plugin_load_order = config::current->plugin_load_order;
105 | // if not found, load after all
106 | std::ranges::sort(files, [&](auto &a, auto &b) {
107 | auto a_name = a.filename().stem().string();
108 | auto b_name = b.filename().stem().string();
109 |
110 | auto a_pos = std::ranges::find(plugin_load_order, a_name);
111 | auto b_pos = std::ranges::find(plugin_load_order, b_name);
112 |
113 | if (a_pos == plugin_load_order.end() &&
114 | b_pos == plugin_load_order.end()) {
115 | return a_name < b_name;
116 | }
117 |
118 | if (a_pos == plugin_load_order.end()) {
119 | return false;
120 | }
121 |
122 | if (b_pos == plugin_load_order.end()) {
123 | return true;
124 | }
125 |
126 | return a_pos < b_pos;
127 | });
128 |
129 | for (auto &path : files) {
130 | try {
131 | std::ifstream file(path);
132 | std::string script((std::istreambuf_iterator(file)),
133 | std::istreambuf_iterator());
134 |
135 | js->moduleLoader = [&](std::string_view module_name) {
136 | auto module_path =
137 | path.parent_path() / (std::string(module_name) + ".js");
138 | if (!std::filesystem::exists(module_path)) {
139 | return qjs::Context::ModuleData{};
140 | }
141 | std::ifstream file(module_path);
142 | std::string script((std::istreambuf_iterator(file)),
143 | std::istreambuf_iterator());
144 | return qjs::Context::ModuleData{script};
145 | };
146 | auto func = JS_Eval(js->ctx, script.c_str(), script.size(),
147 | path.generic_string().c_str(),
148 | JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
149 |
150 | if (JS_IsException(func)) {
151 | std::cerr << "Error in file: " << path << std::endl;
152 | JS_FreeValue(js->ctx, func);
153 | continue;
154 | }
155 |
156 | JSModuleDef *m = (JSModuleDef *)JS_VALUE_GET_PTR(func);
157 | auto meta_obj = JS_GetImportMeta(js->ctx, m);
158 |
159 | JS_DefinePropertyValueStr(
160 | js->ctx, meta_obj, "url",
161 | JS_NewString(js->ctx, path.generic_string().c_str()),
162 | JS_PROP_C_W_E);
163 |
164 | JS_DefinePropertyValueStr(
165 | js->ctx, meta_obj, "name",
166 | JS_NewString(js->ctx, path.filename().generic_string().c_str()),
167 | JS_PROP_C_W_E);
168 |
169 | JS_FreeValue(js->ctx, meta_obj);
170 |
171 | JS_EvalFunction(js->ctx, func);
172 | } catch (std::exception &e) {
173 | std::cerr << "Error in file: " << path << " " << e.what()
174 | << std::endl;
175 | }
176 | }
177 |
178 | while (auto ptr = js) {
179 | if (ptr->ctx) {
180 | while (JS_IsJobPending(rt->rt) && !*ss) {
181 | auto ctx = ptr->ctx;
182 | auto ctx1 = ctx;
183 | auto res = JS_ExecutePendingJob(rt->rt, &ctx1);
184 | if (res == -999) {
185 | has_update = true;
186 | dbgout("JS loop critical error! Restarting...");
187 | return;
188 | }
189 | std::lock_guard lock(ptr->js_job_start_mutex);
190 | ptr->has_pending_job = JS_IsJobPending(rt->rt);
191 | }
192 | }
193 | if (*ss)
194 | break;
195 | std::unique_lock lock(js->js_job_start_mutex);
196 | if (js->has_pending_job)
197 | continue;
198 | js->js_job_start_cv.wait_for(
199 | lock, std::chrono::milliseconds(100),
200 | [&]() { return js->has_pending_job || *ss; });
201 | }
202 | is_thread_js_main = false;
203 | });
204 | };
205 |
206 | reload_all();
207 |
208 | filewatch::FileWatch watch(
209 | path.generic_string(),
210 | [&](const std::string &path, const filewatch::Event change_type) {
211 | if (!path.ends_with(".js")) {
212 | return;
213 | }
214 |
215 | dbgout("File change detected: {}", path);
216 | has_update = true;
217 | });
218 |
219 | while (true) {
220 | std::this_thread::sleep_for(std::chrono::milliseconds(300));
221 | if (has_update && on_reload()) {
222 | has_update = false;
223 | reload_all();
224 | }
225 | }
226 | }
227 |
228 | } // namespace mb_shell
229 |
--------------------------------------------------------------------------------
/src/shell/script/script.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "quickjspp.hpp"
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | extern thread_local bool is_thread_js_main;
14 | namespace mb_shell {
15 | struct script_context {
16 | std::shared_ptr rt;
17 | std::shared_ptr js;
18 | std::shared_ptr stop_signal = std::make_shared(0);
19 | script_context();
20 | void bind();
21 |
22 | void watch_folder(
23 | const std::filesystem::path &path,
24 | std::function on_reload = []() { return true; });
25 | };
26 | } // namespace mb_shell
--------------------------------------------------------------------------------
/src/shell/utils.cc:
--------------------------------------------------------------------------------
1 | #include "utils.h"
2 | #define _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 |
10 | #include
11 |
12 | #include "windows.h"
13 | #include
14 |
15 | #include "logger.h"
16 |
17 | std::wstring mb_shell::utf8_to_wstring(std::string const &str) {
18 | std::wstring_convert<
19 | std::conditional_t,
20 | std::codecvt_utf8_utf16>>
21 | converter;
22 | return converter.from_bytes(str);
23 | }
24 | std::string mb_shell::wstring_to_utf8(std::wstring const &str) {
25 | std::wstring_convert<
26 | std::conditional_t,
27 | std::codecvt_utf8_utf16