├── src ├── shell │ ├── script │ │ ├── ts │ │ │ ├── .gitignore │ │ │ ├── src │ │ │ │ ├── plugin │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── types.d.ts │ │ │ │ │ └── core.ts │ │ │ │ ├── menu │ │ │ │ │ ├── index.ts │ │ │ │ │ └── constants.ts │ │ │ │ ├── type_entry.d.ts │ │ │ │ ├── global.d.ts │ │ │ │ ├── utils │ │ │ │ │ ├── network.ts │ │ │ │ │ ├── string.ts │ │ │ │ │ └── object.ts │ │ │ │ ├── compats │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── explorer_compat.ts │ │ │ │ │ └── onecommander_compat.ts │ │ │ │ ├── config_page │ │ │ │ │ ├── contexts.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── UpdatePage.tsx │ │ │ │ │ ├── PluginStore.tsx │ │ │ │ │ ├── PluginConfig.tsx │ │ │ │ │ ├── ConfigApp.tsx │ │ │ │ │ ├── Sidebar.tsx │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── utils.ts │ │ │ │ │ └── ContextMenuConfig.tsx │ │ │ │ ├── jsx.d.ts │ │ │ │ └── entry.ts │ │ │ ├── tsconfig.json │ │ │ ├── package.json │ │ │ ├── build.js │ │ │ └── build-types.js │ │ ├── binding_types.h │ │ ├── quickjspp.cc │ │ ├── script.h │ │ ├── quickjs │ │ │ ├── libregexp-opcode.h │ │ │ ├── quickjs-c-atomics.h │ │ │ ├── dtoa.h │ │ │ ├── list.h │ │ │ ├── libregexp.h │ │ │ ├── libunicode.h │ │ │ └── builtin-array-fromasync.h │ │ └── binding_types_breeze_ui.h │ ├── error_handler.h │ ├── qjs_sanitizer.h │ ├── fix_win11_menu.h │ ├── entry.h │ ├── build_info.h.in │ ├── contextmenu │ │ ├── hooks.h │ │ ├── menu_render.h │ │ ├── contextmenu.h │ │ ├── menu_render.cc │ │ └── menu_widget.h │ ├── logger.h │ ├── taskbar │ │ ├── taskbar.h │ │ ├── taskbar_widget.h │ │ └── taskbar.cc │ ├── logger.cc │ ├── widgets │ │ ├── background_widget.h │ │ └── background_widget.cc │ ├── window_proc_hook.h │ ├── res_string_loader.h │ ├── paint_color.h │ ├── window_proc_hook.cc │ ├── utils.h │ ├── config.h │ ├── paint_color.cc │ ├── qjs_sanitizer.cc │ ├── config.cc │ ├── entry.cc │ └── error_handler.cc ├── asan │ └── asan_main.cc ├── inject │ └── data_directory.inc └── ui_test │ └── test.cc ├── .gitattributes ├── .clangd ├── .clang-format ├── resources ├── icon.7z ├── bloom.webp ├── code.webp ├── icon.png ├── icon.webp ├── inject.webp ├── icon-small.png ├── inject-en.webp ├── preview1.webp ├── breeze-interface └── animated-preview1.webp ├── scripts ├── debug.cmd ├── reformat.ps1 ├── bindgen.bat ├── rebuild.nu ├── rebuild_taskbar.ps1 ├── rebuild_asan.ps1 ├── left_click.ahk ├── right_click.ahk ├── rebuild.ps1 ├── additional-types.txt └── inject_onecommander.xpr64 ├── .gitignore ├── .gitmodules ├── .vscode └── settings.json ├── DONATE.md ├── dependencies ├── blook.lua └── breeze-ui.lua ├── README_zh.md ├── .github └── workflows │ └── xmake.yml ├── README.md ├── xmake.lua ├── CONFIG_zh.md └── CONFIG.md /src/shell/script/ts/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.h linguist-vendored 2 | *.c linguist-vendored -------------------------------------------------------------------------------- /.clangd: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | Add: ["-std:latest", "/clang:-std=c++26"] -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -4 4 | IndentWidth: 4 5 | ... 6 | -------------------------------------------------------------------------------- /resources/icon.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/std-microblock/breeze-shell/HEAD/resources/icon.7z -------------------------------------------------------------------------------- /scripts/debug.cmd: -------------------------------------------------------------------------------- 1 | set LLDB_USE_NATIVE_PDB_READER=1 2 | xmake b shell_test 3 | xmake r -d shell_test -------------------------------------------------------------------------------- /src/shell/script/ts/src/plugin/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constants'; 2 | export * from './core'; -------------------------------------------------------------------------------- /resources/bloom.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/std-microblock/breeze-shell/HEAD/resources/bloom.webp -------------------------------------------------------------------------------- /resources/code.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/std-microblock/breeze-shell/HEAD/resources/code.webp -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/std-microblock/breeze-shell/HEAD/resources/icon.png -------------------------------------------------------------------------------- /resources/icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/std-microblock/breeze-shell/HEAD/resources/icon.webp -------------------------------------------------------------------------------- /src/shell/error_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | namespace mb_shell { 3 | void install_error_handlers(); 4 | } -------------------------------------------------------------------------------- /src/shell/script/ts/src/menu/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constants'; 2 | export * from './configMenu'; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | compile_commands.json 3 | .xmake 4 | .cache 5 | build_info.h 6 | /test 7 | Xenos.log -------------------------------------------------------------------------------- /resources/inject.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/std-microblock/breeze-shell/HEAD/resources/inject.webp -------------------------------------------------------------------------------- /resources/icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/std-microblock/breeze-shell/HEAD/resources/icon-small.png -------------------------------------------------------------------------------- /resources/inject-en.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/std-microblock/breeze-shell/HEAD/resources/inject-en.webp -------------------------------------------------------------------------------- /resources/preview1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/std-microblock/breeze-shell/HEAD/resources/preview1.webp -------------------------------------------------------------------------------- /resources/breeze-interface: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/std-microblock/breeze-shell/HEAD/resources/breeze-interface -------------------------------------------------------------------------------- /src/shell/script/binding_types.h: -------------------------------------------------------------------------------- 1 | // Forward header for binding types in the script shell 2 | #include "binding_types.hpp" -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dependencies/glfw"] 2 | path = dependencies/glfw 3 | url = https://github.com/breeze-shell/glfw 4 | -------------------------------------------------------------------------------- /resources/animated-preview1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/std-microblock/breeze-shell/HEAD/resources/animated-preview1.webp -------------------------------------------------------------------------------- /src/shell/qjs_sanitizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace mb_shell { 4 | void qjs_sanitizer_enable(); 5 | } // namespace mb_shell -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.ignoreCMakeListsMissing": true, 3 | "files.associations": { 4 | "xstring": "cpp" 5 | } 6 | } -------------------------------------------------------------------------------- /src/shell/fix_win11_menu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | namespace mb_shell { 3 | struct fix_win11_menu { 4 | static void install(); 5 | }; 6 | } // namespace mb_shell -------------------------------------------------------------------------------- /scripts/reformat.ps1: -------------------------------------------------------------------------------- 1 | Get-ChildItem -Path ./src -Recurse -Include "*.h", "*.cpp", "*.hpp", "*.cc" | ForEach-Object { 2 | clang-format -i -style=file $_.FullName 3 | } -------------------------------------------------------------------------------- /src/shell/script/ts/src/type_entry.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | export {} -------------------------------------------------------------------------------- /scripts/bindgen.bat: -------------------------------------------------------------------------------- 1 | npx breeze-bindgen@latest -i src/shell/script/binding_types.hpp --nameFilter mb_shell::js -o src/shell/script --extTypesPath scripts/additional-types.txt --tsModuleName mshell -------------------------------------------------------------------------------- /DONATE.md: -------------------------------------------------------------------------------- 1 | ### Donate to this project 2 | Donate to me if you like the project~ 3 | 4 | #### 中国大陆 5 | 6 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /scripts/rebuild.nu: -------------------------------------------------------------------------------- 1 | chcp 65001 2 | ps -l | where name == "explorer.exe" | where command =~ "/factory,{75dff2b7-6936-4c06-a8bb-676a7b00b24b}" | each { kill -f $in.pid; } 3 | 4 | xmake b --yes inject 5 | xmake b --yes shell 6 | xmake r inject new -------------------------------------------------------------------------------- /src/asan/asan_main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | extern "C" __declspec(dllimport) void func(); 4 | 5 | int main() { 6 | func(); 7 | while (1) { 8 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 9 | } 10 | } -------------------------------------------------------------------------------- /scripts/rebuild_taskbar.ps1: -------------------------------------------------------------------------------- 1 | $pids = (Get-WmiObject Win32_Process -Filter "name = 'rundll32.exe'" | where { $_.CommandLine -like 'taskbar' }).ProcessId 2 | foreach ($pidx in $pids) { 3 | Stop-Process -Id $pidx -Force 4 | } 5 | 6 | xmake r --yes shell taskbar -- -taskbar -------------------------------------------------------------------------------- /src/shell/script/ts/src/plugin/constants.ts: -------------------------------------------------------------------------------- 1 | export const PLUGIN_SOURCES = { 2 | 'Github Raw': 'https://raw.githubusercontent.com/breeze-shell/plugins-packed/refs/heads/main/', 3 | 'Enlysure': 'https://breeze.enlysure.com/', 4 | 'Enlysure Shanghai': 'https://breeze-c.enlysure.com/' 5 | } -------------------------------------------------------------------------------- /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/script/ts/src/global.d.ts: -------------------------------------------------------------------------------- 1 | import { ShellPluginHelper } from "./plugin"; 2 | 3 | declare global { 4 | var plugin: ShellPluginHelper; 5 | var on_plugin_menu: { 6 | [key: string]: any; 7 | } 8 | 9 | var React: typeof import('react'); 10 | var createRenderer: typeof import('./react/renderer').createRenderer; 11 | } -------------------------------------------------------------------------------- /scripts/rebuild_asan.ps1: -------------------------------------------------------------------------------- 1 | $pids = (Get-WmiObject Win32_Process -Filter "name = 'rundll32.exe'" | where { $_.CommandLine -like '-breeze-asan' }).ProcessId 2 | foreach ($pidx in $pids) { 3 | Stop-Process -Id $pidx -Force 4 | } 5 | 6 | xmake f --toolchain=clang-cl -m releasedbg -y --asan=y -c 7 | if ($LASTEXITCODE -ne 0) { 8 | exit $LASTEXITCODE 9 | } 10 | xmake r --yes -v asan_test -------------------------------------------------------------------------------- /src/shell/contextmenu/hooks.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace mb_shell { 6 | struct context_menu_hooks { 7 | static std::atomic_int block_js_reload; 8 | static void install_NtUserTrackPopupMenuEx_hook(); 9 | static void install_SHCreateDefaultContextMenu_hook(); 10 | static void install_GetUIObjectOf_hook(); 11 | }; // namespace context_menu_hooks 12 | } // namespace mb_shell -------------------------------------------------------------------------------- /src/shell/script/ts/src/utils/network.ts: -------------------------------------------------------------------------------- 1 | import * as shell from "mshell" 2 | 3 | export const get_async = url => { 4 | url = url.replaceAll('//', '/').replaceAll(':/', '://') 5 | shell.println(url) 6 | return new Promise((resolve, reject) => { 7 | shell.network.get_async(encodeURI(url), data => { 8 | resolve(data) 9 | }, err => { 10 | reject(err) 11 | }) 12 | }) 13 | } -------------------------------------------------------------------------------- /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 | } // namespace mb_shell -------------------------------------------------------------------------------- /src/shell/script/ts/src/compats/index.ts: -------------------------------------------------------------------------------- 1 | import { breeze } from "mshell"; 2 | import { doOneCommanderCompat } from "./onecommander_compat"; 3 | import { doExplorerCompat } from "./explorer_compat"; 4 | 5 | export const doCompats = () => { 6 | if (breeze.current_process_name() == "OneCommander.exe") 7 | doOneCommanderCompat(); 8 | else if (breeze.current_process_name() == "explorer.exe") 9 | doExplorerCompat(); 10 | } -------------------------------------------------------------------------------- /scripts/left_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 {LButton} 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 f --toolchain=clang-cl -m releasedbg -y -c 7 | xmake b --yes inject 8 | xmake b --yes shell 9 | if ($LASTEXITCODE -ne 0) { 10 | exit $LASTEXITCODE 11 | } 12 | xmake r inject new -------------------------------------------------------------------------------- /scripts/additional-types.txt: -------------------------------------------------------------------------------- 1 | declare module "mshell" { 2 | export function println(...args: any[]): void; 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 | } -------------------------------------------------------------------------------- /src/shell/taskbar/taskbar.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "breeze_ui/ui.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | namespace mb_shell { 12 | struct taskbar_render { 13 | MONITORINFO monitor; 14 | ui::render_target rt; 15 | enum class menu_position { top, bottom } position = menu_position::bottom; 16 | 17 | std::expected init(); 18 | }; 19 | } // namespace mb_shell -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /scripts/inject_onecommander.xpr64: -------------------------------------------------------------------------------- 1 | 2 | E:\breeze-shell\build\windows\x64\releasedbg\shell.dll 3 | 0 4 | OneCommander.exe 5 | 0 6 | 0 7 | 0 8 | 0 9 | 0 10 | 0 11 | 0 12 | 0 13 | 0 14 | 0 15 | 0 16 | C:/ 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/shell/script/ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "jsx": "react-jsx", 5 | "jsxImportSource": "react", 6 | "module": "node16", 7 | "moduleResolution": "node16", 8 | "types": [ 9 | "./src/jsx.d.ts", 10 | "../binding_types.d.ts", 11 | "./src/global.d.ts" 12 | ], 13 | "esModuleInterop": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "strict": false, 16 | "skipLibCheck": true, 17 | "emitDeclarationOnly": true, 18 | "declaration": true, 19 | "declarationDir": "./dist/types" 20 | } 21 | } -------------------------------------------------------------------------------- /dependencies/blook.lua: -------------------------------------------------------------------------------- 1 | package("blook") 2 | set_description("A modern C++ library for hacking.") 3 | set_license("GPL-3.0") 4 | 5 | add_urls("https://github.com/std-microblock/blook.git") 6 | 7 | add_configs("shared", {description = "Build shared library.", default = false, type = "boolean", readonly = true}) 8 | 9 | if is_plat("windows") then 10 | add_syslinks("advapi32") 11 | end 12 | 13 | add_deps("zasm 2024.05.14") 14 | 15 | on_install("windows", function (package) 16 | import("package.tools.xmake").install(package, {}, {target = "blook"}) 17 | end) 18 | -------------------------------------------------------------------------------- /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/script/ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "breeze-shell-types", 3 | "packageManager": "yarn@1.22.19", 4 | "files": [ 5 | "dist", 6 | "package.json" 7 | ], 8 | "scripts": { 9 | "build": "node build.js", 10 | "build-types": "node build-types.js", 11 | "prepublishOnly": "yarn build-types" 12 | }, 13 | "devDependencies": { 14 | "@types/react": "18", 15 | "@types/react-reconciler": "^0.28.9", 16 | "esbuild": "^0.25.5", 17 | "react": "18", 18 | "react-reconciler": "0.29.2" 19 | }, 20 | "version": "0.0.17", 21 | "types": "./dist/types/type_entry.d.ts", 22 | "dependencies": { 23 | "breeze-shell-types": "^0.0.14" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/shell/widgets/background_widget.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "breeze_ui/animator.h" 4 | #include "breeze_ui/extra_widgets.h" 5 | #include "breeze_ui/widget.h" 6 | #include "nanovg.h" 7 | #include 8 | 9 | namespace mb_shell { 10 | 11 | class background_widget : public ui::widget { 12 | public: 13 | using super = ui::widget; 14 | 15 | background_widget(bool is_main); 16 | 17 | void render(ui::nanovg_context ctx) override; 18 | void update(ui::update_context &ctx) override; 19 | 20 | ui::sp_anim_float opacity; 21 | ui::sp_anim_float radius; 22 | NVGcolor bg_color; 23 | 24 | private: 25 | std::shared_ptr bg_impl; 26 | }; 27 | 28 | } // namespace mb_shell -------------------------------------------------------------------------------- /src/shell/script/ts/src/utils/string.ts: -------------------------------------------------------------------------------- 1 | export const splitIntoLines = (str, maxLen) => { 2 | const lines = [] 3 | // one chinese char = 2 english char 4 | const maxLenBytes = maxLen * 2 5 | for (let i = 0; i < str.length; i) { 6 | let x = 0; 7 | let line = str.substr(i, maxLenBytes) 8 | while (x < maxLen && line.length > x) { 9 | if (line.charCodeAt(x) > 255) { 10 | x++ 11 | } 12 | 13 | if (line.charAt(x) === '\n') { 14 | x++; 15 | break 16 | } 17 | 18 | x++ 19 | } 20 | lines.push(line.substr(0, x).trim()) 21 | i += x 22 | } 23 | 24 | return lines 25 | } -------------------------------------------------------------------------------- /src/shell/script/quickjspp.cc: -------------------------------------------------------------------------------- 1 | #include "quickjspp.hpp" 2 | #include 3 | 4 | namespace qjs { 5 | thread_local Context *Context::current; 6 | void wait_with_msgloop(std::function f) { 7 | auto this_thread = GetCurrentThreadId(); 8 | bool completed_flag = false; 9 | auto thread_wait = std::thread([=, &completed_flag]() { 10 | f(); 11 | completed_flag = true; 12 | PostThreadMessageW(this_thread, WM_NULL, 0, 0); 13 | }); 14 | 15 | MSG msg; 16 | while (GetMessageW(&msg, nullptr, 0, 0)) { 17 | TranslateMessage(&msg); 18 | DispatchMessageW(&msg); 19 | 20 | if (completed_flag) { 21 | break; 22 | } 23 | } 24 | thread_wait.join(); 25 | } 26 | } // namespace qjs -------------------------------------------------------------------------------- /src/shell/taskbar/taskbar_widget.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "async_simple/Try.h" 3 | #include "breeze_ui/animator.h" 4 | #include "breeze_ui/extra_widgets.h" 5 | #include "breeze_ui/hbitmap_utils.h" 6 | #include "breeze_ui/nanovg_wrapper.h" 7 | #include "breeze_ui/ui.h" 8 | #include "breeze_ui/widget.h" 9 | #include "nanovg.h" 10 | #include "shell/config.h" 11 | #include "taskbar.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "async_simple/coro/Lazy.h" 18 | 19 | #include "shell/widgets/background_widget.h" 20 | 21 | namespace mb_shell::taskbar { 22 | struct taskbar_widget : public ui::flex_widget { 23 | taskbar_widget(); 24 | ~taskbar_widget(); 25 | }; 26 | } // namespace mb_shell::taskbar -------------------------------------------------------------------------------- /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 | 19 | 20 | public: 21 | std::atomic is_js_ready{false}; 22 | 23 | script_context(); 24 | void bind(); 25 | 26 | void watch_folder( 27 | const std::filesystem::path &path, 28 | std::function on_reload = []() { return true; }); 29 | }; 30 | } // namespace mb_shell -------------------------------------------------------------------------------- /src/shell/script/ts/src/utils/object.ts: -------------------------------------------------------------------------------- 1 | export const getNestedValue = (obj, path) => { 2 | const parts = path.split('.'); 3 | let current = obj; 4 | 5 | for (const part of parts) { 6 | if (current === undefined || current === null) return undefined; 7 | current = current[part]; 8 | } 9 | 10 | return current; 11 | }; 12 | 13 | export const setNestedValue = (obj, path, value) => { 14 | const parts = path.split('.'); 15 | let current = obj; 16 | 17 | for (let i = 0; i < parts.length - 1; i++) { 18 | const part = parts[i]; 19 | if (current[part] === undefined || current[part] === null) { 20 | current[part] = {}; 21 | } 22 | current = current[part]; 23 | } 24 | 25 | current[parts[parts.length - 1]] = value; 26 | return obj; 27 | }; -------------------------------------------------------------------------------- /src/shell/contextmenu/menu_render.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "breeze_ui/ui.h" 3 | #include "contextmenu.h" 4 | #include "shell/utils.h" 5 | #include 6 | #include 7 | 8 | namespace mb_shell { 9 | struct menu_render { 10 | std::shared_ptr rt; 11 | std::optional selected_menu; 12 | bool light_color = is_light_mode(); 13 | static std::optional current; 14 | 15 | menu_render() = delete; 16 | menu_render(std::shared_ptr rt, 17 | std::optional selected_menu); 18 | 19 | ~menu_render(); 20 | const menu_render &operator=(const menu_render &) = delete; 21 | menu_render(menu_render &&t); 22 | menu_render &operator=(menu_render &&t); 23 | static menu_render create(int x, int y, menu menu, bool run_js = true); 24 | }; 25 | } // namespace mb_shell -------------------------------------------------------------------------------- /src/shell/script/ts/build.js: -------------------------------------------------------------------------------- 1 | const esbuild = require('esbuild'); 2 | 3 | async function build() { 4 | try { 5 | await esbuild.build({ 6 | entryPoints: ['src/entry.ts'], 7 | bundle: true, 8 | format: 'esm', 9 | platform: 'browser', 10 | outfile: '../script.js', 11 | external: ['mshell'], 12 | minify: true, 13 | banner: { 14 | js: `// Generated from project in ./ts 15 | // Don't edit this file directly! 16 | 17 | import * as __mshell from "mshell"; 18 | const setTimeout = __mshell.infra.setTimeout; 19 | const clearTimeout = __mshell.infra.clearTimeout; 20 | ` 21 | }, 22 | jsx: "transform", 23 | tsconfigRaw: "{}", 24 | jsxFactory: "h", 25 | jsxFragment: "Fragment" 26 | }); 27 | 28 | console.log('Build completed successfully'); 29 | } catch (error) { 30 | console.error('Build failed:', error); 31 | process.exit(1); 32 | } 33 | } 34 | 35 | build(); -------------------------------------------------------------------------------- /src/shell/script/ts/src/plugin/types.d.ts: -------------------------------------------------------------------------------- 1 | 2 | type FieldPath = K extends keyof T ? T[K] : K extends `${infer K1}.${infer K2}` ? K1 extends keyof T ? FieldPath : never : never; 3 | 4 | declare function plugin(import_meta: { url: string }, default_config?: T): { 5 | i18n: { 6 | define(lang: string, data: { [key: string]: string }): void; 7 | t(key: string): string; 8 | }; 9 | set_on_menu(callback: (m: 10 | import('mshell').menu_controller 11 | ) => void): void; 12 | config_directory: string; 13 | config: { 14 | read_config(): void; 15 | write_config(): void; 16 | get(key: K): FieldPath; 17 | set(key: K, value: FieldPath): void; 18 | all(): T; 19 | }; 20 | log(...args: any[]): void; 21 | }; 22 | 23 | declare type ShellPluginHelper = ReturnType; 24 | 25 | export { 26 | ShellPluginHelper 27 | } -------------------------------------------------------------------------------- /src/shell/script/ts/build-types.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const spawn = require('child_process').spawn; 3 | const path = require('path'); 4 | 5 | spawn('cmd', ['/c', 'tsc'], { stdio: 'inherit' }); 6 | 7 | // copy .d.ts to dist recursively 8 | const listFile = path => { 9 | const files = fs.readdirSync(path); 10 | let result = []; 11 | files.forEach(file => { 12 | const fullPath = `${path}/${file}`; 13 | if (fs.statSync(fullPath).isDirectory()) { 14 | result = result.concat(listFile(fullPath)); 15 | } else if (file.endsWith('.d.ts')) { 16 | result.push(fullPath); 17 | } 18 | }); 19 | return result; 20 | } 21 | 22 | const dtsFiles = listFile('./src'); 23 | dtsFiles.forEach(file => { 24 | const dest = file.replace('./src', './dist/types'); 25 | fs.mkdirSync(path.dirname(dest), { recursive: true }); 26 | fs.copyFileSync(file, dest); 27 | }); 28 | 29 | fs.mkdirSync('./dist/types', { recursive: true }); 30 | fs.copyFileSync('../binding_types.d.ts', './dist/types/binding_types.d.ts'); -------------------------------------------------------------------------------- /src/shell/window_proc_hook.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | namespace mb_shell { 9 | struct window_proc_hook { 10 | void *hwnd; 11 | void *original_proc; 12 | void *hooked_proc = nullptr; 13 | bool installed = false; 14 | 15 | std::vector(void *, void *, size_t, size_t, 16 | size_t)>> 17 | hooks; 18 | std::queue> tasks; 19 | 20 | void send_null(); 21 | auto add_task(auto &&f) -> std::future> { 22 | if (!installed) 23 | throw std::runtime_error("Hook not installed"); 24 | using return_type = std::invoke_result_t; 25 | auto task = std::make_shared>( 26 | std::forward(f)); 27 | std::future res = task->get_future(); 28 | tasks.emplace([task]() { (*task)(); }); 29 | send_null(); 30 | return res; 31 | } 32 | 33 | void install(void *hwnd); 34 | void uninstall(); 35 | ~window_proc_hook(); 36 | }; 37 | } // namespace mb_shell -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/shell/script/ts/src/config_page/contexts.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | // Context for context menu configuration 4 | export const ContextMenuContext = createContext<{ config: any; update: (c: any) => void } | null>(null); 5 | 6 | // Context for debug console state 7 | export const DebugConsoleContext = createContext<{ value: boolean; update: (v: boolean) => void } | null>(null); 8 | 9 | // Context for plugin load order 10 | export const PluginLoadOrderContext = createContext<{ order: any[]; update: (o: any[]) => void } | null>(null); 11 | 12 | // Context for update data 13 | export const UpdateDataContext = createContext<{ updateData: any; setUpdateData: (d: any) => void } | null>(null); 14 | 15 | // Context for notifications (error and loading messages) 16 | export const NotificationContext = createContext<{ 17 | errorMessage: string | null; 18 | setErrorMessage: (msg: string | null) => void; 19 | loadingMessage: string | null; 20 | setLoadingMessage: (msg: string | null) => void; 21 | } | null>(null); 22 | 23 | // Context for plugin source management 24 | export const PluginSourceContext = createContext<{ 25 | currentPluginSource: string; 26 | setCurrentPluginSource: (source: string) => void; 27 | cachedPluginIndex: any; 28 | setCachedPluginIndex: (index: any) => void; 29 | } | null>(null); -------------------------------------------------------------------------------- /src/shell/res_string_loader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | namespace mb_shell { 8 | struct res_string_loader { 9 | struct res_string_identifier { 10 | size_t id; 11 | size_t module; 12 | 13 | bool operator<=>(const res_string_identifier &other) const { 14 | return std::tie(module, id) < std::tie(other.module, other.id); 15 | } 16 | 17 | bool operator==(const res_string_identifier &other) const { 18 | return module == other.module && id == other.id; 19 | } 20 | }; 21 | 22 | using string_id = std::variant; 23 | static string_id string_to_id(std::wstring str); 24 | static std::string string_to_id_string(std::wstring str); 25 | static std::string string_from_id_string(const std::string &str); 26 | static std::vector 27 | get_all_ids_of_string(const std::wstring &str); 28 | static void init_hook(); 29 | static void init_known_strings(); 30 | 31 | static void init(); 32 | }; 33 | } // namespace mb_shell 34 | 35 | namespace std { 36 | template <> struct hash { 37 | size_t operator()(const mb_shell::res_string_loader::res_string_identifier 38 | &id) const noexcept { 39 | return std::hash{}(id.id) ^ std::hash{}(id.module); 40 | } 41 | }; 42 | } // namespace std -------------------------------------------------------------------------------- /src/shell/script/ts/src/compats/explorer_compat.ts: -------------------------------------------------------------------------------- 1 | import { menu_controller, value_reset } from "mshell" 2 | 3 | export const doExplorerCompat = () => { 4 | menu_controller.add_menu_listener(ctx => { 5 | for (const items of ctx.menu.items) { 6 | const data = items.data() 7 | if (data.name_resid === '10580@SHELL32.dll' /* 清空回收站 */ || data.name === '清空回收站') { 8 | items.set_data({ 9 | disabled: false 10 | }) 11 | } 12 | 13 | if (data.name?.startsWith('NVIDIA ')) { 14 | items.set_data({ 15 | icon_svg: ``, 16 | icon_bitmap: new value_reset() 17 | }) 18 | } 19 | } 20 | }) 21 | } -------------------------------------------------------------------------------- /src/shell/paint_color.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | struct NVGcolor; 6 | 7 | namespace ui { 8 | struct nanovg_context; 9 | } 10 | 11 | namespace mb_shell { 12 | 13 | struct rgba_color { 14 | float r = 0; 15 | float g = 0; 16 | float b = 0; 17 | float a = 1; 18 | 19 | rgba_color() = default; 20 | rgba_color(float r, float g, float b, float a = 1) 21 | : r(r), g(g), b(b), a(a) {} 22 | 23 | static rgba_color from_string(const std::string &str); 24 | std::string to_string() const; 25 | 26 | rgba_color(const NVGcolor &color); 27 | NVGcolor nvg() const; 28 | // we want to pass this directly into fillColor( NVGcolor color) 29 | operator NVGcolor() const; 30 | }; 31 | 32 | struct paint_color { 33 | enum class type { 34 | solid, 35 | linear_gradient, 36 | radial_gradient 37 | } type = type::solid; 38 | rgba_color color = {0, 0, 0, 1}; // RGBA 39 | rgba_color color2 = {0, 0, 0, 1}; // RGBA for gradients 40 | float radius = 0; 41 | float radius2 = 0; 42 | float angle = 0; 43 | void apply_to_ctx(ui::nanovg_context &ctx, float x, float y, float width, 44 | float height) const; 45 | 46 | // supported patterns: 47 | // #RRGGBBAA 48 | // #RRGGBB 49 | // #RRGGBB00 50 | // rgba(R, G, B, A) 51 | // rgb(R, G, B) 52 | // linear-gradient(angle, color1, color2) 53 | // radial-gradient(radius, color1, color2) 54 | // solid(color) 55 | static paint_color from_string(const std::string &str); 56 | std::string to_string() const; 57 | }; 58 | } // namespace mb_shell -------------------------------------------------------------------------------- /dependencies/breeze-ui.lua: -------------------------------------------------------------------------------- 1 | package("breeze-glfw") 2 | set_base("glfw") 3 | set_urls("https://github.com/breeze-shell/glfw.git") 4 | add_versions("latest", "master") 5 | 6 | local BREEZE_UI_VER = "2025.12.14" 7 | local BREEZE_UI_HASH = "c197c45a1dc21590abec30e21327b53511c55a41" 8 | 9 | package("breeze-nanosvg") 10 | add_urls("https://github.com/std-microblock/breeze-ui.git") 11 | add_versions(BREEZE_UI_VER, BREEZE_UI_HASH) 12 | 13 | set_kind("library", {headeronly = true}) 14 | set_description("The breeze-nanosvg package") 15 | 16 | on_install("windows", function (package) 17 | import("package.tools.xmake").install(package) 18 | end) 19 | 20 | package("breeze-nanovg") 21 | add_urls("https://github.com/std-microblock/breeze-ui.git") 22 | add_versions(BREEZE_UI_VER, BREEZE_UI_HASH) 23 | 24 | set_description("The breeze-nanovg package") 25 | 26 | add_configs("shared", {description = "Build shared library.", default = false, type = "boolean", readonly = true}) 27 | 28 | on_install("windows", function (package) 29 | import("package.tools.xmake").install(package) 30 | end) 31 | 32 | 33 | package("breeze-ui") 34 | add_urls("https://github.com/std-microblock/breeze-ui.git") 35 | add_versions(BREEZE_UI_VER, BREEZE_UI_HASH) 36 | add_deps("breeze-glfw", "glad", "nanovg", "breeze-nanosvg", { 37 | public = true 38 | }) 39 | add_configs("shared", {description = "Build shared library.", default = false, type = "boolean", readonly = true}) 40 | 41 | if is_plat("windows") then 42 | add_syslinks("dwmapi", "shcore") 43 | end 44 | 45 | on_install("windows", function (package) 46 | import("package.tools.xmake").install(package) 47 | end) 48 | 49 | -------------------------------------------------------------------------------- /src/shell/script/ts/src/menu/constants.ts: -------------------------------------------------------------------------------- 1 | import * as shell from "mshell" 2 | 3 | export const languages = { 4 | 'zh-CN': { 5 | }, 6 | 'en-US': { 7 | '管理 Breeze Shell': 'Manage Breeze Shell', 8 | '插件市场 / 更新本体': 'Plugin Market / Update Shell', 9 | '加载中...': 'Loading...', 10 | '更新中...': 'Updating...', 11 | '新版本已下载,将于下次重启资源管理器生效': 'New version downloaded, will take effect next time the file manager is restarted', 12 | '更新失败: ': 'Update failed: ', 13 | '插件安装成功: ': 'Plugin installed: ', 14 | '当前源: ': 'Current source: ', 15 | '删除': 'Delete', 16 | '版本: ': 'Version: ', 17 | '作者: ': 'Author: ' 18 | } 19 | } 20 | 21 | export const ICON_EMPTY = new shell.value_reset() 22 | export const ICON_CHECKED = `` 23 | export const ICON_CHANGE = `` 24 | export const ICON_REPAIR = `` -------------------------------------------------------------------------------- /.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: branch@master 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 -v --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/*.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 | -------------------------------------------------------------------------------- /src/shell/window_proc_hook.cc: -------------------------------------------------------------------------------- 1 | #include "window_proc_hook.h" 2 | #include "blook/blook.h" 3 | 4 | #include 5 | #include 6 | 7 | namespace mb_shell { 8 | static std::unordered_set hooked_windows; 9 | 10 | void window_proc_hook::install(void *hwnd) { 11 | if (installed) 12 | uninstall(); 13 | this->hwnd = hwnd; 14 | this->original_proc = (void *)GetWindowLongPtrW((HWND)hwnd, GWLP_WNDPROC); 15 | 16 | this->hooked_proc = (void *)blook::Function::into_function_pointer( 17 | [this](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT { 18 | SetWindowLongPtrW((HWND)hwnd, GWLP_WNDPROC, 19 | (LONG_PTR)this->original_proc); 20 | 21 | std::optional callOriginal = std::nullopt; 22 | for (auto &f : this->hooks) { 23 | if (!callOriginal) 24 | callOriginal = f(hwnd, this->original_proc, msg, wp, lp); 25 | } 26 | 27 | while (!this->tasks.empty()) { 28 | this->tasks.front()(); 29 | this->tasks.pop(); 30 | } 31 | 32 | SetWindowLongPtrW((HWND)hwnd, GWLP_WNDPROC, 33 | (LONG_PTR)this->hooked_proc); 34 | 35 | return callOriginal ? *callOriginal 36 | : CallWindowProcW((WNDPROC)this->original_proc, 37 | hwnd, msg, wp, lp); 38 | }); 39 | 40 | SetWindowLongPtrW((HWND)hwnd, GWLP_WNDPROC, (LONG_PTR)this->hooked_proc); 41 | installed = true; 42 | } 43 | 44 | void window_proc_hook::uninstall() { 45 | SetWindowLongPtrW((HWND)hwnd, GWLP_WNDPROC, (LONG_PTR)original_proc); 46 | installed = false; 47 | } 48 | window_proc_hook::~window_proc_hook() { 49 | if (installed) { 50 | uninstall(); 51 | } 52 | } 53 | void window_proc_hook::send_null() { 54 | if (hwnd) { 55 | PostMessageW((HWND)hwnd, WM_NULL, 0, 0); 56 | } 57 | } 58 | } // namespace mb_shell -------------------------------------------------------------------------------- /src/shell/contextmenu/contextmenu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "breeze_ui/nanovg_wrapper.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | namespace mb_shell { 13 | struct menu_item; 14 | struct menu_widget; 15 | struct menu_render; 16 | struct menu { 17 | std::vector items; 18 | void *parent_window = nullptr; 19 | bool is_top_level = false; 20 | 21 | static menu 22 | construct_with_hmenu(HMENU hMenu, HWND hWnd, bool is_top = true, 23 | // This is for handling submenus; messages are required 24 | // to be forwarded to IContextMenu2::HandleMenuMsg for 25 | // submenus and owner-draw menus to work properly 26 | std::function 27 | HandleMenuMsg = {}); 28 | }; 29 | 30 | std::optional 31 | track_popup_menu(menu menu, int x, int y, 32 | std::function on_before_show = {}, 33 | bool run_js = true); 34 | 35 | struct owner_draw_menu_info { 36 | HBITMAP bitmap; 37 | int width, height; 38 | }; 39 | 40 | struct menu_item { 41 | enum class type { 42 | button, 43 | spacer, 44 | } type = type::button; 45 | 46 | std::optional owner_draw{}; 47 | std::optional name; 48 | std::optional> action; 49 | std::optional)>> submenu; 50 | std::optional icon_bitmap; 51 | std::optional icon_svg; 52 | std::optional hotkey; 53 | bool icon_updated = false; 54 | bool disabled = false; 55 | 56 | // the two below are only for information; set them changes nothing 57 | std::optional wID; 58 | std::optional name_resid; 59 | std::optional origin_name; 60 | }; 61 | } // namespace mb_shell -------------------------------------------------------------------------------- /src/shell/script/ts/src/compats/onecommander_compat.ts: -------------------------------------------------------------------------------- 1 | import * as shell from "mshell"; 2 | export const doOneCommanderCompat = () => { 3 | shell.menu_controller.add_menu_listener(m => { 4 | const do_action = (keys: string[]) => () => { 5 | m.menu.close(); 6 | shell.infra.setTimeout(() => { 7 | shell.win32.simulate_hotkeys(keys); 8 | }, 50); 9 | shell.infra.setTimeout(() => { 10 | shell.win32.simulate_hotkeys(keys); 11 | }, 70); 12 | shell.infra.setTimeout(() => { 13 | shell.win32.simulate_hotkeys(keys); 14 | }, 100); 15 | } 16 | 17 | for (const i of m.menu.items) { 18 | if (i.data().name === "重命名" || i.data().name === "Rename") { 19 | i.set_data({ 20 | action: do_action(['f2']) 21 | }) 22 | } 23 | } 24 | 25 | const fill = shell.breeze.is_light_theme() ? "fill=\"#000000\"" : "fill=\"#FFFFFF\""; 26 | const zh = shell.breeze.user_language().startsWith('zh'); 27 | const NEW_NAME = zh ? "新建" : "New"; 28 | const CREATE_FOLDER_NAME = zh ? "文件夹" : "Folder"; 29 | const CREATE_FILE_NAME = zh ? "文件" : "File"; 30 | m.menu.append_item_after({ 31 | name: NEW_NAME, 32 | submenu(m) { 33 | m.append_item({ 34 | name: CREATE_FOLDER_NAME, 35 | action: do_action(['ctrl', 'shift', 'n']), 36 | icon_svg: `` 37 | }) 38 | m.append_item({ 39 | name: CREATE_FILE_NAME, 40 | action: do_action(['ctrl', 'n']), 41 | icon_svg: `` 42 | }) 43 | } 44 | }, -2) 45 | }) 46 | } 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/shell/script/ts/src/jsx.d.ts: -------------------------------------------------------------------------------- 1 | import { breeze_paint } from "mshell"; 2 | 3 | declare module 'react' { 4 | namespace JSX { 5 | interface IntrinsicElements { 6 | flex: { 7 | padding?: number; 8 | paddingTop?: number; 9 | paddingRight?: number; 10 | paddingBottom?: number; 11 | paddingLeft?: number; 12 | backgroundColor?: string; 13 | borderColor?: string; 14 | borderRadius?: number; 15 | borderWidth?: number; 16 | backgroundPaint?: breeze_paint; 17 | borderPaint?: breeze_paint; 18 | onClick?: (key: number) => void; 19 | onMouseEnter?: () => void; 20 | onMouseLeave?: () => void; 21 | onMouseDown?: () => void; 22 | onMouseUp?: () => void; 23 | onMouseMove?: (x: number, y: number) => void; 24 | justifyContent?: 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly'; 25 | alignItems?: 'start' | 'center' | 'end' | 'stretch'; 26 | horizontal?: boolean; 27 | children?: React.ReactNode | React.ReactNode[]; 28 | key?: string | number; 29 | animatedVars?: string[]; 30 | x?: number; 31 | y?: number; 32 | width?: number; 33 | height?: number; 34 | autoSize?: boolean; 35 | gap?: number; 36 | flexGrow?: number; 37 | flexShrink?: number; 38 | maxHeight?: number; 39 | enableScrolling?: boolean; 40 | enableChildClipping?: boolean; 41 | cropOverflow?: boolean; 42 | }, 43 | text: { 44 | text?: string[] | string; 45 | fontSize?: number; 46 | color?: string; 47 | key?: string | number; 48 | animatedVars?: string[]; 49 | x?: number; 50 | y?: number; 51 | width?: number; 52 | height?: number; 53 | maxWidth?: number; 54 | }, 55 | img: { 56 | svg?: string; 57 | key?: string | number; 58 | animatedVars?: string[]; 59 | x?: number; 60 | y?: number; 61 | width?: number; 62 | height?: number; 63 | }, 64 | spacer: { 65 | size?: number; 66 | key?: string | number; 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/shell/script/ts/src/entry.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | globalThis.h = React.createElement 3 | globalThis.Fragment = React.Fragment 4 | 5 | import * as shell from "mshell" 6 | import { plugin } from "./plugin"; 7 | 8 | import { createRenderer } from "./react/renderer"; 9 | import { showConfigPage } from "./config_page"; 10 | import { doCompats } from "./compats"; 11 | 12 | const SVG_CONFIG = `` 13 | 14 | // remove possibly existing shell_old.dll if able to 15 | if (shell.fs.exists(shell.breeze.data_directory() + '/shell_old.dll')) { 16 | try { 17 | shell.fs.remove(shell.breeze.data_directory() + '/shell_old.dll') 18 | } catch (e) { 19 | shell.println('Failed to remove old shell.dll: ', e) 20 | } 21 | } 22 | shell.menu_controller.add_menu_listener(ctx => { 23 | if (ctx.context.folder_view?.current_path.startsWith(shell.breeze.data_directory().replaceAll('/', '\\'))) { 24 | ctx.menu.prepend_menu({ 25 | action() { 26 | showConfigPage() 27 | }, 28 | name: "Breeze Config", 29 | icon_svg: SVG_CONFIG 30 | }) 31 | } 32 | 33 | if (shell.breeze.should_show_settings_button()) 34 | ctx.screenside_button.add_button(SVG_CONFIG, () => { 35 | ctx.menu.close() 36 | showConfigPage() 37 | }) 38 | }) 39 | 40 | doCompats(); 41 | 42 | globalThis.plugin = plugin as any 43 | globalThis.React = React 44 | globalThis.createRenderer = createRenderer 45 | globalThis.showConfigPage = showConfigPage -------------------------------------------------------------------------------- /src/shell/taskbar/taskbar.cc: -------------------------------------------------------------------------------- 1 | #include "taskbar.h" 2 | #include "breeze_ui/widget.h" 3 | 4 | #include "taskbar_widget.h" 5 | 6 | #include 7 | 8 | #include "shell/config.h" 9 | 10 | namespace mb_shell { 11 | std::expected taskbar_render::init() { 12 | rt.transparent = true; 13 | rt.topmost = true; 14 | rt.decorated = false; 15 | rt.title = "Breeze Shell Taskbar"; 16 | if (auto res = rt.init(); !res) { 17 | return std::unexpected(res.error()); 18 | } 19 | 20 | std::println("Taskbar Monitor: {}, {}, {}, {}", monitor.rcMonitor.left, 21 | monitor.rcMonitor.top, monitor.rcMonitor.right, 22 | monitor.rcMonitor.bottom); 23 | 24 | int height = (monitor.rcMonitor.bottom - monitor.rcMonitor.top) / 20; 25 | rt.show(); 26 | config::current->apply_fonts_to_nvg(rt.nvg); 27 | 28 | bool top = position == menu_position::top; 29 | 30 | rt.resize(monitor.rcMonitor.right - monitor.rcMonitor.left, height); 31 | if (top) { 32 | rt.set_position(monitor.rcMonitor.left, monitor.rcMonitor.top); 33 | } else { 34 | rt.set_position(monitor.rcMonitor.left, 35 | monitor.rcMonitor.bottom - height); 36 | } 37 | 38 | APPBARDATA abd = {sizeof(APPBARDATA)}; 39 | abd.hWnd = (HWND)rt.hwnd(); 40 | abd.uEdge = top ? ABE_TOP : ABE_BOTTOM; 41 | abd.rc = 42 | top ? RECT{monitor.rcMonitor.left, monitor.rcMonitor.top, 43 | monitor.rcMonitor.right, monitor.rcMonitor.top + height} 44 | : RECT{monitor.rcMonitor.left, monitor.rcMonitor.bottom - height, 45 | monitor.rcMonitor.right, monitor.rcMonitor.bottom}; 46 | 47 | if (SHAppBarMessage(ABM_NEW, &abd) == 0) { 48 | return std::unexpected("Failed to register taskbar app"); 49 | } 50 | 51 | SHAppBarMessage(ABM_QUERYPOS, &abd); 52 | SHAppBarMessage(ABM_SETPOS, &abd); 53 | rt.set_position(abd.rc.left, abd.rc.top); 54 | abd.lParam = TRUE; 55 | SHAppBarMessage(ABM_ACTIVATE, &abd); 56 | SHAppBarMessage(ABM_WINDOWPOSCHANGED, &abd); 57 | 58 | auto taskbar = rt.root->emplace_child(); 59 | taskbar->width->reset_to( 60 | (monitor.rcMonitor.right - monitor.rcMonitor.left) / rt.dpi_scale); 61 | taskbar->height->reset_to(height); 62 | 63 | return {}; 64 | } 65 | } // namespace mb_shell -------------------------------------------------------------------------------- /src/shell/script/quickjs/libregexp-opcode.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Regular Expression Engine 3 | * 4 | * Copyright (c) 2017-2018 Fabrice Bellard 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | #ifdef DEF 26 | 27 | DEF(invalid, 1) /* never used */ 28 | DEF(char8, 2) /* 7 bits in fact */ 29 | DEF(char16, 3) 30 | DEF(char32, 5) 31 | DEF(dot, 1) 32 | DEF(any, 1) /* same as dot but match any character including line terminator */ 33 | DEF(line_start, 1) 34 | DEF(line_end, 1) 35 | DEF(goto, 5) 36 | DEF(split_goto_first, 5) 37 | DEF(split_next_first, 5) 38 | DEF(match, 1) 39 | DEF(save_start, 2) /* save start position */ 40 | DEF(save_end, 2) /* save end position, must come after saved_start */ 41 | DEF(save_reset, 3) /* reset save positions */ 42 | DEF(loop, 5) /* decrement the top the stack and goto if != 0 */ 43 | DEF(push_i32, 5) /* push integer on the stack */ 44 | DEF(drop, 1) 45 | DEF(word_boundary, 1) 46 | DEF(not_word_boundary, 1) 47 | DEF(back_reference, 2) 48 | DEF(backward_back_reference, 2) /* must come after back_reference */ 49 | DEF(range, 3) /* variable length */ 50 | DEF(range32, 3) /* variable length */ 51 | DEF(lookahead, 5) 52 | DEF(negative_lookahead, 5) 53 | DEF(push_char_pos, 1) /* push the character position on the stack */ 54 | DEF(check_advance, 1) /* pop one stack element and check that it is different from the character position */ 55 | DEF(prev, 1) /* go to the previous char */ 56 | DEF(simple_greedy_quant, 17) 57 | 58 | #endif /* DEF */ 59 | -------------------------------------------------------------------------------- /src/shell/script/quickjs/quickjs-c-atomics.h: -------------------------------------------------------------------------------- 1 | /* 2 | * QuickJS C atomics definitions 3 | * 4 | * Copyright (c) 2023 Marcin Kolny 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | #if (defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) 26 | // Use GCC builtins for version < 4.9 27 | # if((__GNUC__ << 16) + __GNUC_MINOR__ < ((4) << 16) + 9) 28 | # define GCC_BUILTIN_ATOMICS 29 | # endif 30 | #endif 31 | 32 | #ifdef GCC_BUILTIN_ATOMICS 33 | #define atomic_fetch_add(obj, arg) \ 34 | __atomic_fetch_add(obj, arg, __ATOMIC_SEQ_CST) 35 | #define atomic_compare_exchange_strong(obj, expected, desired) \ 36 | __atomic_compare_exchange_n(obj, expected, desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) 37 | #define atomic_exchange(obj, desired) \ 38 | __atomic_exchange_n (obj, desired, __ATOMIC_SEQ_CST) 39 | #define atomic_load(obj) \ 40 | __atomic_load_n(obj, __ATOMIC_SEQ_CST) 41 | #define atomic_store(obj, desired) \ 42 | __atomic_store_n(obj, desired, __ATOMIC_SEQ_CST) 43 | #define atomic_fetch_or(obj, arg) \ 44 | __atomic_fetch_or(obj, arg, __ATOMIC_SEQ_CST) 45 | #define atomic_fetch_xor(obj, arg) \ 46 | __atomic_fetch_xor(obj, arg, __ATOMIC_SEQ_CST) 47 | #define atomic_fetch_and(obj, arg) \ 48 | __atomic_fetch_and(obj, arg, __ATOMIC_SEQ_CST) 49 | #define atomic_fetch_sub(obj, arg) \ 50 | __atomic_fetch_sub(obj, arg, __ATOMIC_SEQ_CST) 51 | #define _Atomic 52 | #else 53 | #include 54 | #endif 55 | -------------------------------------------------------------------------------- /src/shell/script/ts/src/config_page/index.ts: -------------------------------------------------------------------------------- 1 | // Export all components and utilities from the config module 2 | export { ConfigApp as default } from './ConfigApp'; 3 | 4 | // Export individual components 5 | export { default as Sidebar } from './Sidebar'; 6 | export { default as ContextMenuConfig } from './ContextMenuConfig'; 7 | export { default as UpdatePage } from './UpdatePage'; 8 | export { default as PluginStore } from './PluginStore'; 9 | export { default as PluginConfig } from './PluginConfig'; 10 | 11 | // Export UI components 12 | export { 13 | Button, 14 | Text, 15 | TextButton, 16 | Toggle, 17 | SidebarItem, 18 | PluginCheckbox, 19 | PluginMoreButton, 20 | PluginItem, 21 | SimpleMarkdownRender, 22 | iconElement 23 | } from './components'; 24 | 25 | // Export contexts 26 | export { 27 | ContextMenuContext, 28 | DebugConsoleContext, 29 | PluginLoadOrderContext, 30 | UpdateDataContext, 31 | NotificationContext 32 | } from './contexts'; 33 | 34 | // Export utilities 35 | export { 36 | getNestedValue, 37 | setNestedValue, 38 | useTranslation, 39 | getAllSubkeys, 40 | applyPreset, 41 | checkPresetMatch, 42 | getCurrentPreset, 43 | loadConfig, 44 | saveConfig, 45 | loadPlugins as reloadPlugins, 46 | togglePlugin, 47 | deletePlugin, 48 | isPluginInstalled, 49 | getPluginVersion 50 | } from './utils'; 51 | 52 | // Export constants 53 | export { 54 | languages, 55 | PLUGIN_SOURCES, 56 | ICON_CONTEXT_MENU, 57 | ICON_UPDATE, 58 | ICON_PLUGIN_STORE, 59 | ICON_PLUGIN_CONFIG, 60 | ICON_MORE_VERT, 61 | ICON_BREEZE, 62 | theme_presets, 63 | animation_presets, 64 | WINDOW_WIDTH, 65 | WINDOW_HEIGHT, 66 | SIDEBAR_WIDTH 67 | } from './constants'; 68 | 69 | import * as shell from "mshell"; 70 | import ConfigApp from './ConfigApp'; 71 | 72 | let existingConfigWindow: shell.breeze_ui.window | null = null; 73 | export const showConfigPage = () => { 74 | shell.breeze.set_can_reload_js(false); 75 | const win = shell.breeze_ui.window.create_ex("Breeze Config", 800, 600, () => { 76 | shell.breeze.set_can_reload_js(true) 77 | if (existingConfigWindow === win) 78 | existingConfigWindow = null; 79 | }); 80 | if (existingConfigWindow) 81 | existingConfigWindow.close(); 82 | existingConfigWindow = win; 83 | 84 | const widget = shell.breeze_ui.widgets_factory.create_flex_layout_widget(); 85 | const renderer = createRenderer(widget); 86 | renderer.render(React.createElement(ConfigApp, null)); 87 | win.set_root_widget(widget) 88 | } -------------------------------------------------------------------------------- /src/shell/script/ts/src/plugin/core.ts: -------------------------------------------------------------------------------- 1 | import * as shell from "mshell" 2 | import { getNestedValue, setNestedValue } from "../utils/object" 3 | 4 | export const config_directory_main = shell.breeze.data_directory() + '/config/'; 5 | 6 | export const config_dir_watch_callbacks = new Set<(path: string, type: number) => void>(); 7 | 8 | shell.fs.mkdir(config_directory_main) 9 | shell.fs.watch(config_directory_main, (path: string, type: number) => { 10 | for (const callback of config_dir_watch_callbacks) { 11 | callback(path, type) 12 | } 13 | }) 14 | 15 | globalThis.on_plugin_menu = {} 16 | 17 | export const plugin = (import_meta, default_config = {}) => { 18 | const CONFIG_FILE = 'config.json' 19 | 20 | const { name, url } = import_meta 21 | const languages = {} 22 | 23 | const nameNoExt = name.endsWith('.js') ? name.slice(0, -3) : name 24 | 25 | let config = default_config 26 | 27 | const on_reload_callbacks = new Set<(config: any) => void>() 28 | 29 | const plugin = { 30 | i18n: { 31 | define: (lang, data) => { 32 | languages[lang] = data 33 | }, 34 | t: (key) => { 35 | return languages[shell.breeze.user_language()][key] || key 36 | } 37 | }, 38 | set_on_menu: (callback: (m: shell.menu_controller) => void) => { 39 | globalThis.on_plugin_menu[nameNoExt] = callback 40 | }, 41 | config_directory: config_directory_main + nameNoExt + '/', 42 | config: { 43 | read_config() { 44 | if (shell.fs.exists(plugin.config_directory + CONFIG_FILE)) { 45 | try { 46 | config = JSON.parse(shell.fs.read(plugin.config_directory + CONFIG_FILE)) 47 | } catch (e) { 48 | shell.println(`[${name}] 配置文件解析失败: ${e}`) 49 | } 50 | } 51 | }, 52 | write_config() { 53 | shell.fs.write(plugin.config_directory + CONFIG_FILE, JSON.stringify(config, null, 4)) 54 | }, 55 | get(key) { 56 | return getNestedValue(config, key) || getNestedValue(default_config, key) || null 57 | }, 58 | set(key, value) { 59 | setNestedValue(config, key, value) 60 | plugin.config.write_config() 61 | }, 62 | all() { 63 | return config 64 | }, 65 | on_reload(callback) { 66 | const dispose = () => { 67 | on_reload_callbacks.delete(callback) 68 | } 69 | on_reload_callbacks.add(callback) 70 | return dispose 71 | } 72 | }, 73 | log(...args) { 74 | shell.println(`[${name}]`, ...args) 75 | } 76 | } 77 | 78 | shell.fs.mkdir(plugin.config_directory) 79 | plugin.config.read_config() 80 | config_dir_watch_callbacks.add((path, type) => { 81 | const relativePath = path.replace(config_directory_main, ''); 82 | if (relativePath === `${nameNoExt}\\${CONFIG_FILE}`) { 83 | shell.println(`[${name}] 配置文件变更: ${path} ${type}`) 84 | plugin.config.read_config() 85 | for (const callback of on_reload_callbacks) { 86 | callback(config) 87 | } 88 | } 89 | }) 90 | 91 | return plugin 92 | } -------------------------------------------------------------------------------- /src/shell/script/quickjs/dtoa.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Tiny float64 printing and parsing library 3 | * 4 | * Copyright (c) 2024 Fabrice Bellard 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | #ifndef DTOA_H 25 | #define DTOA_H 26 | 27 | //#define JS_DTOA_DUMP_STATS 28 | 29 | /* maximum number of digits for fixed and frac formats */ 30 | #define JS_DTOA_MAX_DIGITS 101 31 | 32 | /* radix != 10 is only supported with flags = JS_DTOA_FORMAT_FREE */ 33 | /* use as many digits as necessary */ 34 | #define JS_DTOA_FORMAT_FREE (0 << 0) 35 | /* use n_digits significant digits (1 <= n_digits <= JS_DTOA_MAX_DIGITS) */ 36 | #define JS_DTOA_FORMAT_FIXED (1 << 0) 37 | /* force fractional format: [-]dd.dd with n_digits fractional digits. 38 | 0 <= n_digits <= JS_DTOA_MAX_DIGITS */ 39 | #define JS_DTOA_FORMAT_FRAC (2 << 0) 40 | #define JS_DTOA_FORMAT_MASK (3 << 0) 41 | 42 | /* select exponential notation either in fixed or free format */ 43 | #define JS_DTOA_EXP_AUTO (0 << 2) 44 | #define JS_DTOA_EXP_ENABLED (1 << 2) 45 | #define JS_DTOA_EXP_DISABLED (2 << 2) 46 | #define JS_DTOA_EXP_MASK (3 << 2) 47 | 48 | #define JS_DTOA_MINUS_ZERO (1 << 4) /* show the minus sign for -0 */ 49 | 50 | /* only accepts integers (no dot, no exponent) */ 51 | #define JS_ATOD_INT_ONLY (1 << 0) 52 | /* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */ 53 | #define JS_ATOD_ACCEPT_BIN_OCT (1 << 1) 54 | /* accept O prefix as octal if radix == 0 and properly formed (Annex B) */ 55 | #define JS_ATOD_ACCEPT_LEGACY_OCTAL (1 << 2) 56 | /* accept _ between digits as a digit separator */ 57 | #define JS_ATOD_ACCEPT_UNDERSCORES (1 << 3) 58 | 59 | typedef struct { 60 | uint64_t mem[37]; 61 | } JSDTOATempMem; 62 | 63 | typedef struct { 64 | uint64_t mem[27]; 65 | } JSATODTempMem; 66 | 67 | /* return a maximum bound of the string length */ 68 | int js_dtoa_max_len(double d, int radix, int n_digits, int flags); 69 | /* return the string length */ 70 | int js_dtoa(char *buf, double d, int radix, int n_digits, int flags, 71 | JSDTOATempMem *tmp_mem); 72 | double js_atod(const char *str, const char **pnext, int radix, int flags, 73 | JSATODTempMem *tmp_mem); 74 | 75 | #ifdef JS_DTOA_DUMP_STATS 76 | void js_dtoa_dump_stats(void); 77 | #endif 78 | 79 | /* additional exported functions */ 80 | size_t u32toa(char *buf, uint32_t n); 81 | size_t i32toa(char *buf, int32_t n); 82 | size_t u64toa(char *buf, uint64_t n); 83 | size_t i64toa(char *buf, int64_t n); 84 | size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix); 85 | size_t i64toa_radix(char *buf, int64_t n, unsigned int radix); 86 | 87 | #endif /* DTOA_H */ 88 | -------------------------------------------------------------------------------- /src/shell/script/quickjs/list.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Linux klist like system 3 | * 4 | * Copyright (c) 2016-2017 Fabrice Bellard 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | #ifndef LIST_H 25 | #define LIST_H 26 | 27 | #ifndef NULL 28 | #include 29 | #endif 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | struct list_head { 36 | struct list_head *prev; 37 | struct list_head *next; 38 | }; 39 | 40 | #define LIST_HEAD_INIT(el) { &(el), &(el) } 41 | 42 | /* return the pointer of type 'type *' containing 'el' as field 'member' */ 43 | #define list_entry(el, type, member) container_of(el, type, member) 44 | 45 | static inline void init_list_head(struct list_head *head) 46 | { 47 | head->prev = head; 48 | head->next = head; 49 | } 50 | 51 | /* insert 'el' between 'prev' and 'next' */ 52 | static inline void __list_add(struct list_head *el, 53 | struct list_head *prev, struct list_head *next) 54 | { 55 | prev->next = el; 56 | el->prev = prev; 57 | el->next = next; 58 | next->prev = el; 59 | } 60 | 61 | /* add 'el' at the head of the list 'head' (= after element head) */ 62 | static inline void list_add(struct list_head *el, struct list_head *head) 63 | { 64 | __list_add(el, head, head->next); 65 | } 66 | 67 | /* add 'el' at the end of the list 'head' (= before element head) */ 68 | static inline void list_add_tail(struct list_head *el, struct list_head *head) 69 | { 70 | __list_add(el, head->prev, head); 71 | } 72 | 73 | static inline void list_del(struct list_head *el) 74 | { 75 | struct list_head *prev, *next; 76 | prev = el->prev; 77 | next = el->next; 78 | prev->next = next; 79 | next->prev = prev; 80 | el->prev = NULL; /* fail safe */ 81 | el->next = NULL; /* fail safe */ 82 | } 83 | 84 | static inline int list_empty(struct list_head *el) 85 | { 86 | return el->next == el; 87 | } 88 | 89 | #define list_for_each(el, head) \ 90 | for(el = (head)->next; el != (head); el = el->next) 91 | 92 | #define list_for_each_safe(el, el1, head) \ 93 | for(el = (head)->next, el1 = el->next; el != (head); \ 94 | el = el1, el1 = el->next) 95 | 96 | #define list_for_each_prev(el, head) \ 97 | for(el = (head)->prev; el != (head); el = el->prev) 98 | 99 | #define list_for_each_prev_safe(el, el1, head) \ 100 | for(el = (head)->prev, el1 = el->prev; el != (head); \ 101 | el = el1, el1 = el->prev) 102 | 103 | #ifdef __cplusplus 104 | } /* extern "C" { */ 105 | #endif 106 | 107 | #endif /* LIST_H */ 108 | -------------------------------------------------------------------------------- /src/shell/script/quickjs/libregexp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Regular Expression Engine 3 | * 4 | * Copyright (c) 2017-2018 Fabrice Bellard 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | #ifndef LIBREGEXP_H 25 | #define LIBREGEXP_H 26 | 27 | #include 28 | #include 29 | 30 | #include "libunicode.h" 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | #define LRE_FLAG_GLOBAL (1 << 0) 37 | #define LRE_FLAG_IGNORECASE (1 << 1) 38 | #define LRE_FLAG_MULTILINE (1 << 2) 39 | #define LRE_FLAG_DOTALL (1 << 3) 40 | #define LRE_FLAG_UNICODE (1 << 4) 41 | #define LRE_FLAG_STICKY (1 << 5) 42 | #define LRE_FLAG_INDICES (1 << 6) /* Unused by libregexp, just recorded. */ 43 | #define LRE_FLAG_NAMED_GROUPS (1 << 7) /* named groups are present in the regexp */ 44 | #define LRE_FLAG_UNICODE_SETS (1 << 8) 45 | 46 | #define LRE_RET_MEMORY_ERROR (-1) 47 | #define LRE_RET_TIMEOUT (-2) 48 | 49 | uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, 50 | const char *buf, size_t buf_len, int re_flags, 51 | void *opaque); 52 | int lre_get_capture_count(const uint8_t *bc_buf); 53 | int lre_get_flags(const uint8_t *bc_buf); 54 | const char *lre_get_groupnames(const uint8_t *bc_buf); 55 | int lre_exec(uint8_t **capture, 56 | const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen, 57 | int cbuf_type, void *opaque); 58 | 59 | int lre_parse_escape(const uint8_t **pp, int allow_utf16); 60 | bool lre_is_space(int c); 61 | 62 | void lre_byte_swap(uint8_t *buf, size_t len, bool is_byte_swapped); 63 | 64 | /* must be provided by the user */ 65 | bool lre_check_stack_overflow(void *opaque, size_t alloca_size); 66 | /* must be provided by the user, return non zero if time out */ 67 | int lre_check_timeout(void *opaque); 68 | void *lre_realloc(void *opaque, void *ptr, size_t size); 69 | 70 | /* JS identifier test */ 71 | extern uint32_t const lre_id_start_table_ascii[4]; 72 | extern uint32_t const lre_id_continue_table_ascii[4]; 73 | 74 | static inline int lre_js_is_ident_first(int c) 75 | { 76 | if ((uint32_t)c < 128) { 77 | return (lre_id_start_table_ascii[c >> 5] >> (c & 31)) & 1; 78 | } else { 79 | return lre_is_id_start(c); 80 | } 81 | } 82 | 83 | static inline int lre_js_is_ident_next(int c) 84 | { 85 | if ((uint32_t)c < 128) { 86 | return (lre_id_continue_table_ascii[c >> 5] >> (c & 31)) & 1; 87 | } else { 88 | /* ZWNJ and ZWJ are accepted in identifiers */ 89 | return lre_is_id_continue(c) || c == 0x200C || c == 0x200D; 90 | } 91 | } 92 | 93 | #ifdef __cplusplus 94 | } /* extern "C" { */ 95 | #endif 96 | 97 | #endif /* LIBREGEXP_H */ 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [Join Discord](https://discord.gg/MgpHk8pa3d) [中文简介 →](./README_zh.md) 2 | 3 | > [!WARNING] 4 | > This project is still in active development. File a bug report if you meet any! 5 | > 此项目仍在开发阶段,如果遇到问题请发送 Issue 6 | > 7 | > Both English and Chinese issues are accepted 8 | > Issue 中使用中文或英文均可 9 | 10 |
11 | 12 |

Breeze Shell

13 |
Bring fluency & delication back to Windows
14 |
15 | 16 |
17 |
18 | 19 | Breeze is an **alternative context menu** for Windows 10 and Windows 11. 20 | 21 | ## Fluent 22 | Breeze is designed with animations in mind. 23 | 24 | 25 | 26 | All animations are configurable and can be scaled and disabled as you want. 27 | ## Extensible 28 | 29 | Empowered by the embedded JavaScript script api, Breeze enables you to extend 30 | the functionalities of your context menu in a few lines of code. 31 | 32 | ```javascript 33 | shell.menu_controller.add_menu_listener((e) => { 34 | e.menu.add({ 35 | type: "button", 36 | name: "Shuffle Buttons", 37 | action: () => { 38 | for (const item of menus) { 39 | item.set_position(Math.floor(menus.length * Math.random())); 40 | } 41 | }, 42 | }, 0); 43 | }); 44 | ``` 45 | 46 | [See full bindings →](./src/shell/script/binding_types.d.ts) 47 | 48 | Send pull requests to [this repo](https://github.com/breeze-shell/plugins) to add your script to the plugin market! 49 | 50 | ## Configurable 51 | Breeze shell exposed a bunch of configurations ranging from item height to background radius method. Customize them as you need. 52 | 53 | [Configuration Document →](./CONFIG.md) 54 | 55 | The config menu of breeze-shell can be found as you open your `Data Folder` and right-click anywhere inside it. 56 | 57 | ## Lightweight & Fast 58 | 59 | Breeze uses breeze_ui, which is implemented to be a cross-platform, simple, 60 | animation-friendly and fast ui library for modern C++, with the support of both 61 | NanoVG and ThorVG render context. This allowed Breeze to have a delicated user 62 | interface in ~2MiB. 63 | 64 | # Try it out! 65 | 66 | Download and extract the zip, and Run `breeze.exe`. 67 |
68 | 69 |
70 | 71 | ### Compatibility 72 | | Name | ✳️ | 73 | |---|---| 74 | | Microsoft Explorer | ✅ | 75 | | OneCommander | ☑️ | 76 | 77 | ✅ - Fully compatible; ☑️ - Mostly works 78 | 79 | # Building 80 | 81 | Breeze uses xmake. You'd have to install xmake in your computer first. Then, 82 | type `xmake` in the project dir and follow the instructions. Both clang-cl and 83 | MSVC 2019+ can build this project. 84 | 85 | # Developing 86 | 87 | After building successfully once, you can oprn the project dir in VSCode for 88 | development. Install clangd plugin for full intellisense. 89 | 90 | # Credits 91 | 92 | #### Third-party libraries 93 | - https://github.com/std-microblock/blook 94 | - https://github.com/quickjs-ng/quickjs 95 | - https://github.com/ftk/quickjspp 96 | - https://github.com/getml/reflect-cpp 97 | - https://github.com/glfw/glfw 98 | - https://github.com/Dav1dde/glad 99 | - https://github.com/memononen/nanovg 100 | - https://github.com/memononen/nanosvg 101 | - https://github.com/freetype/freetype 102 | - https://github.com/qlibs/reflect 103 | 104 | #### Others 105 | - [@lipraty](https://github.com/lipraty) - Icon Design 106 | - [moudey/Shell](https://github.com/moudey/Shell) - Inspiration 107 | (All code in this rewrite is ORIGINAL and UNRELATED to moudey/Shell!) 108 | 109 | -------------------------------------------------------------------------------- /src/shell/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "nanovg.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "reflect.hpp" 17 | 18 | namespace mb_shell { 19 | std::string wstring_to_utf8(std::wstring const &str); 20 | std::wstring utf8_to_wstring(std::string const &str); 21 | bool is_win11_or_later(); 22 | bool is_light_mode(); 23 | bool is_acrylic_available(); 24 | std::optional env(const std::string &name); 25 | bool is_memory_readable(const void *ptr); 26 | NVGcolor parse_color(const std::string &str); 27 | std::string format_color(NVGcolor color); 28 | void set_thread_locale_utf8(); 29 | void set_thread_name(const std::string &name); 30 | 31 | std::vector split_string(const std::string &str, char delimiter); 32 | 33 | struct task_queue { 34 | public: 35 | task_queue(); 36 | 37 | ~task_queue(); 38 | 39 | template 40 | auto add_task(F &&f, Args &&...args) 41 | -> std::future> { 42 | using return_type = std::invoke_result_t; 43 | 44 | if (stop) { 45 | throw std::runtime_error("add_task called on stopped task_queue"); 46 | } 47 | 48 | auto task = std::make_shared>( 49 | std::bind(std::forward(f), std::forward(args)...)); 50 | 51 | std::future res = task->get_future(); 52 | 53 | { 54 | std::lock_guard lock(queue_mutex); 55 | tasks.emplace([task]() { (*task)(); }); 56 | } 57 | 58 | condition.notify_one(); 59 | return res; 60 | } 61 | 62 | private: 63 | void run(); 64 | 65 | std::thread worker; 66 | std::queue> tasks; 67 | std::mutex queue_mutex; 68 | std::condition_variable condition; 69 | bool stop; 70 | }; 71 | 72 | struct perf_counter { 73 | std::chrono::high_resolution_clock::time_point start; 74 | std::chrono::high_resolution_clock::time_point last_end; 75 | std::string name; 76 | void end(std::optional block_name = {}); 77 | perf_counter(std::string name); 78 | }; 79 | 80 | template constexpr std::string_view string_from_enum(E e) { 81 | return reflect::enum_name(e) | 82 | std::views::transform([](char c) { return c == '_' ? '-' : c; }) | 83 | std::ranges::to(); 84 | } 85 | 86 | template constexpr auto create_enum_map() { 87 | constexpr auto min = reflect::enum_min(E{}); 88 | constexpr auto max = reflect::enum_max(E{}); 89 | constexpr size_t count = max - min + 1; 90 | 91 | std::array, count> map{}; 92 | 93 | size_t index = 0; 94 | for (int i = min; i <= max; ++i) { 95 | E value = static_cast(i); 96 | auto name = reflect::enum_name(value); 97 | if (!name.empty() && name != "") { 98 | map[index++] = {name, value}; 99 | } 100 | } 101 | 102 | return map; 103 | } 104 | 105 | template 106 | constexpr std::optional enum_from_string(std::string_view str) { 107 | static constexpr auto enum_map = create_enum_map(); 108 | 109 | for (const auto &[name, value] : enum_map) { 110 | // accept both '-' and '_' as word separators 111 | if ((str | 112 | std::views::transform([](char c) { return c == '-' ? '_' : c; }) | 113 | std::ranges::to()) == name) { 114 | return value; 115 | } 116 | } 117 | return std::nullopt; 118 | } 119 | 120 | } // namespace mb_shell -------------------------------------------------------------------------------- /xmake.lua: -------------------------------------------------------------------------------- 1 | set_project("shell") 2 | local version = "0.1.32" 3 | 4 | option("asan") 5 | set_default(false) 6 | set_showmenu(true) 7 | set_description("Enable AddressSanitizer (ASan) support") 8 | option_end() 9 | 10 | set_exceptions("cxx") 11 | set_languages("c++2b") 12 | set_warnings("all") 13 | add_rules("plugin.compile_commands.autoupdate", {outputdir = "build"}) 14 | add_rules("mode.releasedbg") 15 | 16 | includes("dependencies/blook.lua") 17 | includes("dependencies/breeze-ui.lua") 18 | 19 | set_runtimes("MT") 20 | add_requires("breeze-glfw", {alias = "glfw"}) 21 | add_requires("blook dd51b45ad765274a0b5394b70d9982c948e26c74", "glad", 22 | "reflect-cpp", "wintoast v1.3.1", "cpptrace v0.8.3", "breeze-ui") 23 | 24 | if has_config("asan") then 25 | add_defines("_DISABLE_VECTOR_ANNOTATION", "_DISABLE_STRING_ANNOTATION", "_ASAN_") 26 | end 27 | 28 | add_requires("yalantinglibs b82a21925958b6c50deba3aa26a2737cdb814e27", { 29 | configs = { 30 | ssl = true 31 | } 32 | }) 33 | 34 | add_requireconfs("**.cinatra", { 35 | override = true, 36 | version = "e329293f6705649a6f1e8847ec845a7631179bb8" 37 | }) 38 | 39 | add_requireconfs("**.async_simple", { 40 | override = true, 41 | version = "18f3882be354d407af0f0674121dcddaeff36e26" 42 | }) 43 | 44 | target("ui_test") 45 | set_default(false) 46 | set_kind("binary") 47 | add_packages("breeze-ui") 48 | add_files("src/ui_test/*.cc") 49 | set_encodings("utf-8") 50 | add_tests("defualt") 51 | 52 | target("shell") 53 | set_kind("shared") 54 | add_headerfiles("src/shell/**.h") 55 | add_includedirs("src/", { 56 | public = true 57 | }) 58 | 59 | add_includedirs("src/shell/script/quickjs") 60 | 61 | add_defines("NOMINMAX", "WIN32_LEAN_AND_MEAN") 62 | add_packages("blook", "reflect-cpp", "wintoast", "cpptrace", "yalantinglibs", "breeze-ui") 63 | add_syslinks("oleacc", "ole32", "oleaut32", "uuid", "comctl32", "comdlg32", "gdi32", "user32", "shell32", "kernel32", "advapi32", "psapi", "Winhttp", "dbghelp") 64 | add_rules("utils.bin2c", { 65 | extensions = {".js"} 66 | }) 67 | set_version(version) 68 | set_configdir("src/shell") 69 | add_configfiles("src/shell/build_info.h.in") 70 | on_config(function (package) 71 | local git_commit_hash = os.iorun("git rev-parse --short HEAD"):trim() 72 | local git_branch_name = os.iorun("git describe --all"):trim() 73 | local build_date_time = os.date("%Y-%m-%d %H:%M:%S") 74 | package:set("configvar", "GIT_COMMIT_HASH", git_commit_hash or "null") 75 | package:set("configvar", "GIT_BRANCH_NAME", git_branch_name or "null") 76 | package:set("configvar", "BUILD_DATE_TIME", build_date_time) 77 | end) 78 | on_run(function (target) 79 | if is_host("windows") then 80 | local cmd = "rundll32.exe " .. target:targetfile() .. ",func" 81 | os.exec(cmd) 82 | end 83 | end) 84 | add_files("src/shell/script/script.js") 85 | add_files("src/shell/**.cc", "src/shell/**.c") 86 | set_encodings("utf-8") 87 | 88 | if has_config("asan") then 89 | set_policy("build.sanitizer.address", true) 90 | end 91 | 92 | target("asan_test") 93 | set_kind("binary") 94 | add_deps("shell") 95 | add_files("src/asan/asan_main.cc") 96 | if has_config("asan") then 97 | set_policy("build.sanitizer.address", true) 98 | end 99 | 100 | target("inject") 101 | set_kind("binary") 102 | add_syslinks("psapi", "user32", "shell32", "kernel32", "advapi32") 103 | add_files("src/inject/*.cc") 104 | add_packages("breeze-ui") 105 | set_basename("breeze") 106 | set_encodings("utf-8") 107 | add_rules("utils.bin2c", { 108 | extensions = {".png"} 109 | }) 110 | add_files("resources/icon-small.png") 111 | add_ldflags("/subsystem:windows") 112 | 113 | -------------------------------------------------------------------------------- /src/shell/script/ts/src/config_page/UpdatePage.tsx: -------------------------------------------------------------------------------- 1 | import * as shell from "mshell"; 2 | import { Button, Text, SimpleMarkdownRender } from "./components"; 3 | import { UpdateDataContext, NotificationContext, PluginSourceContext } from "./contexts"; 4 | import { useTranslation } from "./utils"; 5 | import { PLUGIN_SOURCES } from "./constants"; 6 | import { memo, useContext, useEffect, useState, useMemo } from "react"; 7 | 8 | const UpdatePage = memo(() => { 9 | const { updateData } = useContext(UpdateDataContext)!; 10 | const { setErrorMessage } = useContext(NotificationContext)!; 11 | const { currentPluginSource } = useContext(PluginSourceContext)!; 12 | const { t } = useTranslation(); 13 | const current_version = useMemo(() => shell.breeze.version(), []); 14 | 15 | const [exist_old_file, set_exist_old_file] = useState(false); 16 | const [isUpdating, setIsUpdating] = useState(false); 17 | 18 | useEffect(() => { 19 | set_exist_old_file(shell.fs.exists(shell.breeze.data_directory() + '/shell_old.dll')); 20 | }, []); 21 | 22 | if (!updateData) { 23 | return {t("加载中...")}; 24 | } 25 | 26 | const remote_version = updateData.shell.version; 27 | 28 | const updateShell = () => { 29 | if (isUpdating) return; 30 | 31 | setIsUpdating(true); 32 | const shellPath = shell.breeze.data_directory() + '/shell.dll'; 33 | const shellOldPath = shell.breeze.data_directory() + '/shell_old.dll'; 34 | const url = PLUGIN_SOURCES[currentPluginSource] + updateData.shell.path; 35 | 36 | const downloadNewShell = () => { 37 | shell.network.download_async(url, shellPath, () => { 38 | shell.println(t('新版本已下载,将于下次重启资源管理器生效')); 39 | setIsUpdating(false); 40 | set_exist_old_file(true); 41 | }, e => { 42 | shell.println(t('更新失败: ') + e); 43 | setIsUpdating(false); 44 | setErrorMessage(t('更新失败: ') + e); 45 | }); 46 | }; 47 | 48 | try { 49 | if (shell.fs.exists(shellPath)) { 50 | if (shell.fs.exists(shellOldPath)) { 51 | try { 52 | shell.fs.remove(shellOldPath); 53 | shell.fs.rename(shellPath, shellOldPath); 54 | downloadNewShell(); 55 | } catch (e) { 56 | shell.println(t('更新失败: ') + '无法移动当前文件'); 57 | setIsUpdating(false); 58 | setErrorMessage(t('更新失败: ') + '无法移动当前文件'); 59 | } 60 | } else { 61 | shell.fs.rename(shellPath, shellOldPath); 62 | downloadNewShell(); 63 | } 64 | } else { 65 | downloadNewShell(); 66 | } 67 | } catch (e) { 68 | shell.println(t('更新失败: ') + e); 69 | setIsUpdating(false); 70 | setErrorMessage(t('更新失败: ') + e); 71 | } 72 | }; 73 | 74 | return ( 75 | 76 | {t("插件市场")} 77 | 78 | {`当前版本: ${current_version}`} 79 | {`最新版本: ${remote_version}`} 80 | 85 | 86 | 87 | 更新日志: 88 | 89 | 90 | 91 | 92 | 93 | ); 94 | }); 95 | 96 | export default UpdatePage; -------------------------------------------------------------------------------- /src/shell/script/quickjs/libunicode.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Unicode utilities 3 | * 4 | * Copyright (c) 2017-2018 Fabrice Bellard 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | #ifndef LIBUNICODE_H 25 | #define LIBUNICODE_H 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | #define LRE_CC_RES_LEN_MAX 3 36 | 37 | typedef enum { 38 | UNICODE_NFC, 39 | UNICODE_NFD, 40 | UNICODE_NFKC, 41 | UNICODE_NFKD, 42 | } UnicodeNormalizationEnum; 43 | 44 | int lre_case_conv(uint32_t *res, uint32_t c, int conv_type); 45 | int lre_canonicalize(uint32_t c, bool is_unicode); 46 | bool lre_is_cased(uint32_t c); 47 | bool lre_is_case_ignorable(uint32_t c); 48 | 49 | /* char ranges */ 50 | 51 | typedef struct { 52 | int len; /* in points, always even */ 53 | int size; 54 | uint32_t *points; /* points sorted by increasing value */ 55 | void *mem_opaque; 56 | void *(*realloc_func)(void *opaque, void *ptr, size_t size); 57 | } CharRange; 58 | 59 | typedef enum { 60 | CR_OP_UNION, 61 | CR_OP_INTER, 62 | CR_OP_XOR, 63 | } CharRangeOpEnum; 64 | 65 | void cr_init(CharRange *cr, void *mem_opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size)); 66 | void cr_free(CharRange *cr); 67 | int cr_realloc(CharRange *cr, int size); 68 | int cr_copy(CharRange *cr, const CharRange *cr1); 69 | 70 | static inline int cr_add_point(CharRange *cr, uint32_t v) 71 | { 72 | if (cr->len >= cr->size) { 73 | if (cr_realloc(cr, cr->len + 1)) 74 | return -1; 75 | } 76 | cr->points[cr->len++] = v; 77 | return 0; 78 | } 79 | 80 | static inline int cr_add_interval(CharRange *cr, uint32_t c1, uint32_t c2) 81 | { 82 | if ((cr->len + 2) > cr->size) { 83 | if (cr_realloc(cr, cr->len + 2)) 84 | return -1; 85 | } 86 | cr->points[cr->len++] = c1; 87 | cr->points[cr->len++] = c2; 88 | return 0; 89 | } 90 | 91 | int cr_union1(CharRange *cr, const uint32_t *b_pt, int b_len); 92 | 93 | static inline int cr_union_interval(CharRange *cr, uint32_t c1, uint32_t c2) 94 | { 95 | uint32_t b_pt[2]; 96 | b_pt[0] = c1; 97 | b_pt[1] = c2 + 1; 98 | return cr_union1(cr, b_pt, 2); 99 | } 100 | 101 | int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, 102 | const uint32_t *b_pt, int b_len, int op); 103 | 104 | int cr_invert(CharRange *cr); 105 | int cr_regexp_canonicalize(CharRange *cr, bool is_unicode); 106 | 107 | bool lre_is_id_start(uint32_t c); 108 | bool lre_is_id_continue(uint32_t c); 109 | bool lre_is_white_space(uint32_t c); 110 | 111 | int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, 112 | UnicodeNormalizationEnum n_type, 113 | void *opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size)); 114 | 115 | /* Unicode character range functions */ 116 | 117 | int unicode_script(CharRange *cr, 118 | const char *script_name, bool is_ext); 119 | int unicode_general_category(CharRange *cr, const char *gc_name); 120 | int unicode_prop(CharRange *cr, const char *prop_name); 121 | 122 | #ifdef __cplusplus 123 | } /* extern "C" { */ 124 | #endif 125 | 126 | #endif /* LIBUNICODE_H */ 127 | -------------------------------------------------------------------------------- /CONFIG_zh.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-zh_CN.json](./resources/schema-zh_CN.json),在配置文件内写入 15 | 16 | ```json 17 | { 18 | "$schema": "https://raw.githubusercontent.com/std-microblock/breeze-shell/refs/heads/master/resources/schema-zh_CN.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 | // 使用自绘边框、阴影(需关闭 dwm 边框) 103 | "use_self_drawn_border": true, 104 | // 自定义边框示例配置 105 | "border_width": 2.5, 106 | // 支持渐变 [linear-gradient(angle, color1, color2) / radial-gradient(radius, color1, color2)] 107 | "border_color_dark": "linear-gradient(30, #DE73DF, #E5C07B)", 108 | "shadow_size": 20, 109 | "shadow_color_dark_from": "#ff000033", 110 | "shadow_color_dark_to": "#00ff0000" 111 | }, 112 | // 启用垂直同步 113 | "vsync": true, 114 | // 不替换 owner draw 的菜单 115 | "ignore_owner_draw": true, 116 | // 在向上展开时将所有项反向 117 | "reverse_if_open_to_up": true, 118 | // 调试选项,搜索更大范围的图标,不建议打开 119 | "search_large_dwItemData_range": false, 120 | // 定位相关 121 | "position": { 122 | // 竖直边距 123 | "padding_vertical": 20, 124 | // 水平边距 125 | "padding_horizontal": 0 126 | }, 127 | // 是否启用热键 128 | "hotkeys" : true, 129 | }, 130 | 131 | // 开启调试窗口 132 | "debug_console": false, 133 | 134 | // 主字体 135 | "font_path_main": "C:\\WINDOWS\\Fonts\\segoeui.ttf", 136 | // 副字体 137 | "font_path_fallback": "C:\\WINDOWS\\Fonts\\msyh.ttc", 138 | // 使用 hook 方式加载更多 resid 139 | "res_string_loader_use_hook": false, 140 | // 调试选项,避免更改 UI 窗口大小 141 | "avoid_resize_ui": false, 142 | // 插件加载顺序,在越前面的越先加载 143 | // 格式为插件的无拓展名文件名 144 | // 如:Windows 11 Icon Pack 145 | "plugin_load_order": [], 146 | // 全局默认动画效果 147 | "default_animation": animated_float_conf 148 | } 149 | ``` 150 | 151 | ## 示例配置文件 152 | 153 | #### 禁用所有动画 154 | 155 | ```json 156 | { 157 | "default_animation": { 158 | "easing": "mutation" 159 | } 160 | } 161 | ``` 162 | -------------------------------------------------------------------------------- /src/shell/script/ts/src/config_page/PluginStore.tsx: -------------------------------------------------------------------------------- 1 | import * as shell from "mshell"; 2 | import { Button, Text } from "./components"; 3 | import { UpdateDataContext, NotificationContext, PluginSourceContext } from "./contexts"; 4 | import { useTranslation } from "./utils"; 5 | import { PLUGIN_SOURCES } from "./constants"; 6 | import { memo, useContext, useEffect, useState } from "react"; 7 | 8 | const PluginStore = memo(() => { 9 | const { updateData } = useContext(UpdateDataContext)!; 10 | const { setErrorMessage } = useContext(NotificationContext)!; 11 | const { currentPluginSource } = useContext(PluginSourceContext)!; 12 | const { t } = useTranslation(); 13 | 14 | const [plugins, setPlugins] = useState([]); 15 | const [installingPlugins, setInstallingPlugins] = useState>(new Set()); 16 | 17 | useEffect(() => { 18 | if (updateData) { 19 | setPlugins(updateData.plugins); 20 | } 21 | }, [updateData]); 22 | 23 | const installPlugin = (plugin: any) => { 24 | if (installingPlugins.has(plugin.name)) return; 25 | 26 | setInstallingPlugins(prev => new Set(prev).add(plugin.name)); 27 | const path = shell.breeze.data_directory() + '/scripts/' + plugin.local_path; 28 | const url = PLUGIN_SOURCES[currentPluginSource] + plugin.path; 29 | shell.network.get_async(url, (data: string) => { 30 | shell.fs.write(path, data); 31 | shell.println(t('插件安装成功: ') + plugin.name); 32 | setInstallingPlugins(prev => { 33 | const newSet = new Set(prev); 34 | newSet.delete(plugin.name); 35 | return newSet; 36 | }); 37 | }, (e: any) => { 38 | shell.println(e); 39 | setErrorMessage(t('插件安装失败: ') + plugin.name); 40 | setInstallingPlugins(prev => { 41 | const newSet = new Set(prev); 42 | newSet.delete(plugin.name); 43 | return newSet; 44 | }); 45 | }); 46 | }; 47 | 48 | const [_, rerender] = useState(0); 49 | 50 | return ( 51 | 52 | {t("插件市场")} 53 | 54 | 55 | {plugins.map((plugin: any) => { 56 | let install_path = null; 57 | if (shell.fs.exists(shell.breeze.data_directory() + '/scripts/' + plugin.local_path)) { 58 | install_path = shell.breeze.data_directory() + '/scripts/' + plugin.local_path; 59 | } 60 | if (shell.fs.exists(shell.breeze.data_directory() + '/scripts/' + plugin.local_path + '.disabled')) { 61 | install_path = shell.breeze.data_directory() + '/scripts/' + plugin.local_path + '.disabled'; 62 | } 63 | const installed = install_path !== null; 64 | const local_version_match = installed ? shell.fs.read(install_path).match(/\/\/ @version:\s*(.*)/) : null; 65 | const local_version = local_version_match ? local_version_match[1] : '未安装'; 66 | const have_update = installed && local_version !== plugin.version; 67 | 68 | return ( 69 | 70 | 73 | 75 | 76 | {plugin.name} 77 | {plugin.description} 78 | 79 | 80 | 83 | 84 | 85 | 86 | ); 87 | })} 88 | 89 | 90 | 91 | ); 92 | }); 93 | 94 | export default PluginStore; -------------------------------------------------------------------------------- /src/shell/script/ts/src/config_page/PluginConfig.tsx: -------------------------------------------------------------------------------- 1 | import * as shell from "mshell"; 2 | import { showMenu } from "./utils"; 3 | import { Text, PluginItem } from "./components"; 4 | import { PluginLoadOrderContext } from "./contexts"; 5 | import { useTranslation, loadPlugins, togglePlugin, deletePlugin } from "./utils"; 6 | import { memo, useContext, useEffect, useState } from "react"; 7 | 8 | const PluginConfig = memo(() => { 9 | const { order, update } = useContext(PluginLoadOrderContext)!; 10 | const { t } = useTranslation(); 11 | 12 | const [installedPlugins, setInstalledPlugins] = useState([]); 13 | 14 | useEffect(() => { 15 | reloadPluginsList(); 16 | }, []); 17 | 18 | const reloadPluginsList = () => { 19 | const plugins = loadPlugins(); 20 | setInstalledPlugins(plugins); 21 | }; 22 | 23 | const handleTogglePlugin = (name: string) => { 24 | togglePlugin(name); 25 | reloadPluginsList(); 26 | }; 27 | 28 | const handleDeletePlugin = (name: string) => { 29 | deletePlugin(name); 30 | reloadPluginsList(); 31 | }; 32 | 33 | const isPrioritized = (name: string) => { 34 | return order?.includes(name) || false; 35 | }; 36 | 37 | const togglePrioritize = (name: string) => { 38 | const newOrder = [...(order || [])]; 39 | if (newOrder.includes(name)) { 40 | const index = newOrder.indexOf(name); 41 | newOrder.splice(index, 1); 42 | } else { 43 | newOrder.unshift(name); 44 | } 45 | update(newOrder); 46 | }; 47 | 48 | const showContextMenu = (pluginName: string) => { 49 | showMenu(menu => { 50 | menu.append_menu({ 51 | name: isPrioritized(pluginName) ? '取消优先加载' : '设为优先加载', 52 | action() { 53 | togglePrioritize(pluginName); 54 | menu.close(); 55 | } 56 | }); 57 | menu.append_menu({ 58 | name: t("删除"), 59 | action() { 60 | handleDeletePlugin(pluginName); 61 | menu.close(); 62 | } 63 | }); 64 | if (on_plugin_menu[pluginName]) { 65 | on_plugin_menu[pluginName](menu) 66 | } 67 | }); 68 | }; 69 | 70 | const prioritizedPlugins = installedPlugins.filter(name => isPrioritized(name)); 71 | const regularPlugins = installedPlugins.filter(name => !isPrioritized(name)); 72 | 73 | return ( 74 | 75 | {t("插件配置")} 76 | 77 | 78 | {prioritizedPlugins.length > 0 && ( 79 | 80 | {t("优先加载插件")} 81 | {prioritizedPlugins.map(name => { 82 | const isEnabled = shell.fs.exists(shell.breeze.data_directory() + '/scripts/' + name + '.js'); 83 | return ( 84 | handleTogglePlugin(name)} 90 | onMoreClick={showContextMenu} 91 | /> 92 | ); 93 | })} 94 | 95 | 96 | 97 | )} 98 | 99 | {prioritizedPlugins.length === 0 && {t("已安装插件")}} 100 | {regularPlugins.map(name => { 101 | const isEnabled = shell.fs.exists(shell.breeze.data_directory() + '/scripts/' + name + '.js'); 102 | return ( 103 | handleTogglePlugin(name)} 109 | onMoreClick={showContextMenu} 110 | /> 111 | ); 112 | })} 113 | 114 | 115 | 116 | 117 | ); 118 | }); 119 | 120 | export default PluginConfig; -------------------------------------------------------------------------------- /src/shell/script/ts/src/config_page/ConfigApp.tsx: -------------------------------------------------------------------------------- 1 | import * as shell from "mshell"; 2 | import { WINDOW_WIDTH, WINDOW_HEIGHT, SIDEBAR_WIDTH } from "./constants"; 3 | import { loadConfig, saveConfig } from "./utils"; 4 | import { 5 | ContextMenuContext, 6 | DebugConsoleContext, 7 | PluginLoadOrderContext, 8 | UpdateDataContext, 9 | NotificationContext, 10 | PluginSourceContext 11 | } from "./contexts"; 12 | import Sidebar from "./Sidebar"; 13 | import ContextMenuConfig from "./ContextMenuConfig"; 14 | import UpdatePage from "./UpdatePage"; 15 | import PluginStore from "./PluginStore"; 16 | import PluginConfig from "./PluginConfig"; 17 | import { useState, useEffect } from "react"; 18 | 19 | export const ConfigApp = () => { 20 | const [activePage, setActivePage] = useState('context-menu'); 21 | const [contextMenuConfig, setContextMenuConfig] = useState({}); 22 | const [debugConsole, setDebugConsole] = useState(false); 23 | const [pluginLoadOrder, setPluginLoadOrder] = useState([]); 24 | const [updateData, setUpdateData] = useState(null); 25 | const [config, setConfig] = useState({}); 26 | const [errorMessage, setErrorMessage] = useState(null); 27 | const [loadingMessage, setLoadingMessage] = useState(null); 28 | const [currentPluginSource, setCurrentPluginSource] = useState('Enlysure'); 29 | const [cachedPluginIndex, setCachedPluginIndex] = useState(null); 30 | 31 | useEffect(() => { 32 | const current_config_path = shell.breeze.data_directory() + '/config.json'; 33 | const current_config = shell.fs.read(current_config_path); 34 | const parsed = JSON.parse(current_config); 35 | setConfig(parsed); 36 | setContextMenuConfig(parsed.context_menu || {}); 37 | setDebugConsole(parsed.debug_console || false); 38 | setPluginLoadOrder(parsed.plugin_load_order || []); 39 | }, []); 40 | 41 | const updateContextMenu = (newConfig: any) => { 42 | setContextMenuConfig(newConfig); 43 | const newGlobal = { ...config, context_menu: newConfig }; 44 | setConfig(newGlobal); 45 | saveConfig(newGlobal); 46 | }; 47 | 48 | const updateDebugConsole = (value: boolean) => { 49 | setDebugConsole(value); 50 | const newGlobal = { ...config, debug_console: value }; 51 | setConfig(newGlobal); 52 | saveConfig(newGlobal); 53 | }; 54 | 55 | const updatePluginLoadOrder = (order: any[]) => { 56 | setPluginLoadOrder(order); 57 | const newGlobal = { ...config, plugin_load_order: order }; 58 | setConfig(newGlobal); 59 | saveConfig(newGlobal); 60 | }; 61 | 62 | return ( 63 | 64 | 65 | 66 | 67 | 73 | 79 | 80 | 86 | 87 | {activePage === 'context-menu' && } 88 | {activePage === 'update' && } 89 | {activePage === 'plugin-store' && } 90 | {activePage === 'plugin-config' && } 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | ); 100 | }; 101 | 102 | export default ConfigApp; -------------------------------------------------------------------------------- /src/shell/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "breeze_ui/animator.h" 4 | #include "breeze_ui/nanovg_wrapper.h" 5 | #include "nanovg.h" 6 | #include "utils.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "paint_color.h" 13 | 14 | namespace mb_shell { 15 | 16 | struct config { 17 | static std::filesystem::path default_main_font(); 18 | static std::filesystem::path default_fallback_font(); 19 | static std::filesystem::path default_mono_font(); 20 | 21 | struct animated_float_conf { 22 | float duration = _default_animation.duration; 23 | ui::easing_type easing = _default_animation.easing; 24 | float delay_scale = _default_animation.delay_scale; 25 | 26 | void apply_to(ui::sp_anim_float &anim, float delay = 0); 27 | void apply_to(ui::animated_color &anim, float delay = 0); 28 | void operator()(ui::sp_anim_float &anim, float delay = 0); 29 | } default_animation; 30 | 31 | static animated_float_conf _default_animation; 32 | struct context_menu { 33 | struct theme { 34 | bool use_dwm_if_available = true; 35 | float background_opacity = 1; 36 | bool acrylic = true; 37 | float radius = 6; 38 | float font_size = 14; 39 | float item_height = 23; 40 | float item_gap = 3; 41 | float item_radius = 5; 42 | float margin = 5; 43 | float padding = 6; 44 | float text_padding = 8; 45 | float icon_padding = 4; 46 | float right_icon_padding = 10; 47 | float multibutton_line_gap = -6; 48 | float scrollbar_width = 6; 49 | float scrollbar_radius = 3; 50 | float hotkey_padding = 4; 51 | 52 | std::string acrylic_color_light = "#fefefe00"; 53 | std::string acrylic_color_dark = "#28282800"; 54 | 55 | bool use_self_drawn_border = true; 56 | // These values are used when use_self_drawn_border is true 57 | paint_color border_color_light = 58 | paint_color::from_string("#00000022"); 59 | paint_color border_color_dark = 60 | paint_color::from_string("#ffffff22"); 61 | std::string shadow_color_light_from = "#00000020"; 62 | std::string shadow_color_light_to = "#00000000"; 63 | std::string shadow_color_dark_from = "#00000033"; 64 | std::string shadow_color_dark_to = "#00000000"; 65 | float shadow_blur = 10; 66 | float shadow_offset_x = 0; 67 | float shadow_offset_y = 0; 68 | float shadow_opacity = 0.2; 69 | float shadow_size = 10; 70 | float border_width = 1.5; 71 | bool inset_border = true; 72 | 73 | // unused, only for backward compatibility 74 | float acrylic_opacity = 0.1; 75 | 76 | struct animation { 77 | struct main { 78 | animated_float_conf y; 79 | } main; 80 | struct item { 81 | animated_float_conf opacity; 82 | animated_float_conf x, y; 83 | animated_float_conf width; 84 | } item; 85 | struct bg { 86 | animated_float_conf opacity; 87 | animated_float_conf x, y, w, h; 88 | } main_bg, submenu_bg; 89 | } animation; 90 | } theme; 91 | 92 | bool vsync = true; 93 | bool ignore_owner_draw = true; 94 | bool reverse_if_open_to_up = true; 95 | bool experimental_ownerdraw_support = false; 96 | bool hotkeys = true; 97 | bool show_settings_button = true; 98 | 99 | // debug purpose only 100 | bool search_large_dwItemData_range = false; 101 | 102 | struct position { 103 | int padding_vertical = 20; 104 | int padding_horizontal = 0; 105 | } position; 106 | } context_menu; 107 | 108 | struct taskbar { 109 | struct theme { 110 | struct animation { 111 | animated_float_conf bg_color; 112 | animated_float_conf active_indicator; 113 | } animation; 114 | } theme; 115 | } taskbar; 116 | 117 | bool debug_console = false; 118 | // Restart to apply font/hook changes 119 | std::filesystem::path font_path_main = default_main_font(); 120 | std::filesystem::path font_path_fallback = default_fallback_font(); 121 | std::filesystem::path font_path_monospace = default_mono_font(); 122 | bool res_string_loader_use_hook = false; 123 | bool avoid_resize_ui = false; 124 | std::vector plugin_load_order = {}; 125 | 126 | std::string $schema; 127 | static std::unique_ptr current; 128 | static void read_config(); 129 | static void write_config(); 130 | static void run_config_loader(); 131 | static std::string dump_config(); 132 | 133 | static std::filesystem::path data_directory(); 134 | void apply_fonts_to_nvg(NVGcontext *nvg); 135 | }; 136 | } // namespace mb_shell -------------------------------------------------------------------------------- /src/ui_test/test.cc: -------------------------------------------------------------------------------- 1 | #include "GLFW/glfw3.h" 2 | #include "breeze_ui/animator.h" 3 | #include "breeze_ui/extra_widgets.h" 4 | #include "breeze_ui/nanovg_wrapper.h" 5 | #include "breeze_ui/ui.h" 6 | #include "breeze_ui/widget.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | struct test_widget : public ui::acrylic_background_widget { 16 | using super = ui::acrylic_background_widget; 17 | test_widget() : super() { 18 | x->animate_to(100); 19 | y->animate_to(100); 20 | width->animate_to(100); 21 | height->animate_to(100); 22 | 23 | acrylic_bg_color = nvgRGBAf(0, 0.5, 0.5, 0.5); 24 | update_color(); 25 | radius->animate_to(10); 26 | } 27 | 28 | ui::sp_anim_float color_transition = anim_float(256, 3000); 29 | void render(ui::nanovg_context ctx) override { 30 | super::render(ctx); 31 | ctx.fontFace("main"); 32 | ctx.fontSize(24); 33 | ctx.text(*x + 10, *y + 30, "Button", nullptr); 34 | } 35 | 36 | void update(ui::update_context &ctx) override { 37 | super::update(ctx); 38 | if (ctx.mouse_down_on(this)) { 39 | color_transition->animate_to(255); 40 | width->animate_to(200); 41 | height->animate_to(200); 42 | } else if (ctx.hovered(this)) { 43 | color_transition->animate_to(0); 44 | width->animate_to(150); 45 | height->animate_to(150); 46 | } else { 47 | color_transition->animate_to(128); 48 | width->animate_to(100); 49 | height->animate_to(100); 50 | } 51 | 52 | if (ctx.mouse_clicked_on(this)) { 53 | if (x->dest() == 100) { 54 | x->animate_to(200); 55 | y->animate_to(200); 56 | } else { 57 | x->animate_to(100); 58 | y->animate_to(100); 59 | } 60 | } 61 | } 62 | }; 63 | 64 | struct dying_widget_test : public ui::widget { 65 | using super = ui::widget; 66 | 67 | ui::sp_anim_float opacity = anim_float(0, 200); 68 | 69 | dying_widget_test() : super() { 70 | x->animate_to(100); 71 | y->animate_to(100); 72 | width->animate_to(100); 73 | height->animate_to(100); 74 | opacity->animate_to(255); 75 | } 76 | 77 | void render(ui::nanovg_context ctx) override { 78 | super::render(ctx); 79 | 80 | static std::string s = 81 | R"#()#"; 82 | static auto svg = nsvgParse(s.data(), "px", 96); 83 | ctx.fillColor(nvgRGBAf(0.5, 0.5, 0, *opacity / 255.f)); 84 | ctx.fillRect(*x, *y, *width, *height); 85 | ctx.drawSVG(svg, *x, *y, *width, *height); 86 | // std::println("Rendering dying widget"); 87 | } 88 | 89 | void update(ui::update_context &ctx) override { 90 | super::update(ctx); 91 | if (ctx.mouse_down_on(this)) { 92 | dying_time = 200; 93 | } 94 | 95 | if (dying_time) { 96 | opacity->animate_to(0); 97 | } else if (ctx.hovered(this)) { 98 | opacity->animate_to(128); 99 | } else { 100 | opacity->animate_to(255); 101 | } 102 | } 103 | }; 104 | 105 | int main() { 106 | 107 | if (auto res = ui::render_target::init_global(); !res) { 108 | std::println("Failed to initialize global render target: {}", 109 | res.error()); 110 | return 1; 111 | } 112 | 113 | ui::render_target rt; 114 | rt.decorated = true; 115 | // rt.topmost = true; 116 | // rt.transparent = false; 117 | 118 | if (auto res = rt.init(); !res) { 119 | std::println("Failed to initialize render target: {}", res.error()); 120 | return 1; 121 | } 122 | nvgCreateFont(rt.nvg, "main", "C:\\WINDOWS\\FONTS\\msyh.ttc"); 123 | 124 | rt.start_loop(); 125 | return 0; 126 | 127 | glfwInit(); 128 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 129 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 130 | glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); 131 | glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); 132 | glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); 133 | glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); 134 | glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); 135 | 136 | auto window = glfwCreateWindow(800, 600, "UI", nullptr, nullptr); 137 | glfwMakeContextCurrent(window); 138 | 139 | if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { 140 | std::cerr << "Failed to initialize GLAD" << std::endl; 141 | return -1; 142 | } 143 | 144 | while (!glfwWindowShouldClose(window)) { 145 | glClearColor(0, 0, 0, 0.3); 146 | glClear(GL_COLOR_BUFFER_BIT); 147 | glfwSwapBuffers(window); 148 | glfwPollEvents(); 149 | } 150 | 151 | return 0; 152 | } -------------------------------------------------------------------------------- /src/shell/contextmenu/menu_render.cc: -------------------------------------------------------------------------------- 1 | #include "menu_render.h" 2 | #include "shell/utils.h" 3 | #define GLFW_EXPOSE_NATIVE_WIN32 4 | #include "GLFW/glfw3native.h" 5 | #include "Windows.h" 6 | #include "menu_widget.h" 7 | 8 | #include "breeze_ui/ui.h" 9 | #include "shell/entry.h" 10 | #include "shell/logger.h" 11 | #include "shell/script/binding_types.hpp" 12 | #include 13 | #include 14 | 15 | namespace mb_shell { 16 | std::optional menu_render::current{}; 17 | menu_render menu_render::create(int x, int y, menu menu, bool run_js) { 18 | if (auto res = ui::render_target::init_global(); !res) { 19 | MessageBoxW(NULL, L"Failed to initialize global render target", 20 | L"Error", MB_ICONERROR); 21 | return {nullptr, std::nullopt}; 22 | } 23 | 24 | static auto rt = []() { 25 | static window_proc_hook glfw_proc_hook; 26 | auto rt = std::make_shared(); 27 | rt->transparent = true; 28 | rt->no_activate = true; 29 | rt->capture_all_input = true; 30 | rt->decorated = false; 31 | rt->topmost = true; 32 | rt->vsync = config::current->context_menu.vsync; 33 | 34 | if (config::current->avoid_resize_ui) { 35 | rt->width = 3840; 36 | rt->height = 2159; 37 | } 38 | 39 | if (auto res = rt->init(); !res) { 40 | MessageBoxW(NULL, L"Failed to initialize render target", L"Error", 41 | MB_ICONERROR); 42 | } 43 | 44 | glfw_proc_hook.install(rt->hwnd()); 45 | SetCapture((HWND)rt->hwnd()); 46 | glfw_proc_hook.hooks.push_back([](void *hwnd, void *original_proc, 47 | size_t msg, size_t wparam, 48 | size_t lparam) -> std::optional { 49 | if (msg == WM_MOUSEACTIVATE) { 50 | return MA_NOACTIVATE; 51 | } 52 | 53 | return std::nullopt; 54 | }); 55 | 56 | config::current->apply_fonts_to_nvg(rt->nvg); 57 | return rt; 58 | }(); 59 | auto render = menu_render(rt, std::nullopt); 60 | 61 | rt->parent = menu.parent_window; 62 | 63 | // get the monitor in which the menu is being shown 64 | auto monitor = MonitorFromPoint({x, y}, MONITOR_DEFAULTTONEAREST); 65 | MONITORINFOEX monitor_info; 66 | monitor_info.cbSize = sizeof(MONITORINFOEX); 67 | GetMonitorInfo(monitor, &monitor_info); 68 | 69 | // set the position of the window to fullscreen in this monitor + padding 70 | 71 | dbgout("Monitor: {} {} {} {}", monitor_info.rcMonitor.left, 72 | monitor_info.rcMonitor.top, monitor_info.rcMonitor.right, 73 | monitor_info.rcMonitor.bottom); 74 | 75 | rt->set_position(monitor_info.rcMonitor.left + 1, 76 | monitor_info.rcMonitor.top + 1); 77 | if (!config::current->avoid_resize_ui) 78 | rt->resize( 79 | monitor_info.rcMonitor.right - monitor_info.rcMonitor.left - 2, 80 | monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top - 2); 81 | 82 | glfwMakeContextCurrent(rt->window); 83 | glfwSwapInterval(config::current->context_menu.vsync ? 1 : 0); 84 | 85 | rt->show(); 86 | auto menu_wid = std::make_shared( 87 | menu, 88 | // convert the x and y to the window coordinates 89 | x - monitor_info.rcMonitor.left, y - monitor_info.rcMonitor.top); 90 | rt->root->children.push_back(menu_wid); 91 | auto current_js_context = 92 | entry::main_window_loop_hook 93 | .add_task([&]() { 94 | return std::make_shared( 95 | js::js_menu_context::$from_window(menu.parent_window)); 96 | }) 97 | .get(); 98 | 99 | js::menu_info_basic_js menu_info{ 100 | .menu = std::make_shared(menu_wid->menu_wid), 101 | .context = current_js_context, 102 | .screenside_button = 103 | std::make_shared(menu_wid), 104 | }; 105 | 106 | if (run_js) { 107 | dbgout("[perf] JS plugins start"); 108 | auto before_js = rt->clock.now(); 109 | for (auto &listener : menu_callbacks_js) { 110 | listener->operator()(menu_info); 111 | } 112 | dbgout("[perf] JS plugins costed {}ms", 113 | std::chrono::duration_cast( 114 | rt->clock.now() - before_js) 115 | .count()); 116 | } else { 117 | dbgout("Skipped running JS"); 118 | } 119 | 120 | dbgout("Current menu: {}", menu_render::current.has_value()); 121 | return render; 122 | } 123 | 124 | menu_render::menu_render(std::shared_ptr rt, 125 | std::optional selected_menu) 126 | : rt(std::move(rt)), selected_menu(selected_menu) { 127 | current = this; 128 | } 129 | menu_render::~menu_render() { 130 | if (this->rt) { 131 | current = nullptr; 132 | } 133 | } 134 | menu_render::menu_render(menu_render &&t) { 135 | current = this; 136 | 137 | rt = std::move(t.rt); 138 | selected_menu = std::move(t.selected_menu); 139 | } 140 | menu_render &menu_render::operator=(menu_render &&t) { 141 | current = this; 142 | rt = std::move(t.rt); 143 | selected_menu = std::move(t.selected_menu); 144 | return *this; 145 | } 146 | }; // namespace mb_shell -------------------------------------------------------------------------------- /src/shell/script/ts/src/config_page/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | import * as shell from "mshell"; 2 | import { showMenu } from "./utils"; 3 | import { memo, useEffect, useContext } from "react"; 4 | import { Button, SidebarItem, Text, iconElement } from "./components"; 5 | import { 6 | ICON_BREEZE, 7 | ICON_CONTEXT_MENU, 8 | ICON_UPDATE, 9 | ICON_PLUGIN_STORE, 10 | ICON_PLUGIN_CONFIG, 11 | PLUGIN_SOURCES 12 | } from "./constants"; 13 | import { UpdateDataContext, NotificationContext, PluginSourceContext } from "./contexts"; 14 | import { useTranslation } from "./utils"; 15 | 16 | const Sidebar = memo(({ 17 | activePage, 18 | setActivePage, 19 | sidebarWidth, 20 | windowHeight 21 | }: { 22 | activePage: string; 23 | setActivePage: (page: string) => void; 24 | sidebarWidth: number; 25 | windowHeight: number; 26 | }) => { 27 | const { t } = useTranslation(); 28 | const { updateData, setUpdateData } = useContext(UpdateDataContext)!; 29 | const { errorMessage, setErrorMessage, loadingMessage, setLoadingMessage } = useContext(NotificationContext)!; 30 | const { currentPluginSource, setCurrentPluginSource, cachedPluginIndex, setCachedPluginIndex } = useContext(PluginSourceContext)!; 31 | 32 | useEffect(() => { 33 | if (errorMessage) { 34 | const timer = setTimeout(() => { 35 | setErrorMessage(null); 36 | }, 3000); 37 | return () => clearTimeout(timer); 38 | } 39 | }, [errorMessage, setErrorMessage]); 40 | 41 | const handleSourceChange = (sourceName: string) => { 42 | setCurrentPluginSource(sourceName); 43 | setCachedPluginIndex(null); 44 | setLoadingMessage(t("切换源中...")); 45 | 46 | shell.network.get_async(PLUGIN_SOURCES[sourceName] + 'plugins-index.json', (data: string) => { 47 | setCachedPluginIndex(data); 48 | setUpdateData(JSON.parse(data)); 49 | setLoadingMessage(null); 50 | }, (e: any) => { 51 | shell.println('Failed to fetch update data:', e); 52 | setErrorMessage(t("加载失败")); 53 | setLoadingMessage(null); 54 | }); 55 | }; 56 | 57 | useEffect(() => { 58 | handleSourceChange(currentPluginSource); 59 | }, [currentPluginSource]); 60 | 61 | return ( 62 | 71 | 72 | {iconElement(ICON_BREEZE, 24)} 73 | Breeze 74 | 75 | setActivePage('context-menu')} icon={ICON_CONTEXT_MENU} isActive={activePage === 'context-menu'}>主配置 76 | setActivePage('update')} icon={ICON_UPDATE} isActive={activePage === 'update'}>更新 77 | setActivePage('plugin-store')} icon={ICON_PLUGIN_STORE} isActive={activePage === 'plugin-store'}>插件商店 78 | setActivePage('plugin-config')} icon={ICON_PLUGIN_CONFIG} isActive={activePage === 'plugin-config'}>插件配置 79 | 80 | 81 | {/* 错误提示 */} 82 | {errorMessage && ( 83 | 89 | 94 | 95 | )} 96 | 97 | {/* 加载提示 */} 98 | {loadingMessage && ( 99 | 105 | 110 | 111 | )} 112 | 113 | 129 | 130 | ); 131 | }); 132 | 133 | export default Sidebar; -------------------------------------------------------------------------------- /src/shell/paint_color.cc: -------------------------------------------------------------------------------- 1 | #include "paint_color.h" 2 | #include "breeze_ui/ui.h" 3 | #include "nanovg.h" 4 | #include "utils.h" 5 | #include 6 | #include 7 | 8 | namespace mb_shell { 9 | paint_color paint_color::from_string(const std::string &str) { 10 | paint_color res; 11 | // Trim whitespace 12 | std::string trimmed = str; 13 | trimmed.erase(0, trimmed.find_first_not_of(" \t\n\r")); 14 | trimmed.erase(trimmed.find_last_not_of(" \t\n\r") + 1); 15 | 16 | if (trimmed.starts_with("solid(") && trimmed.ends_with(")")) { 17 | std::string color_str = trimmed.substr(6, trimmed.length() - 7); 18 | res.type = type::solid; 19 | res.color = rgba_color::from_string(color_str); 20 | } else if (trimmed.starts_with("linear-gradient(") && 21 | trimmed.ends_with(")")) { 22 | std::string params = trimmed.substr(16, trimmed.length() - 17); 23 | 24 | // Split by commas 25 | std::vector parts; 26 | size_t start = 0, end = 0; 27 | while ((end = params.find(',', start)) != std::string::npos) { 28 | std::string part = params.substr(start, end - start); 29 | part.erase(0, part.find_first_not_of(" \t")); 30 | part.erase(part.find_last_not_of(" \t") + 1); 31 | parts.push_back(part); 32 | start = end + 1; 33 | } 34 | std::string last_part = params.substr(start); 35 | last_part.erase(0, last_part.find_first_not_of(" \t")); 36 | last_part.erase(last_part.find_last_not_of(" \t") + 1); 37 | parts.push_back(last_part); 38 | 39 | if (parts.size() >= 3) { 40 | res.type = type::linear_gradient; 41 | res.angle = std::stof(parts[0]) * std::numbers::pi / 42 | 180.0f; // Convert degrees to radians 43 | res.color = parse_color(parts[1]); 44 | res.color2 = parse_color(parts[2]); 45 | } 46 | } else if (trimmed.starts_with("radial-gradient(") && 47 | trimmed.ends_with(")")) { 48 | std::string params = trimmed.substr(16, trimmed.length() - 17); 49 | 50 | // Split by commas 51 | std::vector parts; 52 | size_t start = 0, end = 0; 53 | while ((end = params.find(',', start)) != std::string::npos) { 54 | std::string part = params.substr(start, end - start); 55 | part.erase(0, part.find_first_not_of(" \t")); 56 | part.erase(part.find_last_not_of(" \t") + 1); 57 | parts.push_back(part); 58 | start = end + 1; 59 | } 60 | std::string last_part = params.substr(start); 61 | last_part.erase(0, last_part.find_first_not_of(" \t")); 62 | last_part.erase(last_part.find_last_not_of(" \t") + 1); 63 | parts.push_back(last_part); 64 | 65 | if (parts.size() >= 3) { 66 | res.type = type::radial_gradient; 67 | res.radius = std::stof(parts[0]); 68 | res.color = parse_color(parts[1]); 69 | res.color2 = parse_color(parts[2]); 70 | res.radius2 = res.radius * 2; // Default outer radius 71 | } 72 | } else { 73 | // Default to solid color 74 | res.type = type::solid; 75 | res.color = parse_color(trimmed); 76 | } 77 | 78 | return res; 79 | } 80 | std::string paint_color::to_string() const { 81 | switch (type) { 82 | case type::solid: 83 | return "solid(" + format_color(color) + ")"; 84 | case type::linear_gradient: 85 | return "linear-gradient(" + 86 | std::to_string(angle * 180.0f / std::numbers::pi) + ", " + 87 | format_color(color) + ", " + format_color(color2) + ")"; 88 | case type::radial_gradient: 89 | return "radial-gradient(" + std::to_string(radius) + ", " + 90 | format_color(color) + ", " + format_color(color2) + ")"; 91 | } 92 | return ""; 93 | } 94 | void paint_color::apply_to_ctx(ui::nanovg_context &ctx, float x, float y, 95 | float width, float height) const { 96 | 97 | switch (type) { 98 | case type::solid: { 99 | ctx.fillColor(color); 100 | ctx.strokeColor(color); 101 | return; 102 | } 103 | case type::linear_gradient: { 104 | auto paint = ctx.linearGradient(x, y, x + width * cos(angle), 105 | y + height * sin(angle), color, color2); 106 | ctx.fillPaint(paint); 107 | ctx.strokePaint(paint); 108 | return; 109 | } 110 | case type::radial_gradient: { 111 | auto paint = ctx.radialGradient(x + width / 2, y + height / 2, radius, 112 | radius2, color, color2); 113 | ctx.fillPaint(paint); 114 | ctx.strokePaint(paint); 115 | return; 116 | } 117 | } 118 | throw std::runtime_error("Unknown paint color type"); 119 | } 120 | rgba_color::operator NVGcolor() const { return nvgRGBAf(r, g, b, a); } 121 | rgba_color::rgba_color(const NVGcolor &color) 122 | : r(color.r), g(color.g), b(color.b), a(color.a) {} 123 | NVGcolor rgba_color::nvg() const { return nvgRGBAf(r, g, b, a); } 124 | rgba_color rgba_color::from_string(const std::string &str) { 125 | return parse_color(str); 126 | } 127 | std::string rgba_color::to_string() const { 128 | return std::format("#{0:02x}{1:02x}{2:02x}{3:02x}", 129 | static_cast(r * 255), static_cast(g * 255), 130 | static_cast(b * 255), static_cast(a * 255)); 131 | } 132 | } // namespace mb_shell -------------------------------------------------------------------------------- /src/shell/script/quickjs/builtin-array-fromasync.h: -------------------------------------------------------------------------------- 1 | /* File generated automatically by the QuickJS-ng compiler. */ 2 | 3 | #include 4 | 5 | const uint32_t qjsc_builtin_array_fromasync_size = 826; 6 | 7 | const uint8_t qjsc_builtin_array_fromasync[826] = { 8 | 0x15, 0x0d, 0x01, 0x1a, 0x61, 0x73, 0x79, 0x6e, 9 | 0x63, 0x49, 0x74, 0x65, 0x72, 0x61, 0x74, 0x6f, 10 | 0x72, 0x01, 0x10, 0x69, 0x74, 0x65, 0x72, 0x61, 11 | 0x74, 0x6f, 0x72, 0x01, 0x12, 0x61, 0x72, 0x72, 12 | 0x61, 0x79, 0x4c, 0x69, 0x6b, 0x65, 0x01, 0x0a, 13 | 0x6d, 0x61, 0x70, 0x46, 0x6e, 0x01, 0x0e, 0x74, 14 | 0x68, 0x69, 0x73, 0x41, 0x72, 0x67, 0x01, 0x0c, 15 | 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x01, 0x02, 16 | 0x69, 0x01, 0x1a, 0x69, 0x73, 0x43, 0x6f, 0x6e, 17 | 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f, 0x72, 18 | 0x01, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x01, 0x0c, 19 | 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x01, 0x08, 20 | 0x69, 0x74, 0x65, 0x72, 0x01, 0x1c, 0x6e, 0x6f, 21 | 0x74, 0x20, 0x61, 0x20, 0x66, 0x75, 0x6e, 0x63, 22 | 0x74, 0x69, 0x6f, 0x6e, 0x01, 0x08, 0x63, 0x61, 23 | 0x6c, 0x6c, 0x0c, 0x00, 0x02, 0x00, 0xa2, 0x01, 24 | 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x04, 0x01, 25 | 0xa4, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x43, 0x02, 26 | 0x01, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x01, 27 | 0x03, 0x05, 0xaa, 0x02, 0x00, 0x01, 0x40, 0xa0, 28 | 0x03, 0x00, 0x01, 0x40, 0xc6, 0x03, 0x00, 0x01, 29 | 0x40, 0xcc, 0x01, 0x00, 0x01, 0x40, 0xc8, 0x03, 30 | 0x00, 0x01, 0x40, 0x0c, 0x60, 0x02, 0x01, 0xf8, 31 | 0x01, 0x03, 0x0e, 0x01, 0x06, 0x05, 0x00, 0x86, 32 | 0x04, 0x11, 0xca, 0x03, 0x00, 0x01, 0x00, 0xcc, 33 | 0x03, 0x00, 0x01, 0x00, 0xce, 0x03, 0x00, 0x01, 34 | 0x00, 0xca, 0x03, 0x01, 0xff, 0xff, 0xff, 0xff, 35 | 0x0f, 0x20, 0xcc, 0x03, 0x01, 0x01, 0x20, 0xce, 36 | 0x03, 0x01, 0x02, 0x20, 0xd0, 0x03, 0x02, 0x00, 37 | 0x20, 0xd2, 0x03, 0x02, 0x04, 0x20, 0xd4, 0x03, 38 | 0x02, 0x05, 0x20, 0xd6, 0x03, 0x02, 0x06, 0x20, 39 | 0xd8, 0x03, 0x02, 0x07, 0x20, 0x64, 0x06, 0x08, 40 | 0x20, 0x82, 0x01, 0x07, 0x09, 0x20, 0xda, 0x03, 41 | 0x0a, 0x08, 0x30, 0x82, 0x01, 0x0d, 0x0b, 0x20, 42 | 0xd4, 0x01, 0x0d, 0x0c, 0x20, 0x10, 0x00, 0x01, 43 | 0x00, 0xa0, 0x03, 0x01, 0x03, 0xc6, 0x03, 0x02, 44 | 0x03, 0xc8, 0x03, 0x04, 0x03, 0xaa, 0x02, 0x00, 45 | 0x03, 0xcc, 0x01, 0x03, 0x03, 0x08, 0xc4, 0x0d, 46 | 0x62, 0x02, 0x00, 0x62, 0x01, 0x00, 0x62, 0x00, 47 | 0x00, 0xd3, 0xcb, 0xd4, 0x11, 0xf4, 0xec, 0x08, 48 | 0x0e, 0x39, 0x46, 0x00, 0x00, 0x00, 0xdc, 0xcc, 49 | 0xd5, 0x11, 0xf4, 0xec, 0x08, 0x0e, 0x39, 0x46, 50 | 0x00, 0x00, 0x00, 0xdd, 0xcd, 0x62, 0x07, 0x00, 51 | 0x62, 0x06, 0x00, 0x62, 0x05, 0x00, 0x62, 0x04, 52 | 0x00, 0x62, 0x03, 0x00, 0xd4, 0x39, 0x46, 0x00, 53 | 0x00, 0x00, 0xb0, 0xec, 0x16, 0xd4, 0x98, 0x04, 54 | 0x1b, 0x00, 0x00, 0x00, 0xb0, 0xec, 0x0c, 0xdf, 55 | 0x11, 0x04, 0xee, 0x00, 0x00, 0x00, 0x21, 0x01, 56 | 0x00, 0x30, 0x06, 0xce, 0xb6, 0xc4, 0x04, 0xc3, 57 | 0x0d, 0xf7, 0xc4, 0x05, 0x09, 0xc4, 0x06, 0xd3, 58 | 0xe0, 0x48, 0xc4, 0x07, 0x63, 0x07, 0x00, 0x07, 59 | 0xad, 0xec, 0x0f, 0x0a, 0x11, 0x64, 0x06, 0x00, 60 | 0x0e, 0xd3, 0xe1, 0x48, 0x11, 0x64, 0x07, 0x00, 61 | 0x0e, 0x63, 0x07, 0x00, 0x07, 0xad, 0x6a, 0xa6, 62 | 0x00, 0x00, 0x00, 0x62, 0x08, 0x00, 0x06, 0x11, 63 | 0xf4, 0xed, 0x0c, 0x71, 0x43, 0x32, 0x00, 0x00, 64 | 0x00, 0xc4, 0x08, 0x0e, 0xee, 0x05, 0x0e, 0xd3, 65 | 0xee, 0xf2, 0x63, 0x08, 0x00, 0x8e, 0x11, 0xed, 66 | 0x03, 0x0e, 0xb6, 0x11, 0x64, 0x08, 0x00, 0x0e, 67 | 0x63, 0x05, 0x00, 0xec, 0x0c, 0xc3, 0x0d, 0x11, 68 | 0x63, 0x08, 0x00, 0x21, 0x01, 0x00, 0xee, 0x06, 69 | 0xe2, 0x63, 0x08, 0x00, 0xf1, 0x11, 0x64, 0x03, 70 | 0x00, 0x0e, 0x63, 0x04, 0x00, 0x63, 0x08, 0x00, 71 | 0xa7, 0x6a, 0x2a, 0x01, 0x00, 0x00, 0x62, 0x09, 72 | 0x00, 0xd3, 0x63, 0x04, 0x00, 0x48, 0xc4, 0x09, 73 | 0x63, 0x06, 0x00, 0xec, 0x0a, 0x63, 0x09, 0x00, 74 | 0x8c, 0x11, 0x64, 0x09, 0x00, 0x0e, 0xd4, 0xec, 75 | 0x17, 0xd4, 0x43, 0xef, 0x00, 0x00, 0x00, 0xd5, 76 | 0x63, 0x09, 0x00, 0x63, 0x04, 0x00, 0x24, 0x03, 77 | 0x00, 0x8c, 0x11, 0x64, 0x09, 0x00, 0x0e, 0x5f, 78 | 0x04, 0x00, 0x63, 0x03, 0x00, 0x63, 0x04, 0x00, 79 | 0x92, 0x64, 0x04, 0x00, 0x0b, 0x63, 0x09, 0x00, 80 | 0x4d, 0x41, 0x00, 0x00, 0x00, 0x0a, 0x4d, 0x3e, 81 | 0x00, 0x00, 0x00, 0x0a, 0x4d, 0x3f, 0x00, 0x00, 82 | 0x00, 0xf3, 0x0e, 0xee, 0x9e, 0x62, 0x0a, 0x00, 83 | 0x63, 0x07, 0x00, 0x43, 0xef, 0x00, 0x00, 0x00, 84 | 0xd3, 0x24, 0x01, 0x00, 0xc4, 0x0a, 0x63, 0x05, 85 | 0x00, 0xec, 0x09, 0xc3, 0x0d, 0x11, 0x21, 0x00, 86 | 0x00, 0xee, 0x03, 0xe2, 0xf0, 0x11, 0x64, 0x03, 87 | 0x00, 0x0e, 0x6d, 0x8c, 0x00, 0x00, 0x00, 0x62, 88 | 0x0c, 0x00, 0x62, 0x0b, 0x00, 0x06, 0x11, 0xf4, 89 | 0xed, 0x13, 0x71, 0x43, 0x41, 0x00, 0x00, 0x00, 90 | 0xc4, 0x0b, 0x43, 0x6a, 0x00, 0x00, 0x00, 0xc4, 91 | 0x0c, 0x0e, 0xee, 0x10, 0x0e, 0x63, 0x0a, 0x00, 92 | 0x43, 0x6b, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 93 | 0x8c, 0xee, 0xe0, 0x63, 0x0c, 0x00, 0xed, 0x4e, 94 | 0x63, 0x06, 0x00, 0xec, 0x0a, 0x63, 0x0b, 0x00, 95 | 0x8c, 0x11, 0x64, 0x0b, 0x00, 0x0e, 0xd4, 0xec, 96 | 0x17, 0xd4, 0x43, 0xef, 0x00, 0x00, 0x00, 0xd5, 97 | 0x63, 0x0b, 0x00, 0x63, 0x04, 0x00, 0x24, 0x03, 98 | 0x00, 0x8c, 0x11, 0x64, 0x0b, 0x00, 0x0e, 0x5f, 99 | 0x04, 0x00, 0x63, 0x03, 0x00, 0x63, 0x04, 0x00, 100 | 0x92, 0x64, 0x04, 0x00, 0x0b, 0x63, 0x0b, 0x00, 101 | 0x4d, 0x41, 0x00, 0x00, 0x00, 0x0a, 0x4d, 0x3e, 102 | 0x00, 0x00, 0x00, 0x0a, 0x4d, 0x3f, 0x00, 0x00, 103 | 0x00, 0xf3, 0x0e, 0xee, 0x83, 0x0e, 0x06, 0x6e, 104 | 0x0d, 0x00, 0x00, 0x00, 0x0e, 0xee, 0x1e, 0x6e, 105 | 0x05, 0x00, 0x00, 0x00, 0x30, 0x63, 0x0a, 0x00, 106 | 0x42, 0x06, 0x00, 0x00, 0x00, 0xec, 0x0d, 0x63, 107 | 0x0a, 0x00, 0x43, 0x06, 0x00, 0x00, 0x00, 0x24, 108 | 0x00, 0x00, 0x0e, 0x6f, 0x63, 0x03, 0x00, 0x63, 109 | 0x04, 0x00, 0x44, 0x32, 0x00, 0x00, 0x00, 0x63, 110 | 0x03, 0x00, 0x2f, 0xc1, 0x00, 0x28, 0xc1, 0x00, 111 | 0xcf, 0x28, 112 | }; 113 | 114 | -------------------------------------------------------------------------------- /src/shell/script/ts/src/config_page/constants.ts: -------------------------------------------------------------------------------- 1 | import * as shell from "mshell"; 2 | 3 | export const languages = { 4 | 'zh-CN': { 5 | "管理 Breeze Shell": "管理 Breeze Shell", 6 | "插件市场": "插件市场", 7 | "加载中...": "加载中...", 8 | "更新中...": "更新中...", 9 | "安装中...": "安装中...", 10 | "新版本已下载,将于下次重启资源管理器生效": "新版本已下载,将于下次重启资源管理器生效", 11 | "更新失败: ": "更新失败: ", 12 | "插件安装成功: ": "插件安装成功: ", 13 | "版本: ": "版本: ", 14 | "作者: ": "作者: ", 15 | "删除": "删除", 16 | "Breeze 设置": "Breeze 设置", 17 | "优先加载插件": "优先加载插件", 18 | "调试控制台": "调试控制台", 19 | "垂直同步": "垂直同步", 20 | "忽略自绘菜单": "忽略自绘菜单", 21 | "向上展开时反向排列": "向上展开时反向排列", 22 | "尝试使用 Windows 11 圆角": "尝试使用 Windows 11 圆角", 23 | "亚克力背景效果": "亚克力背景效果", 24 | "主题": "主题", 25 | "动画": "动画", 26 | "当前源: ": "当前源: ", 27 | "插件": "插件", 28 | "插件源": "插件源", 29 | "换源": "换源", 30 | "请稍候": "请稍候", 31 | "切换源中...": "切换源中...", 32 | "加载失败": "加载失败", 33 | "网络错误": "网络错误", 34 | "切换源成功": "切换源成功" 35 | }, 36 | 'en-US': { 37 | } 38 | }; 39 | 40 | export const PLUGIN_SOURCES = { 41 | 'Github Raw': 'https://raw.githubusercontent.com/breeze-shell/plugins-packed/refs/heads/main/', 42 | 'Enlysure': 'https://breeze.enlysure.com/', 43 | 'Enlysure Shanghai': 'https://breeze-c.enlysure.com/' 44 | }; 45 | 46 | export const ICON_CONTEXT_MENU = ``; 47 | export const ICON_UPDATE = ``; 48 | export const ICON_PLUGIN_STORE = ``; 49 | export const ICON_PLUGIN_CONFIG = ``; 50 | export const ICON_MORE_VERT = ``; 51 | export const ICON_BREEZE = ``; 52 | 53 | 54 | // Window dimensions 55 | export const WINDOW_WIDTH = 800; 56 | export const WINDOW_HEIGHT = 600; 57 | export const SIDEBAR_WIDTH = 170; 58 | 59 | // Theme presets 60 | export const theme_presets = { 61 | "默认": null, 62 | "紧凑": { 63 | radius: 4.0, 64 | item_height: 20.0, 65 | item_gap: 2.0, 66 | item_radius: 3.0, 67 | margin: 4.0, 68 | padding: 4.0, 69 | text_padding: 6.0, 70 | icon_padding: 3.0, 71 | right_icon_padding: 16.0, 72 | multibutton_line_gap: -4.0 73 | }, 74 | "宽松": { 75 | radius: 6.0, 76 | item_height: 24.0, 77 | item_gap: 4.0, 78 | item_radius: 8.0, 79 | margin: 6.0, 80 | padding: 6.0, 81 | text_padding: 8.0, 82 | icon_padding: 4.0, 83 | right_icon_padding: 20.0, 84 | multibutton_line_gap: -6.0 85 | }, 86 | "圆角": { 87 | radius: 12.0, 88 | item_radius: 12.0 89 | }, 90 | "方角": { 91 | radius: 0.0, 92 | item_radius: 0.0 93 | } 94 | }; 95 | 96 | // Animation presets 97 | export const anim_none = { 98 | easing: "mutation", 99 | }; 100 | 101 | export const animation_presets = { 102 | "默认": null, 103 | "快速": { 104 | "item": { 105 | "opacity": { 106 | "delay_scale": 0 107 | }, 108 | "width": anim_none, 109 | "x": anim_none, 110 | }, 111 | "submenu_bg": { 112 | "opacity": { 113 | "delay_scale": 0, 114 | "duration": 100 115 | } 116 | }, 117 | "main_bg": { 118 | "opacity": anim_none, 119 | } 120 | }, 121 | "无": { 122 | "item": { 123 | "opacity": anim_none, 124 | "width": anim_none, 125 | "x": anim_none, 126 | "y": anim_none 127 | }, 128 | "submenu_bg": { 129 | "opacity": anim_none, 130 | "x": anim_none, 131 | "y": anim_none, 132 | "w": anim_none, 133 | "h": anim_none 134 | }, 135 | "main_bg": { 136 | "opacity": anim_none, 137 | "x": anim_none, 138 | "y": anim_none, 139 | "w": anim_none, 140 | "h": anim_none 141 | } 142 | } 143 | }; -------------------------------------------------------------------------------- /CONFIG.md: -------------------------------------------------------------------------------- 1 | [中文 →](./CONFIG_zh.md) 2 | 3 | # Configuration File Format Description 4 | 5 | This project's configuration file adopts the JSON format, and it is recommended to use **VSCode** for editing. 6 | 7 | The default location of the configuration file is: 8 | `%USERPROFILE%/.breeze-shell/config.json`. 9 | 10 | When the configuration file is saved, the plugin will automatically reload the configuration without requiring a restart. 11 | 12 | **If a black window appears after saving, it indicates an error in the configuration file. Please read the error message in the black window and fix the issue accordingly.** 13 | 14 | --- 15 | 16 | ## Schema 17 | 18 | The JSON Schema for the Breeze Shell configuration file is located at 19 | [resources/schema.json](./resources/schema.json). To enable type checking and autocompletion in VSCode, add the following line to your configuration file: 20 | 21 | ```json 22 | { 23 | "$schema": "https://raw.githubusercontent.com/std-microblock/breeze-shell/refs/heads/master/resources/schema.json" 24 | } 25 | ``` 26 | 27 | This allows VSCode to provide real-time validation and suggestions . 28 | 29 | --- 30 | 31 | ## Configuration File Structure 32 | 33 | The following is a fully annotated default JSON configuration. Note that this **cannot** be directly copied into `config.json` as JSON does not support comments. 34 | 35 | ```json5 36 | { 37 | "context_menu": { 38 | "theme": { 39 | // Use DWM-rounded corners instead of SetWindowRgn rounded corners on Windows 11 40 | "use_dwm_if_available": true, 41 | // Enable acrylic background effect 42 | "acrylic": true, 43 | // Corner radius (only effective when DWM-rounded corners are not used) 44 | "radius": 6.0, 45 | // Font size (adjust to align with scaled integer font sizes to avoid blurring) 46 | "font_size": 14.0, 47 | // Item height 48 | "item_height": 23.0, 49 | // Item spacing 50 | "item_gap": 3.0, 51 | // Item corner radius 52 | "item_radius": 5.0, 53 | // Margin 54 | "margin": 5.0, 55 | // Padding 56 | "padding": 6.0, 57 | // Text padding 58 | "text_padding": 8.0, 59 | // Icon padding 60 | "icon_padding": 4.0, 61 | // Right icon (expand icon) padding 62 | "right_icon_padding": 20.0, 63 | // Horizontal button spacing (negative value to offset item spacing) 64 | "multibutton_line_gap": -6.0, 65 | // Acrylic background color in light themes 66 | "acrylic_color_light": "#fefefe00", 67 | // Acrylic background color in dark themes 68 | "acrylic_color_dark": "#28282800", 69 | // Background opacity 70 | "background_opacity": 1.0, 71 | // Animation settings 72 | "animation": { 73 | // Menu item animations 74 | "item": { 75 | // animated_float_conf: General animation configuration 76 | "opacity": { 77 | // Duration in milliseconds 78 | "duration": 200.0, 79 | // Animation curve 80 | // Options: 81 | // mutation (disable animation), 82 | // linear (linear), 83 | // ease_in, ease_out, ease_in_out (easing curves) 84 | "easing": "ease_in_out", 85 | // Delay scaling factor 86 | // Example: If the original delay is 50ms, 87 | // a delay_scale of 2 results in a 100ms delay 88 | "delay_scale": 1.0 89 | }, 90 | // Same structure as opacity for x, y, width 91 | "x": animated_float_conf, 92 | "y": animated_float_conf, 93 | "width": animated_float_conf 94 | }, 95 | // Main menu background animation 96 | "main_bg": { 97 | "opacity": animated_float_conf, 98 | "x": animated_float_conf, 99 | "y": animated_float_conf, 100 | "w": animated_float_conf, 101 | "h": animated_float_conf 102 | }, 103 | // Submenu background animation (same as main menu) 104 | "submenu_bg": { 105 | ... 106 | } 107 | }, 108 | // Use custom-drawn border/shadow (disable DWM border) 109 | "use_self_drawn_border": true, 110 | // Custom border width example 111 | "border_width": 2.5, 112 | // Gradient support: [linear-gradient(angle, color1, color2) / radial-gradient(radius, color1, color2)] 113 | "border_color_dark": "linear-gradient(30, #DE73DF, #E5C07B)", 114 | "shadow_size": 20, 115 | "shadow_color_dark_from": "#ff000033", 116 | "shadow_color_dark_to": "#00ff0000" 117 | }, 118 | // Enable vertical sync 119 | "vsync": true, 120 | // Do not replace owner-drawn menus 121 | "ignore_owner_draw": true, 122 | // Reverse all items when expanding upward 123 | "reverse_if_open_to_up": true, 124 | // Debug option: search a larger range for icons (not recommended) 125 | "search_large_dwItemData_range": false, 126 | // Positioning settings 127 | "position": { 128 | // Vertical padding 129 | "padding_vertical": 20, 130 | // Horizontal padding 131 | "padding_horizontal": 0 132 | }, 133 | // Enable hotkeys 134 | "hotkeys": true 135 | }, 136 | 137 | // Enable debug console 138 | "debug_console": false, 139 | 140 | // Primary font path 141 | "font_path_main": "C:\\WINDOWS\\Fonts\\segoeui.ttf", 142 | // Fallback font path 143 | "font_path_fallback": "C:\\WINDOWS\\Fonts\\msyh.ttc", 144 | // Use hook to load additional resource strings 145 | "res_string_loader_use_hook": false, 146 | // Debug option: avoid resizing UI windows 147 | "avoid_resize_ui": false, 148 | // Plugin load order (plugins listed first load earlier) 149 | // Format: plugin filename without extension 150 | // Example: "Windows 11 Icon Pack" 151 | "plugin_load_order": [], 152 | // Global default animation 153 | "default_animation": animated_float_conf 154 | } 155 | ``` 156 | 157 | --- 158 | 159 | ## Example Configuration 160 | 161 | ### Disable All Animations 162 | 163 | ```json 164 | { 165 | "default_animation": { 166 | "easing": "mutation" 167 | } 168 | } 169 | ``` 170 | 171 | This configuration disables all animations by setting the easing curve to `mutation` . 172 | -------------------------------------------------------------------------------- /src/shell/contextmenu/menu_widget.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "breeze_ui/animator.h" 3 | #include "breeze_ui/extra_widgets.h" 4 | #include "breeze_ui/nanovg_wrapper.h" 5 | #include "breeze_ui/ui.h" 6 | #include "breeze_ui/widget.h" 7 | #include "contextmenu.h" 8 | #include "shell/config.h" 9 | #include "shell/utils.h" 10 | #include "shell/widgets/background_widget.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace mb_shell { 17 | 18 | struct menu_widget; 19 | 20 | struct menu_item_widget : public ui::widget { 21 | using super = ui::widget; 22 | menu_item item; 23 | ui::sp_anim_float opacity = anim_float(0, 200); 24 | menu_item_widget(); 25 | virtual void reset_appear_animation(float delay); 26 | }; 27 | 28 | struct menu_item_ownerdraw_widget : public menu_item_widget { 29 | using super = menu_item_widget; 30 | owner_draw_menu_info owner_draw; 31 | std::optional img{}; 32 | menu_item_ownerdraw_widget(menu_item item); 33 | void update(ui::update_context &ctx) override; 34 | void render(ui::nanovg_context ctx) override; 35 | void reset_appear_animation(float delay) override; 36 | }; 37 | 38 | struct menu_item_parent_widget : public menu_item_widget { 39 | using super = menu_item_widget; 40 | void update(ui::update_context &ctx) override; 41 | void reset_appear_animation(float delay) override; 42 | }; 43 | 44 | struct menu_item_normal_widget : public menu_item_widget { 45 | using super = menu_item_widget; 46 | ui::sp_anim_float opacity = anim_float(0, 200); 47 | float text_padding = config::current->context_menu.theme.text_padding; 48 | float margin = config::current->context_menu.theme.margin; 49 | bool has_icon_padding = false; 50 | bool has_submenu_padding = false; 51 | float padding = config::current->context_menu.theme.padding; 52 | float icon_padding = config::current->context_menu.theme.icon_padding; 53 | float right_icon_padding = 54 | config::current->context_menu.theme.right_icon_padding; 55 | menu_item_normal_widget(menu_item item); 56 | void reset_appear_animation(float delay) override; 57 | 58 | std::optional icon_img{}; 59 | std::optional icon_unfold_img{}; 60 | 61 | std::shared_ptr submenu_wid = nullptr; 62 | float show_submenu_timer = 0.f; 63 | 64 | ui::sp_anim_float bg_opacity = anim_float(0, 200); 65 | void render(ui::nanovg_context ctx) override; 66 | void update(ui::update_context &ctx) override; 67 | float measure_width(ui::update_context &ctx) override; 68 | bool check_hit(const ui::update_context &ctx) override; 69 | 70 | void hide_submenu(); 71 | void show_submenu(ui::update_context &ctx); 72 | void reload_icon_img(ui::nanovg_context ctx); 73 | }; 74 | 75 | struct menu_item_custom_widget : public menu_item_widget { 76 | using super = menu_item_widget; 77 | std::shared_ptr custom_widget; 78 | menu_item_custom_widget(std::shared_ptr custom_widget) 79 | : custom_widget(custom_widget) {} 80 | void render(ui::nanovg_context ctx) override; 81 | void update(ui::update_context &ctx) override; 82 | float measure_width(ui::update_context &ctx) override; 83 | float measure_height(ui::update_context &ctx) override; 84 | }; 85 | 86 | enum class popup_direction { 87 | // 第一象限 ~ 第四象限 88 | top_left, 89 | top_right, 90 | bottom_left, 91 | bottom_right, 92 | }; 93 | struct menu_item_widget; 94 | struct menu_widget : public ui::flex_widget { 95 | using super = ui::flex_widget; 96 | float bg_padding_vertical = 6; 97 | 98 | std::shared_ptr bg; 99 | 100 | std::shared_ptr bg_submenu; 101 | std::shared_ptr current_submenu; 102 | std::optional> parent_item_widget; 103 | std::vector> rendering_submenus; 104 | 105 | menu_widget *parent_menu = nullptr; 106 | 107 | menu menu_data; 108 | menu_widget(); 109 | popup_direction direction = popup_direction::bottom_right; 110 | void init_from_data(menu menu_data); 111 | bool animate_appear_started = false; 112 | void reset_animation(bool reverse = false); 113 | void update(ui::update_context &ctx) override; 114 | 115 | void update_icon_width(); 116 | 117 | void render(ui::nanovg_context ctx) override; 118 | 119 | bool check_hit(const ui::update_context &ctx) override; 120 | void close(); 121 | }; 122 | 123 | struct screenside_button_group_widget : public ui::flex_widget { 124 | struct button_widget : public ui::widget { 125 | using super = ui::widget; 126 | std::string icon_svg; 127 | std::optional icon{}; 128 | std::function on_click; 129 | button_widget(std::string icon_svg); 130 | 131 | ui::sp_anim_float bg_opacity = anim_float(0, 200); 132 | 133 | void update(ui::update_context &ctx) override; 134 | 135 | void render(ui::nanovg_context ctx) override; 136 | }; 137 | 138 | using super = ui::flex_widget; 139 | screenside_button_group_widget(); 140 | }; 141 | 142 | struct mouse_menu_widget_main : public ui::widget { 143 | float anchor_x = 0, anchor_y = 0; 144 | mouse_menu_widget_main(menu menu_data, float x, float y); 145 | bool position_calibrated = false, direction_calibrated = false; 146 | popup_direction direction; 147 | std::shared_ptr menu_wid; 148 | 149 | void update(ui::update_context &ctx); 150 | 151 | void render(ui::nanovg_context ctx); 152 | 153 | static std::pair 154 | calculate_position(menu_widget *menu_wid, ui::update_context &ctx, 155 | float anchor_x, float anchor_y, 156 | popup_direction direction); 157 | 158 | static popup_direction calculate_direction( 159 | menu_widget *menu_wid, ui::update_context &ctx, float anchor_x, 160 | float anchor_y, 161 | popup_direction prefer_direction = popup_direction::bottom_right); 162 | 163 | void calibrate_position(ui::update_context &ctx, bool animated = true); 164 | void calibrate_direction(ui::update_context &ctx); 165 | }; 166 | 167 | } // namespace mb_shell -------------------------------------------------------------------------------- /src/shell/widgets/background_widget.cc: -------------------------------------------------------------------------------- 1 | #include "background_widget.h" 2 | #include "breeze_ui/extra_widgets.h" 3 | #include "nanovg.h" 4 | #include "shell/config.h" 5 | #include "shell/contextmenu/menu_render.h" 6 | #include "shell/utils.h" 7 | 8 | namespace mb_shell { 9 | 10 | background_widget::background_widget(bool is_main) { 11 | auto light_color = is_light_mode(); 12 | if (is_acrylic_available() && config::current->context_menu.theme.acrylic) { 13 | auto acrylic_color = 14 | light_color 15 | ? parse_color( 16 | config::current->context_menu.theme.acrylic_color_light) 17 | : parse_color( 18 | config::current->context_menu.theme.acrylic_color_dark); 19 | 20 | auto acrylic = std::make_shared( 21 | config::current->context_menu.theme.use_dwm_if_available 22 | ? is_win11_or_later() 23 | : false); 24 | acrylic->acrylic_bg_color = acrylic_color; 25 | acrylic->update_color(); 26 | bg_impl = acrylic; 27 | 28 | if (light_color) 29 | bg_impl->bg_color = nvgRGBAf(1, 1, 1, 0); 30 | else 31 | bg_impl->bg_color = nvgRGBAf(0, 0, 0, 0); 32 | } else { 33 | bg_impl = std::make_shared(); 34 | auto c = light_color ? 1 : 25 / 255.f; 35 | bg_impl->bg_color = nvgRGBAf(c, c, c, 1); 36 | } 37 | 38 | bg_impl->radius->reset_to(config::current->context_menu.theme.radius); 39 | bg_impl->opacity->reset_to(0); 40 | 41 | if (is_main) 42 | config::current->context_menu.theme.animation.main_bg.opacity( 43 | bg_impl->opacity, 0); 44 | else { 45 | config::current->context_menu.theme.animation.submenu_bg.opacity( 46 | bg_impl->opacity, 0); 47 | config::current->context_menu.theme.animation.submenu_bg.x(bg_impl->x, 48 | 0); 49 | config::current->context_menu.theme.animation.submenu_bg.y(bg_impl->y, 50 | 0); 51 | config::current->context_menu.theme.animation.submenu_bg.w( 52 | bg_impl->width, 0); 53 | config::current->context_menu.theme.animation.submenu_bg.h( 54 | bg_impl->height, 0); 55 | } 56 | bg_impl->opacity->animate_to( 57 | 255 * config::current->context_menu.theme.background_opacity); 58 | 59 | opacity = bg_impl->opacity; 60 | x = bg_impl->x; 61 | y = bg_impl->y; 62 | width = bg_impl->width; 63 | height = bg_impl->height; 64 | radius = bg_impl->radius; 65 | bg_color = bg_impl->bg_color; 66 | } 67 | 68 | void background_widget::update(ui::update_context &ctx) { 69 | bg_impl->x->reset_to(x->dest()); 70 | bg_impl->y->reset_to(y->dest()); 71 | bg_impl->width->animate_to(width->dest()); 72 | bg_impl->height->animate_to(height->dest()); 73 | bg_impl->bg_color = bg_color; 74 | bg_impl->update(ctx); 75 | 76 | super::update(ctx); 77 | } 78 | 79 | void background_widget::render(ui::nanovg_context ctx) { 80 | { 81 | auto t = ctx.transaction(); 82 | ctx.globalAlpha(*opacity / 255.f); 83 | auto &theme = config::current->context_menu.theme; 84 | bool light = is_light_mode(); 85 | 86 | bool use_dwm = theme.use_dwm_if_available ? is_win11_or_later() : false; 87 | bool use_self_drawn_border = theme.use_self_drawn_border && !use_dwm; 88 | 89 | float boarder_width = use_self_drawn_border ? theme.border_width : 0.0f; 90 | if (use_self_drawn_border) { 91 | float shadow_size = theme.shadow_size, 92 | shadow_offset_x = theme.shadow_offset_x, 93 | shadow_offset_y = theme.shadow_offset_y; 94 | float corner_radius = theme.radius; 95 | NVGcolor shadow_color_from = 96 | parse_color(light ? theme.shadow_color_light_from 97 | : theme.shadow_color_dark_from), 98 | shadow_color_to = 99 | parse_color(light ? theme.shadow_color_light_to 100 | : theme.shadow_color_dark_to); 101 | 102 | ctx.beginPath(); 103 | ctx.roundedRect(*x - shadow_size + shadow_offset_x, 104 | *y - shadow_size + shadow_offset_y, 105 | *width + shadow_size * 2, *height + shadow_size * 2, 106 | corner_radius + shadow_size); 107 | ctx.fillPaint(ctx.boxGradient(*x + shadow_offset_x, 108 | *y + shadow_offset_y, *width, *height, 109 | corner_radius, shadow_size, 110 | shadow_color_from, shadow_color_to)); 111 | ctx.fill(); 112 | 113 | ctx.beginPath(); 114 | if (theme.inset_border) { 115 | ctx.roundedRect(*x + boarder_width / 2, *y + boarder_width / 2, 116 | *width - boarder_width, *height - boarder_width, 117 | corner_radius); 118 | } else { 119 | ctx.roundedRect(*x, *y, *width, *height, corner_radius); 120 | } 121 | ctx.strokeWidth(boarder_width); 122 | auto border_color = 123 | light ? theme.border_color_light : theme.border_color_dark; 124 | border_color.apply_to_ctx(ctx, *x, *y, *width, *height); 125 | ctx.stroke(); 126 | } 127 | 128 | ctx.globalCompositeOperation(NVG_DESTINATION_IN); 129 | ctx.globalAlpha(1); 130 | auto cl = nvgRGBAf(0, 0, 0, 1 - *opacity / 255.f); 131 | ctx.fillColor(cl); 132 | if (theme.inset_border) 133 | ctx.fillRoundedRect(*x + boarder_width, *y + boarder_width, 134 | *width - boarder_width * 2, 135 | *height - boarder_width * 2, *radius); 136 | else 137 | ctx.fillRoundedRect(*x, *y, *width, *height, *radius); 138 | } 139 | bg_impl->render(ctx); 140 | super::render(ctx); 141 | } 142 | 143 | } // namespace mb_shell 144 | -------------------------------------------------------------------------------- /src/shell/script/binding_types_breeze_ui.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "../paint_color.h" 12 | 13 | namespace ui { 14 | struct widget; 15 | struct render_target; 16 | } // namespace ui 17 | 18 | namespace mb_shell::js { 19 | struct breeze_ui { 20 | struct js_text_widget; 21 | struct js_flex_layout_widget; 22 | struct breeze_paint; 23 | struct js_image_widget; 24 | struct js_spacer_widget; 25 | struct js_widget : public std::enable_shared_from_this { 26 | std::shared_ptr $widget; 27 | 28 | js_widget() = default; 29 | js_widget(std::shared_ptr widget) : $widget(widget) {} 30 | virtual ~js_widget() = default; 31 | 32 | std::optional> $rt_lock(); 33 | 34 | std::vector> children() const; 35 | void append_child(std::shared_ptr child); 36 | void prepend_child(std::shared_ptr child); 37 | void remove_child(std::shared_ptr child); 38 | void append_child_after(std::shared_ptr child, 39 | int after_index); 40 | 41 | void set_animation(std::string variable_name, bool enabled); 42 | 43 | float get_x() const; 44 | void set_x(float x); 45 | float get_y() const; 46 | void set_y(float y); 47 | float get_width() const; 48 | void set_width(float width); 49 | float get_height() const; 50 | void set_height(float height); 51 | 52 | std::variant< 53 | std::shared_ptr, std::shared_ptr, 54 | std::shared_ptr, 55 | std::shared_ptr, std::shared_ptr> 56 | downcast(); 57 | // // Note: You can only certain widgets that can be loaded with 58 | // `downcast()`. 59 | // std::shared_ptr clone(bool with_children = true) const; 60 | }; 61 | 62 | struct js_text_widget : public js_widget { 63 | std::string get_text() const; 64 | void set_text(std::string text); 65 | int get_font_size() const; 66 | void set_font_size(int size); 67 | float get_max_width() const; 68 | void set_max_width(float w); 69 | std::optional> get_color() const; 70 | void 71 | set_color(std::optional> color); 72 | }; 73 | 74 | struct js_flex_layout_widget : public js_widget { 75 | #define DEFINE_PROP(type, name) \ 76 | type get_##name() const; \ 77 | void set_##name(type); 78 | 79 | DEFINE_PROP(bool, auto_size) 80 | 81 | DEFINE_PROP(bool, horizontal) 82 | DEFINE_PROP(float, padding_left) 83 | DEFINE_PROP(float, padding_right) 84 | DEFINE_PROP(float, padding_top) 85 | DEFINE_PROP(float, padding_bottom) 86 | DEFINE_PROP(float, flex_grow) 87 | DEFINE_PROP(float, flex_shrink) 88 | DEFINE_PROP(float, max_height) 89 | DEFINE_PROP(bool, enable_scrolling) 90 | DEFINE_PROP(bool, enable_child_clipping) 91 | DEFINE_PROP(bool, crop_overflow) 92 | std::tuple get_padding() const; 93 | void set_padding(float left, float right, float top, float bottom); 94 | 95 | DEFINE_PROP(std::function, on_click) 96 | DEFINE_PROP(std::function, on_mouse_move) 97 | DEFINE_PROP(std::function, on_mouse_enter) 98 | DEFINE_PROP(std::function, on_mouse_leave) 99 | DEFINE_PROP(std::function, on_mouse_down) 100 | DEFINE_PROP(std::function, on_mouse_up) 101 | 102 | std::string get_justify_content() const; 103 | void set_justify_content(std::string justify); 104 | std::string get_align_items() const; 105 | void set_align_items(std::string align); 106 | 107 | void set_background_color( 108 | std::optional> color); 109 | std::optional> 110 | get_background_color() const; 111 | 112 | DEFINE_PROP(std::shared_ptr, background_paint) 113 | DEFINE_PROP(std::shared_ptr, border_paint) 114 | DEFINE_PROP(float, border_radius) 115 | DEFINE_PROP(float, gap) 116 | void set_border_color( 117 | std::optional> color); 118 | std::optional> 119 | get_border_color() const; 120 | DEFINE_PROP(float, border_width) 121 | 122 | #undef DEFINE_PROP 123 | }; 124 | 125 | struct js_image_widget : public js_widget { 126 | std::string get_svg() const; 127 | void set_svg(std::string svg); 128 | }; 129 | 130 | struct js_spacer_widget : public js_widget { 131 | float get_size() const; 132 | void set_size(float size); 133 | }; 134 | 135 | struct widgets_factory { 136 | static std::shared_ptr create_text_widget(); 137 | static std::shared_ptr 138 | create_flex_layout_widget(); 139 | static std::shared_ptr create_image_widget(); 140 | static std::shared_ptr create_spacer_widget(); 141 | }; 142 | 143 | struct breeze_paint { 144 | paint_color $paint; 145 | static std::shared_ptr from_color(std::string color); 146 | }; 147 | 148 | struct window { 149 | static std::shared_ptr create(std::string title, int width, 150 | int height) { 151 | return create_ex(title, width, height, nullptr); 152 | } 153 | 154 | static std::shared_ptr 155 | create_ex(std::string title, int width, int height, 156 | std::function on_close); 157 | 158 | std::shared_ptr $render_target; 159 | void set_root_widget( 160 | std::shared_ptr widget); 161 | void close(); 162 | }; 163 | }; 164 | } // namespace mb_shell::js -------------------------------------------------------------------------------- /src/shell/qjs_sanitizer.cc: -------------------------------------------------------------------------------- 1 | #include "qjs_sanitizer.h" 2 | 3 | #include "blook/hook.h" 4 | #include "quickjs.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define FN(name) {#name, (void *)&name} 12 | static const std::unordered_map func_to_hook = { 13 | FN(JS_NewObject), 14 | FN(JS_NewArray), 15 | FN(JS_NewCFunction), 16 | FN(JS_NewCFunctionData), 17 | FN(JS_NewPromiseCapability), 18 | FN(JS_NewString), 19 | FN(JS_NewObjectProto), 20 | FN(JS_NewArrayBuffer), 21 | FN(JS_NewDate), 22 | FN(JS_NewSymbol), 23 | FN(JS_NewArrayBufferCopy), 24 | FN(JS_NewTypedArray), 25 | FN(JS_NewUint8Array), 26 | FN(JS_NewUint8ArrayCopy), 27 | FN(JS_NewCFunction2), 28 | FN(JS_NewCFunction3), 29 | FN(JS_NewCFunctionData2), 30 | FN(JS_NewCModule), 31 | FN(JS_ParseJSON), 32 | FN(JS_JSONStringify), 33 | FN(JS_WriteObject), 34 | FN(JS_ReadObject), 35 | FN(JS_EvalFunction), 36 | FN(JS_LoadModule), 37 | FN(JS_SetConstructor), 38 | FN(JS_SetPropertyFunctionList), 39 | FN(JS_AddModuleExport), 40 | FN(JS_AddModuleExportList), 41 | FN(JS_SetModuleExport), 42 | FN(JS_SetModuleExportList), 43 | FN(JS_FreeContext), 44 | FN(JS_DupContext), 45 | FN(JS_SetContextOpaque), 46 | FN(JS_GetContextOpaque), 47 | FN(JS_GetRuntime), 48 | FN(JS_SetClassProto), 49 | FN(JS_GetClassProto), 50 | FN(JS_GetFunctionProto), 51 | FN(JS_AddIntrinsicBaseObjects), 52 | FN(JS_AddIntrinsicDate), 53 | FN(JS_AddIntrinsicEval), 54 | FN(JS_AddIntrinsicRegExpCompiler), 55 | FN(JS_AddIntrinsicRegExp), 56 | FN(JS_AddIntrinsicJSON), 57 | FN(JS_AddIntrinsicProxy), 58 | FN(JS_AddIntrinsicMapSet), 59 | FN(JS_AddIntrinsicTypedArrays), 60 | FN(JS_AddIntrinsicPromise), 61 | FN(JS_AddIntrinsicBigInt), 62 | FN(JS_AddIntrinsicWeakRef), 63 | FN(JS_AddPerformance), 64 | FN(JS_AddIntrinsicDOMException), 65 | FN(JS_IsEqual), 66 | FN(JS_IsStrictEqual), 67 | FN(JS_IsSameValue), 68 | FN(JS_IsSameValueZero), 69 | FN(JS_Throw), 70 | FN(JS_GetException), 71 | FN(JS_HasException), 72 | FN(JS_SetUncatchableError), 73 | FN(JS_ClearUncatchableError), 74 | FN(JS_ResetUncatchableError), 75 | FN(JS_NewError), 76 | FN(JS_NewInternalError), 77 | FN(JS_NewPlainError), 78 | FN(JS_NewRangeError), 79 | FN(JS_NewReferenceError), 80 | FN(JS_NewSyntaxError), 81 | FN(JS_NewTypeError), 82 | FN(JS_ThrowInternalError), 83 | FN(JS_ThrowPlainError), 84 | FN(JS_ThrowRangeError), 85 | FN(JS_ThrowReferenceError), 86 | FN(JS_ThrowSyntaxError), 87 | FN(JS_ThrowTypeError), 88 | FN(JS_ThrowDOMException), 89 | FN(JS_ThrowOutOfMemory), 90 | FN(JS_FreeValue), 91 | FN(JS_DupValue), 92 | FN(JS_ToBool), 93 | FN(JS_ToNumber), 94 | FN(JS_ToInt32), 95 | FN(JS_ToInt64), 96 | FN(JS_ToIndex), 97 | FN(JS_ToFloat64), 98 | FN(JS_ToBigInt64), 99 | FN(JS_ToBigUint64), 100 | FN(JS_ToInt64Ext), 101 | FN(JS_NewStringLen), 102 | FN(JS_NewTwoByteString), 103 | FN(JS_NewAtomString), 104 | FN(JS_ToString), 105 | FN(JS_ToPropertyKey), 106 | FN(JS_ToCStringLen2), 107 | FN(JS_FreeCString), 108 | FN(JS_NewObjectProtoClass), 109 | FN(JS_NewObjectClass), 110 | FN(JS_NewObjectFrom), 111 | FN(JS_NewObjectFromStr), 112 | FN(JS_ToObject), 113 | FN(JS_ToObjectString), 114 | FN(JS_IsFunction), 115 | FN(JS_IsConstructor), 116 | FN(JS_SetConstructorBit), 117 | FN(JS_NewArrayFrom), 118 | FN(JS_GetProxyTarget), 119 | FN(JS_GetProxyHandler), 120 | FN(JS_GetProperty), 121 | FN(JS_GetPropertyUint32), 122 | FN(JS_GetPropertyInt64), 123 | FN(JS_GetPropertyStr), 124 | FN(JS_SetProperty), 125 | FN(JS_SetPropertyUint32), 126 | FN(JS_SetPropertyInt64), 127 | FN(JS_SetPropertyStr), 128 | FN(JS_HasProperty), 129 | FN(JS_IsExtensible), 130 | FN(JS_PreventExtensions), 131 | FN(JS_DeleteProperty), 132 | FN(JS_SetPrototype), 133 | FN(JS_GetPrototype), 134 | FN(JS_GetLength), 135 | FN(JS_SetLength), 136 | FN(JS_SealObject), 137 | FN(JS_FreezeObject), 138 | FN(JS_GetOwnPropertyNames), 139 | FN(JS_GetOwnProperty), 140 | FN(JS_FreePropertyEnum), 141 | FN(JS_Call), 142 | FN(JS_Invoke), 143 | FN(JS_CallConstructor), 144 | FN(JS_CallConstructor2), 145 | FN(JS_Eval), 146 | FN(JS_Eval2), 147 | FN(JS_EvalThis), 148 | FN(JS_EvalThis2), 149 | FN(JS_GetGlobalObject), 150 | FN(JS_IsInstanceOf), 151 | FN(JS_DefineProperty), 152 | FN(JS_DefinePropertyValue), 153 | FN(JS_DefinePropertyValueUint32), 154 | FN(JS_DefinePropertyValueStr), 155 | FN(JS_DefinePropertyGetSet), 156 | FN(JS_GetOpaque2), 157 | FN(JS_DetachArrayBuffer), 158 | FN(JS_GetArrayBuffer), 159 | FN(JS_GetUint8Array), 160 | FN(JS_GetTypedArrayBuffer), 161 | FN(JS_PromiseState), 162 | FN(JS_PromiseResult), 163 | FN(JS_SetIsHTMLDDA), 164 | FN(JS_GetImportMeta), 165 | FN(JS_GetModuleName), 166 | FN(JS_GetModuleNamespace), 167 | FN(JS_EnqueueJob), 168 | FN(JS_WriteObject2), 169 | FN(JS_ReadObject2), 170 | FN(JS_ResolveModule), 171 | FN(JS_GetScriptOrModuleName), 172 | }; 173 | 174 | static std::mutex thread_context_map_mutex; 175 | static std::unordered_map thread_context_map; 176 | 177 | void mb_shell::qjs_sanitizer_enable() { 178 | for (const auto &[name, addr] : func_to_hook) { 179 | blook::VEHHookManager::instance().add_breakpoint( 180 | blook::VEHHookManager::SoftwareBreakpoint { 181 | .address = addr 182 | }, [name](blook::VEHHookManager::VEHHookContext &ctx) { 183 | std::lock_guard lock(thread_context_map_mutex); 184 | auto js_ctx = reinterpret_cast(ctx.exception_info->ContextRecord->Rcx); 185 | if (thread_context_map.find(js_ctx) == thread_context_map.end()) { 186 | thread_context_map[js_ctx] = std::this_thread::get_id(); 187 | } else { 188 | auto existing_thread_id = thread_context_map[js_ctx]; 189 | if (existing_thread_id != std::this_thread::get_id()) { 190 | // Detected cross-thread JS context access 191 | std::cerr << "Detected cross-thread access to JSContext in function " << name << std::endl; 192 | } 193 | } 194 | } 195 | ); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/shell/script/ts/src/config_page/utils.ts: -------------------------------------------------------------------------------- 1 | import * as shell from "mshell"; 2 | import { theme_presets, animation_presets } from "./constants"; 3 | import { menu_controller } from "mshell"; 4 | import { useState, useEffect, memo } from "react"; 5 | 6 | export const useHoverActive = () => { 7 | const [isHovered, setIsHovered] = useState(false); 8 | const [isActive, setIsActive] = useState(false); 9 | 10 | const onMouseEnter = () => setIsHovered(true); 11 | const onMouseLeave = () => setIsHovered(false); 12 | const onMouseDown = () => setIsActive(true); 13 | const onMouseUp = () => setIsActive(false); 14 | 15 | return { isHovered, isActive, onMouseEnter, onMouseLeave, onMouseDown, onMouseUp }; 16 | }; 17 | 18 | 19 | export const showMenu = (callback: (ctl: menu_controller) => void) => { 20 | const menu = menu_controller.create_detached(); 21 | callback(menu); 22 | menu.show_at_cursor(); 23 | } 24 | 25 | // Utility functions for nested object manipulation 26 | export const getNestedValue = (obj: any, path: string) => { 27 | return path.split('.').reduce((o, k) => o?.[k], obj); 28 | }; 29 | 30 | export const setNestedValue = (obj: any, path: string, value: any) => { 31 | const keys = path.split('.'); 32 | const last = keys.pop()!; 33 | const target = keys.reduce((o, k) => o[k] = o[k] || {}, obj); 34 | target[last] = value; 35 | }; 36 | 37 | // Translation helper 38 | export const useTranslation = () => { 39 | const currentLang = shell.breeze.user_language() === 'zh-CN' ? 'zh-CN' : 'en-US'; 40 | const t = (key: string) => { 41 | const languages = { 42 | 'zh-CN': { 43 | "管理 Breeze Shell": "管理 Breeze Shell", 44 | "插件市场": "插件市场", 45 | "加载中...": "加载中...", 46 | "更新中...": "更新中...", 47 | "安装中...": "安装中...", 48 | "新版本已下载,将于下次重启资源管理器生效": "新版本已下载,将于下次重启资源管理器生效", 49 | "更新失败: ": "更新失败: ", 50 | "插件安装成功: ": "插件安装成功: ", 51 | "版本: ": "版本: ", 52 | "作者: ": "作者: ", 53 | "删除": "删除", 54 | "Breeze 设置": "Breeze 设置", 55 | "优先加载插件": "优先加载插件", 56 | "调试控制台": "调试控制台", 57 | "垂直同步": "垂直同步", 58 | "忽略自绘菜单": "忽略自绘菜单", 59 | "向上展开时反向排列": "向上展开时反向排列", 60 | "尝试使用 Windows 11 圆角": "尝试使用 Windows 11 圆角", 61 | "亚克力背景效果": "亚克力背景效果", 62 | "主题": "主题", 63 | "动画": "动画", 64 | "当前源: ": "当前源: ", 65 | "插件": "插件", 66 | "插件源": "插件源", 67 | "换源": "换源", 68 | "请稍候": "请稍候", 69 | "切换源中...": "切换源中...", 70 | "加载失败": "加载失败", 71 | "网络错误": "网络错误", 72 | "切换源成功": "切换源成功" 73 | }, 74 | 'en-US': {} 75 | }; 76 | return languages[currentLang][key] || key; 77 | }; 78 | return { t, currentLang }; 79 | }; 80 | 81 | // Theme preset utilities 82 | export const getAllSubkeys = (presets: any) => { 83 | if (!presets) return []; 84 | const keys = new Set(); 85 | for (const v of Object.values(presets)) { 86 | if (v) 87 | for (const key of Object.keys(v)) { 88 | keys.add(key); 89 | } 90 | } 91 | return [...keys]; 92 | }; 93 | 94 | export const applyPreset = (preset: any, origin: any, presets: any) => { 95 | const allSubkeys = getAllSubkeys(presets); 96 | const newPreset = preset ? { ...preset } : {}; 97 | for (let key in origin) { 98 | if (allSubkeys.includes(key)) continue; 99 | newPreset[key] = origin[key]; 100 | } 101 | return newPreset; 102 | }; 103 | 104 | export const checkPresetMatch = (current: any, preset: any) => { 105 | if (!current) return false; 106 | if (!preset) return false; 107 | return Object.keys(preset).every(key => JSON.stringify(current[key]) === JSON.stringify(preset[key])); 108 | }; 109 | 110 | export const getCurrentPreset = (current: any, presets: any) => { 111 | if (!current) return "默认"; 112 | for (const [name, preset] of Object.entries(presets)) { 113 | if (preset && checkPresetMatch(current, preset)) { 114 | return name; 115 | } 116 | } 117 | return "自定义"; 118 | }; 119 | 120 | // Config file operations 121 | export const loadConfig = () => { 122 | const current_config_path = shell.breeze.data_directory() + '/config.json'; 123 | const current_config = shell.fs.read(current_config_path); 124 | return JSON.parse(current_config); 125 | }; 126 | 127 | export const saveConfig = (config: any) => { 128 | shell.fs.write(shell.breeze.data_directory() + '/config.json', JSON.stringify(config, null, 4)); 129 | }; 130 | 131 | // Plugin utilities 132 | export const loadPlugins = () => { 133 | return shell.fs.readdir(shell.breeze.data_directory() + '/scripts') 134 | .map(v => v.split('/').pop()) 135 | .filter(v => v.endsWith('.js') || v.endsWith('.disabled')) 136 | .map(v => v.replace('.js', '').replace('.disabled', '')); 137 | }; 138 | 139 | export const togglePlugin = (name: string) => { 140 | const path = shell.breeze.data_directory() + '/scripts/' + name; 141 | if (shell.fs.exists(path + '.js')) { 142 | shell.fs.rename(path + '.js', path + '.js.disabled'); 143 | } else if (shell.fs.exists(path + '.js.disabled')) { 144 | shell.fs.rename(path + '.js.disabled', path + '.js'); 145 | } 146 | }; 147 | 148 | export const deletePlugin = (name: string) => { 149 | const path = shell.breeze.data_directory() + '/scripts/' + name; 150 | if (shell.fs.exists(path + '.js')) { 151 | shell.fs.remove(path + '.js'); 152 | } 153 | if (shell.fs.exists(path + '.js.disabled')) { 154 | shell.fs.remove(path + '.js.disabled'); 155 | } 156 | }; 157 | 158 | export const isPluginInstalled = (plugin: any) => { 159 | if (shell.fs.exists(shell.breeze.data_directory() + '/scripts/' + plugin.local_path)) { 160 | return shell.breeze.data_directory() + '/scripts/' + plugin.local_path; 161 | } 162 | if (shell.fs.exists(shell.breeze.data_directory() + '/scripts/' + plugin.local_path + '.disabled')) { 163 | return shell.breeze.data_directory() + '/scripts/' + plugin.local_path + '.disabled'; 164 | } 165 | return null; 166 | }; 167 | 168 | export const getPluginVersion = (installPath: string) => { 169 | const local_version_match = shell.fs.read(installPath).match(/\/\/ @version:\s*(.*)/); 170 | return local_version_match ? local_version_match[1] : '未安装'; 171 | }; -------------------------------------------------------------------------------- /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 rfl { 17 | template <> struct Reflector { 18 | using ReflType = std::string; 19 | 20 | static mb_shell::paint_color to(const ReflType &v) noexcept { 21 | return mb_shell::paint_color::from_string(v); 22 | } 23 | 24 | static ReflType from(const mb_shell::paint_color &v) { 25 | return v.to_string(); 26 | } 27 | }; 28 | } // namespace rfl 29 | 30 | namespace mb_shell { 31 | std::unique_ptr config::current; 32 | config::animated_float_conf config::_default_animation{ 33 | .duration = 150, 34 | .easing = ui::easing_type::ease_in_out, 35 | .delay_scale = 1, 36 | }; 37 | 38 | void config::write_config() { 39 | auto config_file = data_directory() / "config.json"; 40 | std::ofstream ofs(config_file); 41 | if (!ofs) { 42 | std::cerr << "Failed to write config file." << std::endl; 43 | return; 44 | } 45 | 46 | ofs << rfl::json::write(*config::current); 47 | } 48 | void config::read_config() { 49 | auto config_file = data_directory() / "config.json"; 50 | 51 | #ifdef __llvm__ 52 | std::ifstream ifs(config_file); 53 | if (!std::filesystem::exists(config_file)) { 54 | auto config_file = data_directory() / "config.json"; 55 | std::ofstream ofs(config_file); 56 | if (!ofs) { 57 | std::cerr << "Failed to write config file." << std::endl; 58 | } 59 | 60 | ofs << R"({ 61 | "$schema": "https://raw.githubusercontent.com/std-microblock/breeze-shell/refs/heads/master/resources/schema.json" 62 | })"; 63 | } 64 | if (!ifs) { 65 | std::cerr 66 | << "Config file could not be opened. Using default config instead." 67 | << std::endl; 68 | config::current = std::make_unique(); 69 | config::current->debug_console = true; 70 | } else { 71 | std::string json_str; 72 | std::copy(std::istreambuf_iterator(ifs), 73 | std::istreambuf_iterator(), 74 | std::back_inserter(json_str)); 75 | 76 | if (auto json = rfl::json::read(json_str)) { 78 | // parse twice for default value 79 | _default_animation = json.value().default_animation; 80 | json = rfl::json::read(json_str); 82 | config::current = std::make_unique(json.value()); 83 | std::cout << "Config reloaded." << std::endl; 84 | } else { 85 | std::cerr << "Failed to read config file: " << json.error().what() 86 | << "\nUsing default config instead." << std::endl; 87 | config::current = std::make_unique(); 88 | config::current->debug_console = true; 89 | } 90 | } 91 | #else 92 | #pragma message \ 93 | "We don't support loading config file on MSVC because of a bug in MSVC." 94 | dbgout("We don't support loading config file when compiled with MSVC " 95 | "because of a bug in MSVC."); 96 | config::current = std::make_unique(); 97 | config::current->debug_console = true; 98 | #endif 99 | 100 | if (config::current->debug_console) { 101 | ShowWindow(GetConsoleWindow(), SW_SHOW); 102 | } else { 103 | ShowWindow(GetConsoleWindow(), SW_HIDE); 104 | } 105 | } 106 | 107 | std::filesystem::path config::data_directory() { 108 | static std::optional path; 109 | static std::mutex mtx; 110 | std::lock_guard lock(mtx); 111 | 112 | if (!path) { 113 | path = 114 | std::filesystem::path(env("USERPROFILE").value()) / ".breeze-shell"; 115 | } 116 | 117 | if (!std::filesystem::exists(*path)) { 118 | std::filesystem::create_directories(*path); 119 | } 120 | 121 | return path.value(); 122 | } 123 | void config::run_config_loader() { 124 | auto config_path = config::data_directory() / "config.json"; 125 | dbgout("config file: {}", config_path.string()); 126 | config::read_config(); 127 | std::thread([config_path]() { 128 | auto last_mod = std::filesystem::last_write_time(config_path); 129 | while (true) { 130 | if (std::filesystem::last_write_time(config_path) != last_mod) { 131 | last_mod = std::filesystem::last_write_time(config_path); 132 | config::read_config(); 133 | } 134 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 135 | } 136 | }).detach(); 137 | } 138 | void config::animated_float_conf::apply_to(ui::sp_anim_float &anim, 139 | float delay) { 140 | anim->set_duration(duration); 141 | anim->set_easing(easing); 142 | anim->set_delay(delay * delay_scale); 143 | } 144 | void config::animated_float_conf::operator()(ui::sp_anim_float &anim, 145 | float delay) { 146 | apply_to(anim, delay); 147 | } 148 | 149 | std::filesystem::path config::default_main_font() { 150 | return std::filesystem::path(env("WINDIR").value()) / "Fonts" / 151 | "segoeui.ttf"; 152 | } 153 | std::filesystem::path config::default_fallback_font() { 154 | return std::filesystem::path(env("WINDIR").value()) / "Fonts" / "msyh.ttc"; 155 | } 156 | std::string config::dump_config() { return rfl::json::write(*config::current); } 157 | std::filesystem::path config::default_mono_font() { 158 | return std::filesystem::path(env("WINDIR").value()) / "Fonts" / 159 | "consola.ttf"; 160 | } 161 | void config::apply_fonts_to_nvg(NVGcontext *nvg) { 162 | nvgCreateFont(nvg, "main", font_path_main.string().c_str()); 163 | nvgCreateFont(nvg, "fallback", font_path_fallback.string().c_str()); 164 | nvgCreateFont(nvg, "monospace", font_path_monospace.string().c_str()); 165 | nvgAddFallbackFont(nvg, "main", "fallback"); 166 | nvgAddFallbackFont(nvg, "monospace", "main"); 167 | } 168 | void config::animated_float_conf::apply_to(ui::animated_color &anim, 169 | float delay) { 170 | apply_to(anim.r, delay); 171 | apply_to(anim.g, delay); 172 | apply_to(anim.b, delay); 173 | apply_to(anim.a, delay); 174 | } 175 | } // namespace mb_shell -------------------------------------------------------------------------------- /src/shell/entry.cc: -------------------------------------------------------------------------------- 1 | 2 | #include "GLFW/glfw3.h" 3 | #include "blook/blook.h" 4 | 5 | #include "breeze_ui/ui.h" 6 | #include "config.h" 7 | #include "contextmenu/hooks.h" 8 | #include "entry.h" 9 | #include "error_handler.h" 10 | #include "res_string_loader.h" 11 | #include "script/binding_types.hpp" 12 | #include "script/quickjspp.hpp" 13 | #include "script/script.h" 14 | #include "utils.h" 15 | 16 | #include "./contextmenu/contextmenu.h" 17 | #include "./contextmenu/menu_render.h" 18 | #include "./contextmenu/menu_widget.h" 19 | 20 | #include "./taskbar/taskbar.h" 21 | 22 | #include "fix_win11_menu.h" 23 | 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 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | #include 50 | 51 | #include "cpptrace/from_current.hpp" 52 | 53 | #include "qjs_sanitizer.h" 54 | 55 | namespace mb_shell { 56 | window_proc_hook entry::main_window_loop_hook{}; 57 | void main() { 58 | set_thread_locale_utf8(); 59 | AllocConsole(); 60 | freopen("CONOUT$", "w", stdout); 61 | freopen("CONOUT$", "w", stderr); 62 | freopen("CONIN$", "r", stdin); 63 | ShowWindow(GetConsoleWindow(), SW_HIDE); 64 | 65 | install_error_handlers(); 66 | config::run_config_loader(); 67 | 68 | static script_context script_ctx; 69 | std::thread([]() { 70 | auto data_dir = config::data_directory(); 71 | auto script_dir = data_dir / "scripts"; 72 | 73 | if (!std::filesystem::exists(script_dir)) 74 | std::filesystem::create_directories(script_dir); 75 | 76 | script_ctx.watch_folder(script_dir, [&]() { 77 | return !context_menu_hooks::block_js_reload.load(); 78 | }); 79 | }).detach(); 80 | 81 | std::set_terminate([]() { 82 | auto eptr = std::current_exception(); 83 | if (eptr) { 84 | try { 85 | std::rethrow_exception(eptr); 86 | } catch (const std::exception &e) { 87 | std::cerr << "Uncaught exception: " << e.what() << std::endl; 88 | } catch (...) { 89 | std::cerr << "Uncaught exception of unknown type" << std::endl; 90 | } 91 | 92 | ShowWindow(GetConsoleWindow(), SW_SHOW); 93 | std::getchar(); 94 | } 95 | std::abort(); 96 | }); 97 | 98 | wchar_t executable_path[MAX_PATH]; 99 | if (GetModuleFileNameW(NULL, executable_path, MAX_PATH) == 0) { 100 | MessageBoxW(NULL, L"Failed to get executable path", L"Error", 101 | MB_ICONERROR); 102 | return; 103 | } 104 | 105 | auto init_render_global = [&]() { 106 | std::thread([]() { 107 | if (auto res = ui::render_target::init_global(); !res) { 108 | MessageBoxW(NULL, L"Failed to initialize global render target", 109 | L"Error", MB_ICONERROR); 110 | return; 111 | } 112 | }).detach(); 113 | }; 114 | 115 | std::filesystem::path exe_path(executable_path); 116 | auto filename = 117 | exe_path.filename().string() | 118 | std::views::transform([](char c) { return std::tolower(c); }) | 119 | std::ranges::to(); 120 | 121 | if (filename == "explorer.exe" || filename == "360filebrowser64.exe") { 122 | init_render_global(); 123 | res_string_loader::init(); 124 | context_menu_hooks::install_NtUserTrackPopupMenuEx_hook(); 125 | fix_win11_menu::install(); 126 | } 127 | 128 | if (filename == "onecommander.exe") { 129 | init_render_global(); 130 | context_menu_hooks::install_SHCreateDefaultContextMenu_hook(); 131 | res_string_loader::init(); 132 | } 133 | 134 | if (filename == "rundll32.exe") { 135 | auto command_line = std::wstring(GetCommandLineW()); 136 | 137 | if (command_line.contains(L"-taskbar")) { 138 | SetProcessDPIAware(); 139 | CoInitialize(nullptr); 140 | std::thread([]() { 141 | CPPTRACE_TRY { 142 | SetThreadDpiAwarenessContext( 143 | DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); 144 | taskbar_render taskbar; 145 | auto monitor = 146 | MonitorFromPoint({0, 0}, MONITOR_DEFAULTTOPRIMARY); 147 | if (!monitor) { 148 | MessageBoxW(NULL, L"Failed to get primary monitor", 149 | L"Error", MB_ICONERROR); 150 | return; 151 | } 152 | taskbar.monitor.cbSize = sizeof(MONITORINFO); 153 | if (GetMonitorInfoW(monitor, &taskbar.monitor) == 0) { 154 | MessageBoxW(NULL, 155 | (L"Failed to get monitor info: " + 156 | std::to_wstring(GetLastError())) 157 | .c_str(), 158 | L"Error", MB_ICONERROR); 159 | return; 160 | } 161 | taskbar.position = taskbar_render::menu_position::bottom; 162 | if (auto res = taskbar.init(); !res) { 163 | MessageBoxW(NULL, L"Failed to initialize taskbar", 164 | L"Error", MB_ICONERROR); 165 | return; 166 | } 167 | 168 | taskbar.rt.start_loop(); 169 | } 170 | CPPTRACE_CATCH(const std::exception &e) { 171 | std::cerr << "Error in taskbar thread: " << e.what() 172 | << std::endl; 173 | cpptrace::from_current_exception().print(); 174 | } 175 | }).detach(); 176 | } 177 | } 178 | 179 | if (filename == "asan_test.exe") { 180 | // ASAN environment 181 | init_render_global(); 182 | ShowWindow(GetConsoleWindow(), SW_SHOW); 183 | std::thread([]() { 184 | script_ctx.is_js_ready.wait(false); 185 | std::println("Is js ready: {}", script_ctx.is_js_ready.load()); 186 | script_ctx.js->enqueueJob([]() { 187 | script_ctx.js->eval("globalThis.showConfigPage()", "asan.js"); 188 | }); 189 | }).detach(); 190 | } 191 | } 192 | } // namespace mb_shell 193 | 194 | int APIENTRY DllMain(HINSTANCE hInstance, DWORD fdwReason, LPVOID lpvReserved) { 195 | switch (fdwReason) { 196 | case DLL_PROCESS_ATTACH: { 197 | mb_shell::main(); 198 | break; 199 | } 200 | } 201 | return 1; 202 | } 203 | 204 | extern "C" __declspec(dllexport) void func() { 205 | while (true) { 206 | // This function is called by rundll32.exe, which is used to run the 207 | // taskbar in a separate thread. We can use this to keep the taskbar 208 | // running without blocking the main thread. 209 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 210 | } 211 | } -------------------------------------------------------------------------------- /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 "cpptrace/basic.hpp" 15 | #include "utils.h" 16 | 17 | #include "Windows.h" 18 | #include "cpptrace/cpptrace.hpp" 19 | 20 | void show_console() { 21 | if (!GetConsoleWindow()) { 22 | AllocConsole(); 23 | freopen("CONOUT$", "w", stdout); 24 | freopen("CONOUT$", "w", stderr); 25 | } 26 | ShowWindow(GetConsoleWindow(), SW_SHOW); 27 | SetForegroundWindow(GetConsoleWindow()); 28 | } 29 | 30 | inline void output_crash_header(std::stringstream &ss) { 31 | ss << "Breeze Shell " << BREEZE_VERSION << " crash report" << std::endl; 32 | ss << "----------------------------------------" << std::endl; 33 | ss << "Build date: " << BREEZE_BUILD_DATE_TIME << std::endl; 34 | ss << "Git commit hash: " << BREEZE_GIT_COMMIT_HASH << std::endl; 35 | ss << "Commit page: https://github.com/std-microblock/breeze-shell/commit/" 36 | << BREEZE_GIT_COMMIT_HASH << std::endl; 37 | ss << "Git branch: " << BREEZE_GIT_BRANCH_NAME << std::endl; 38 | ss << "Data directory: " << mb_shell::config::data_directory() << std::endl; 39 | ss << "Windows version: " 40 | << (mb_shell::is_win11_or_later() ? "11 or later" : "10 or earlier") 41 | << std::endl; 42 | ss << "----------------------------------------" << std::endl; 43 | ss << "Config:" << std::endl; 44 | ss << mb_shell::config::dump_config() << std::endl; 45 | ss << "----------------------------------------" << std::endl; 46 | } 47 | 48 | std::string GetExceptionName(EXCEPTION_POINTERS *ExceptionInfo) { 49 | std::string exceptionName; 50 | switch (ExceptionInfo->ExceptionRecord->ExceptionCode) { 51 | case EXCEPTION_ACCESS_VIOLATION: 52 | exceptionName = "ACCESS_VIOLATION"; 53 | break; 54 | case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: 55 | exceptionName = "ARRAY_BOUNDS_EXCEEDED"; 56 | break; 57 | case EXCEPTION_BREAKPOINT: 58 | exceptionName = "BREAKPOINT"; 59 | break; 60 | case EXCEPTION_DATATYPE_MISALIGNMENT: 61 | exceptionName = "DATATYPE_MISALIGNMENT"; 62 | break; 63 | case EXCEPTION_FLT_DENORMAL_OPERAND: 64 | exceptionName = "FLT_DENORMAL_OPERAND"; 65 | break; 66 | case EXCEPTION_FLT_DIVIDE_BY_ZERO: 67 | exceptionName = "FLT_DIVIDE_BY_ZERO"; 68 | break; 69 | case EXCEPTION_FLT_INEXACT_RESULT: 70 | exceptionName = "FLT_INEXACT_RESULT"; 71 | break; 72 | case EXCEPTION_FLT_INVALID_OPERATION: 73 | exceptionName = "FLT_INVALID_OPERATION"; 74 | break; 75 | case EXCEPTION_FLT_OVERFLOW: 76 | exceptionName = "FLT_OVERFLOW"; 77 | break; 78 | case EXCEPTION_FLT_STACK_CHECK: 79 | exceptionName = "FLT_STACK_CHECK"; 80 | break; 81 | case EXCEPTION_FLT_UNDERFLOW: 82 | exceptionName = "FLT_UNDERFLOW"; 83 | break; 84 | case EXCEPTION_ILLEGAL_INSTRUCTION: 85 | exceptionName = "ILLEGAL_INSTRUCTION"; 86 | break; 87 | case EXCEPTION_IN_PAGE_ERROR: 88 | exceptionName = "IN_PAGE_ERROR"; 89 | break; 90 | case EXCEPTION_INT_DIVIDE_BY_ZERO: 91 | exceptionName = "INT_DIVIDE_BY_ZERO"; 92 | break; 93 | case EXCEPTION_INT_OVERFLOW: 94 | exceptionName = "INT_OVERFLOW"; 95 | break; 96 | case EXCEPTION_INVALID_DISPOSITION: 97 | exceptionName = "INVALID_DISPOSITION"; 98 | break; 99 | case EXCEPTION_NONCONTINUABLE_EXCEPTION: 100 | exceptionName = "NONCONTINUABLE_EXCEPTION"; 101 | break; 102 | case EXCEPTION_PRIV_INSTRUCTION: 103 | exceptionName = "PRIV_INSTRUCTION"; 104 | break; 105 | case EXCEPTION_SINGLE_STEP: 106 | exceptionName = "SINGLE_STEP"; 107 | break; 108 | case EXCEPTION_STACK_OVERFLOW: 109 | exceptionName = "STACK_OVERFLOW"; 110 | break; 111 | default: 112 | exceptionName = "UNKNOWN"; 113 | break; 114 | } 115 | return exceptionName; 116 | } 117 | 118 | void mb_shell::install_error_handlers() { 119 | SetUnhandledExceptionFilter([](PEXCEPTION_POINTERS ex) -> LONG { 120 | show_console(); 121 | 122 | std::ofstream file(config::data_directory().string() + 123 | "\\crash_report.txt"); 124 | 125 | std::stringstream ss; 126 | output_crash_header(ss); 127 | ss << "Exception code: " << std::hex 128 | << ex->ExceptionRecord->ExceptionCode << "(" << GetExceptionName(ex) 129 | << ")" << std::endl; 130 | ss << "Exception flags: " << std::hex 131 | << ex->ExceptionRecord->ExceptionFlags << std::endl; 132 | ss << "Exception address: " << std::hex 133 | << ex->ExceptionRecord->ExceptionAddress << std::endl; 134 | ss << "Registers:" << std::endl; 135 | 136 | ss << "RAX: " << std::hex << ex->ContextRecord->Rax << std::endl; 137 | ss << "RBX: " << std::hex << ex->ContextRecord->Rbx << std::endl; 138 | ss << "RCX: " << std::hex << ex->ContextRecord->Rcx << std::endl; 139 | ss << "RDX: " << std::hex << ex->ContextRecord->Rdx << std::endl; 140 | ss << "R8: " << std::hex << ex->ContextRecord->R8 << std::endl; 141 | ss << "R9: " << std::hex << ex->ContextRecord->R9 << std::endl; 142 | ss << "R10: " << std::hex << ex->ContextRecord->R10 << std::endl; 143 | ss << "R11: " << std::hex << ex->ContextRecord->R11 << std::endl; 144 | ss << "R12: " << std::hex << ex->ContextRecord->R12 << std::endl; 145 | ss << "R13: " << std::hex << ex->ContextRecord->R13 << std::endl; 146 | ss << "R14: " << std::hex << ex->ContextRecord->R14 << std::endl; 147 | ss << "R15: " << std::hex << ex->ContextRecord->R15 << std::endl; 148 | ss << "RDI: " << std::hex << ex->ContextRecord->Rdi << std::endl; 149 | ss << "RSI: " << std::hex << ex->ContextRecord->Rsi << std::endl; 150 | ss << "RBP: " << std::hex << ex->ContextRecord->Rbp << std::endl; 151 | ss << "RSP: " << std::hex << ex->ContextRecord->Rsp << std::endl; 152 | ss << "RIP: " << std::hex << ex->ContextRecord->Rip << std::endl; 153 | 154 | ss << "Stack trace:" << std::endl; 155 | ss << cpptrace::stacktrace::current().to_string() << std::endl; 156 | 157 | if (file.is_open()) { 158 | file << ss.str(); 159 | file.flush(); 160 | file.close(); 161 | } 162 | std::cerr << ss.str(); 163 | 164 | Sleep(5000); 165 | return EXCEPTION_CONTINUE_EXECUTION; 166 | }); 167 | 168 | std::set_terminate([]() { 169 | show_console(); 170 | 171 | std::stringstream ss; 172 | output_crash_header(ss); 173 | try { 174 | throw std::current_exception(); 175 | } catch (const std::exception &e) { 176 | ss << "Uncaught exception: " << e.what() << std::endl; 177 | } catch (...) { 178 | ss << "Uncaught exception of unknown type" << std::endl; 179 | } 180 | 181 | ss << "Stack trace:" << std::endl; 182 | ss << cpptrace::stacktrace::current().to_string() << std::endl; 183 | 184 | std::ofstream file(config::data_directory().string() + 185 | "\\crash_report.txt"); 186 | 187 | if (file.is_open()) { 188 | file << ss.str(); 189 | file.flush(); 190 | file.close(); 191 | } 192 | 193 | std::cerr << ss.str(); 194 | 195 | Sleep(5000); 196 | }); 197 | } 198 | -------------------------------------------------------------------------------- /src/shell/script/ts/src/config_page/ContextMenuConfig.tsx: -------------------------------------------------------------------------------- 1 | import * as shell from "mshell"; 2 | import { Button, Text, Toggle } from "./components"; 3 | import { ContextMenuContext, DebugConsoleContext } from "./contexts"; 4 | import { useTranslation, getNestedValue, setNestedValue } from "./utils"; 5 | import { theme_presets, animation_presets } from "./constants"; 6 | import { memo, useContext } from "react"; 7 | const ContextMenuConfig = memo(() => { 8 | const { config, update } = useContext(ContextMenuContext)!; 9 | const { value: debugConsole, update: updateDebugConsole } = useContext(DebugConsoleContext)!; 10 | const { t } = useTranslation(); 11 | 12 | const currentTheme = config?.theme; 13 | const currentAnimation = config?.theme?.animation; 14 | 15 | const getAllSubkeys = (presets: any) => { 16 | if (!presets) return []; 17 | const keys = new Set(); 18 | for (const v of Object.values(presets)) { 19 | if (v) 20 | for (const key of Object.keys(v)) { 21 | keys.add(key); 22 | } 23 | } 24 | return [...keys]; 25 | }; 26 | 27 | const applyPreset = (preset: any, origin: any, presets: any) => { 28 | const allSubkeys = getAllSubkeys(presets); 29 | const newPreset = preset ? { ...preset } : {}; 30 | for (let key in origin) { 31 | if (allSubkeys.includes(key)) continue; 32 | newPreset[key] = origin[key]; 33 | } 34 | return newPreset; 35 | }; 36 | 37 | const checkPresetMatch = (current: any, preset: any) => { 38 | if (!current) return false; 39 | if (!preset) return false; 40 | return Object.keys(preset).every(key => JSON.stringify(current[key]) === JSON.stringify(preset[key])); 41 | }; 42 | 43 | const getCurrentPreset = (current: any, presets: any) => { 44 | if (!current) return "默认"; 45 | for (const [name, preset] of Object.entries(presets)) { 46 | if (preset && checkPresetMatch(current, preset)) { 47 | return name; 48 | } 49 | } 50 | return "自定义"; 51 | }; 52 | 53 | const currentThemePreset = getCurrentPreset(currentTheme, theme_presets); 54 | const currentAnimationPreset = getCurrentPreset(currentAnimation, animation_presets); 55 | 56 | return ( 57 | 58 | {t("Breeze 设置")} 59 | 60 | 61 | {t("主题")} 62 | 63 | {Object.keys(theme_presets).map(name => ( 64 | 83 | ))} 84 | 85 | 86 | 87 | 88 | {t("动画")} 89 | 90 | {Object.keys(animation_presets).map(name => ( 91 | 109 | ))} 110 | 111 | 112 | 113 | 114 | {t("杂项")} 115 | 116 | { 117 | const newConfig = { ...config }; 118 | setNestedValue(newConfig, "vsync", v); 119 | update(newConfig); 120 | }} /> 121 | { 122 | const newConfig = { ...config }; 123 | setNestedValue(newConfig, "ignore_owner_draw", v); 124 | update(newConfig); 125 | }} /> 126 | { 127 | const newConfig = { ...config }; 128 | setNestedValue(newConfig, "reverse_if_open_to_up", v); 129 | update(newConfig); 130 | }} /> 131 | { 132 | const newConfig = { ...config }; 133 | setNestedValue(newConfig, "theme.use_dwm_if_available", v); 134 | update(newConfig); 135 | }} /> 136 | { 137 | const newConfig = { ...config }; 138 | setNestedValue(newConfig, "theme.acrylic", v); 139 | update(newConfig); 140 | }} /> 141 | { 142 | const newConfig = { ...config }; 143 | setNestedValue(newConfig, "hotkeys", v); 144 | update(newConfig); 145 | }} /> 146 | { 147 | const newConfig = { ...config }; 148 | setNestedValue(newConfig, "show_settings_button", v); 149 | update(newConfig); 150 | }} /> 151 | 152 | 153 | ); 154 | }); 155 | 156 | export default ContextMenuConfig; --------------------------------------------------------------------------------