From 85f7e634236b86057165c70af3c07b2c2e8d348c Mon Sep 17 00:00:00 2001 From: 12tall Date: Mon, 11 Oct 2021 21:35:48 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yaml | 77 ++++++++++ .gitignore | 3 +- Cargo.lock | 74 ++++++---- Cargo.toml | 4 + README.md | 108 +++++++------- res/README.md | 2 + logo128.ico => res/logo128.ico | Bin src/main.rs | 135 +++--------------- src/utils/mod.rs | 101 +++++++++++++ ...51\345\235\221\346\227\245\350\256\260.md" | 59 ++++++++ 10 files changed, 370 insertions(+), 193 deletions(-) create mode 100644 .github/workflows/build.yaml create mode 100644 res/README.md rename logo128.ico => res/logo128.ico (100%) create mode 100644 src/utils/mod.rs create mode 100644 "\350\270\251\345\235\221\346\227\245\350\256\260.md" diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..6a1c790 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,77 @@ +name: build_yan_for_windows +on: + push: + branches: + - "master" +jobs: + release-all: + name: release + needs: [build-win] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + # 读取json 文件的属性 + - name: Read Properties + id: version # step_id 在后面会用到 + uses: ashley-taylor/read-json-property-action@v1.0 + with: + path: ./web_src/package.json # 文件路径 + property: version # 相应的字段 + - name: create release + id: create_release + uses: actions/create-release@master + env: + GITHUB_TOKEN: ${{ secrets.TOKEN }} + with: + # 根据版本号打标签 + tag_name: v${{steps.version.outputs.value}} + release_name: Release v${{steps.version.outputs.value}} + draft: false + prerelease: false + - uses: actions/download-artifact@v2 + with: + name: release + - name: upload win + id: upload-windows + uses: actions/upload-release-asset@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./yan-win.zip + asset_name: yan-win.zip + asset_content_type: application/zip + + build-win: + name: build for windows + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@v2 + + - name: install rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + + - name: install node/npm + uses: actions/setup-node@v2 + with: + node-version: '14' + - name: build + # 编译后在根目录生成index.node + run: | + npm install -g yarn + cd web_src + yarn + yarn run build + npm uninstall -g yarn + cd ../ + cargo build --release + - name: zip windows artifact + run: | + powershell Compress-Archive ./target/release/yan.exe yan-win.zip + - uses: actions/upload-artifact@v2 + with: + name: release + path: ./yan-win.zip \ No newline at end of file diff --git a/.gitignore b/.gitignore index eacd62a..434f77d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ web/ -target/ \ No newline at end of file +target/ +yan.exe \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 7eb5c5f..cc7f364 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,9 +100,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" [[package]] name = "cfg-expr" @@ -452,9 +452,9 @@ dependencies = [ [[package]] name = "gdk" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a453eae5ec10345b3a96ca1b547328bfc94edd40aa95b08f14bb4c35863db140" +checksum = "b9d749dcfc00d8de0d7c3a289e04a04293eb5ba3d8a4e64d64911d481fa9933b" dependencies = [ "bitflags", "cairo-rs", @@ -510,9 +510,9 @@ dependencies = [ [[package]] name = "gio" -version = "0.14.6" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a29d8062af72045518271a2cd98b4e1617ce43f5b4223ad0fb9a0eff8f718c" +checksum = "711c3632b3ebd095578a9c091418d10fed492da9443f58ebc8f45efbeb215cb0" dependencies = [ "bitflags", "futures-channel", @@ -553,9 +553,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.14.5" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a930b7208e6e0ab839eea5f65ac2b82109f729621430d47fe905e2e09d33f4" +checksum = "7c515f1e62bf151ef6635f528d05b02c11506de986e43b34a5c920ef0b3796a4" dependencies = [ "bitflags", "futures-channel", @@ -635,9 +635,9 @@ dependencies = [ [[package]] name = "gtk" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6603bb79ded6ac6f3bac203794383afa8b1d6a8656d34a93a88f0b22826cd46c" +checksum = "2eb51122dd3317e9327ec1e4faa151d1fa0d95664cd8fb8dcfacf4d4d29ac70c" dependencies = [ "atk", "bitflags", @@ -933,6 +933,18 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d" +[[package]] +name = "nix" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -1022,9 +1034,9 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "pango" -version = "0.14.3" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc88307d9797976ea62722ff2ec5de3fae279c6e20100ed3f49ca1a4bf3f96" +checksum = "546fd59801e5ca735af82839007edd226fe7d3bb06433ec48072be4439c28581" dependencies = [ "bitflags", "glib", @@ -1175,9 +1187,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ "proc-macro2", ] @@ -1202,9 +1214,9 @@ dependencies = [ [[package]] name = "rfd" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b4745feef3bc92e709042c78d205e9a566ec46e18c58d4fac458e6fc12627e" +checksum = "2acac5884e3a23b02ebd6ce50fd2729732cdbdb16ea944fbbfbfa638a67992aa" dependencies = [ "block", "dispatch", @@ -1293,6 +1305,19 @@ dependencies = [ "serde", ] +[[package]] +name = "single-instance" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e235a472aa6d6b39ac39f2a314590396ba724528298f1d56fe4da71ddc1064" +dependencies = [ + "libc", + "nix", + "thiserror", + "widestring", + "winapi", +] + [[package]] name = "slab" version = "0.4.4" @@ -1364,9 +1389,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.78" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4eac2e6c19f5c3abc0c229bea31ff0b9b091c7b14990e8924b92902a303a0c0" +checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" dependencies = [ "proc-macro2", "quote", @@ -1471,18 +1496,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.29" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.29" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", @@ -1521,9 +1546,9 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "unicode-bidi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" [[package]] name = "unicode-normalization" @@ -1799,5 +1824,6 @@ dependencies = [ "include_dir", "mime", "rfd", + "single-instance", "wry", ] diff --git a/Cargo.toml b/Cargo.toml index 83816cc..b42de85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,9 @@ name = "yan" version = "0.1.0" edition = "2018" +[build] +target-dir = "../output" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [[bin]] name = "yan" # bin 文件不要与lib 文件重名 @@ -13,6 +16,7 @@ name = "libyan" crate-type = ["staticlib", "cdylib"] [dependencies] +single-instance = "0.3.2" wry = "0.12.2" rfd = "0.5.0" include_dir = "0.6.2" diff --git a/README.md b/README.md index 73ff607..6ec8527 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,61 @@ -一次基于`wry`的本地可执行程序模板,以`vue3`作为前端、`rust`作为后端的尝试。至于为什么不用`Electron`或者`tauri`: - - `Electron`打包后的文件体积比较大,且资源文件不能集成到可执行程序; - - `tauri`虽然功能丰富,但是学习起来不如更底层的实现容易。 +# 燕/yan -项目名称源自唐代诗人刘禹锡的《乌衣巷》:`旧时王谢堂前燕 飞入寻常百姓家`。 +`旧时王谢堂前燕 飞入寻常百姓家`——《乌衣巷》唐·刘禹锡。 -## 调试与打包 +此项目是一个基于`rust`和`webview`的模板。后端采用`wry`,前端采用`vue3`作为骨架,前后端通过IPC 进程通信。至于为什么不直接用`Electron`或者`tauri`?首先`Electron`打包后的文件体积比较大,但是打包后的结果文件不够简洁;而`tauri`由于封装程度比较高,并不适合拿来学习理解整个项目的实现原理。 + +当然,通过`webview(2)`也有一些瑕疵,即无法直接调用平台的某些api、需要自己设置`user_data_directory`的位置。这些都是值得的,因为这些问题即使这次不遇到,以后也可能会遇到。就像《天月神话》里面说的,根本就没有银弹。 + +----- + +## 说明 +由于没有嵌入`Chromium`,编译后的结果文件会比较小;但是这也导致要求系统必须已经预装了`Webview`运行时。当然也可以通过`set_env`来指定固定版本的`webview(2)`运行时,但是这样的编译结果就违背了简洁的初衷。另外,某些跨平台的代码,需要添加宏命令来实现条件编译。 + +### Rust 后端 +1. `Object`对象可以通过`get`方法访问键值; +2. 修改图标:需要引用`ico`库,详细代码参见[wry/examples/icon.rs](https://github.com/tauri-apps/wry/blob/6ffd1d7194bda9ca1434fa2ca0d0bd0c8237f01f/examples/icon.rs),需要注意的是源代码没有包含在`wry`库中,且有些类型已重新命名。 +3. 条件编译 + - `#[cfg(debug_assertions)]`:判断程序是否处于debug 状态,可用于任何语句块之前 + - `#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]`,添加在`main.rs`最前面 + + +### Vue前端 +1. `vscode`的插件不能正确识别`web_srv`子项目,所以最好能在新的IDE 窗口修改前端部分; +2. 前端事件的监听器可以是异步函数; + + +## 依赖的库 +不必过于担心引入过多的库而导致程序运行效率低下,因为: + 1. 事情总要做,不是自己做就是别人做; + 2. 编译器会帮我们优化; + 3. 开发效率也很重要,并且引入三方库让代码更整洁。 +### rust 库 + +序号|名称|说明 +:---:|:---|:--- +01|Geal/nom|二进制文件解析 +02|WLBF/single-instance|跨平台应用单例模式 +03|BurntSushi/byteorder|Read trait 扩展,可用于二进制文件解析,比nom 更基础 +04|tauri-app/wry|基于tao,支持webview2,生成桌面应用 +05|Michael-F-Bryan/include_dir|将文件夹以二进制的形式打包进exe +06|PolyMeilex/rfd|跨平台的对话框库 +07|hyperium/mime|MIME 文件类型支持 +08|retep998/winapi-rs|win32api 的封装 +09|microsoft/windows-rs|微软官方库,但有些东西不全 +10|mdsteele/rust-ico|ico 图标文件支持 + +### js 库 + +序号|名称|说明 +:---:|:---|:--- +01|vue3|前端框架 +02|jiggzson/nerdamer|数学符号计算库 + + +## 调试与打包 ### 调试 +调试模式下,需要启动一个vue 的web 服务,可以实现前端代码修改的实时同步。 ```bash # 1. 启动vue(需要新开一个控制台) cd web_src @@ -17,7 +66,8 @@ yarn dev # 此进程会阻塞 cargo run ``` -### 打包 +### 发布 +发布模式下,需要先编译前端vue 代码,然后再编译rust 代码。 ```bash # 1. 打包web 页面 cd web_src @@ -27,48 +77,4 @@ yarn build # 2. 打包exe cd .. # 切换回根目录 cargo build --release -``` - -## Vue前端 -1. 创建`vue3`项目:`yarn create vite web_src --template vue`; -2. 在`vite.config.js`中修改输出目录; -```js -// vite.config.js -export default defineConfig({ - // ... , - build: { - outDir: '../web', // 打包结果输出目录 - }, - server: { - port: 8341, // 指定调试服务的的各种信息 - } -}) -``` -3. 项目有一些缺点,当然也有一个优点:由于是调用系统的`webview`功能,打包后的可执行文件体积非常小! - - `vscode`的插件不能正确识别`web_srv`子项目,所以最好能在新的IDE 窗口修改前端部分。 - - 有些跨平台的代码需要手动添加宏操作,例如图标等资源文件打包、窗口管理等 -4. 前端事件的监听器可以是异步函数 - -## Rust后端 -1. 以`wry`作为后端:`wry = "0.12.2"`; -2. 将`web`作为二进制文件包含至`exe`:`include_dir = "0.6.2"`; -3. `webview`不能直接在前端获取本地文件路径,所以需要`rfd`来从后端选择并处理文件; -4. 根据文件后缀名获取`MIME`类型:`mime = "0.3.16"`; -5. PE 文件在`windows.rs`中有定义,基本上所有的类型在这个`binding`中都能找到; -6. 通过`rfd`调用系统的对话框,但是需要根据平台去设置初始路径、或者干脆不设置; -7. `Object`对象可以通过`get`方法访问键值; -8. 修改图标:需要引用`ico`库,详细代码参见[wry/examples/icon.rs](https://github.com/tauri-apps/wry/blob/6ffd1d7194bda9ca1434fa2ca0d0bd0c8237f01f/examples/icon.rs),需要注意的是源代码没有包含在`wry`库中,且有些类型已重新命名。 -9. 引入`byteorder = "1.4.3"`以二进制的形式读取文件,并可以区分大小端字节序,记得要先将`File`封装为`BufReader` - - -### 条件编译 - -1. 调试状态 - - `#[cfg(debug_assertions)]`:判断程序是否处于debug 状态 - - `#[cfg(not(debug_assertions))]`:可以用于任何语句(块)前面 -2. 取消Windows 下的命令行窗口: - - 在`main.rs`最上方添加:`#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]` - - -### 有用的库 -1. [二进制文件解析](https://github.com/Geal/nom) \ No newline at end of file +``` \ No newline at end of file diff --git a/res/README.md b/res/README.md new file mode 100644 index 0000000..bbc3aee --- /dev/null +++ b/res/README.md @@ -0,0 +1,2 @@ +# res +rust 代码会用到的资源文件,不会用在vue 子项目中 \ No newline at end of file diff --git a/logo128.ico b/res/logo128.ico similarity index 100% rename from logo128.ico rename to res/logo128.ico diff --git a/src/main.rs b/src/main.rs index 293545a..88f164f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,117 +1,28 @@ -// use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; -// use std::{ -// fs::File, -// io::{self, BufReader, Read, Seek, SeekFrom}, -// mem -// }; -// use std::env; -// // representation 描述、表示 -// // packaged 表示紧密排列 -// #[repr(C, packed)] -// #[derive(Debug)] -// struct DosHeader { -// magic: [u8; 2], -// } -// fn main() { -// env::set_var( -// "WEBVIEW2_BROWSER_EXECUTABLE_FOLDER", -// "./", -// ); -// let mut pe = File::open("yan.exe").unwrap(); -// let mut dh = unsafe { mem::zeroed::() }; -// let mz = pe.read_exact(& mut dh.magic); -// // let mz= std::ascii::escape_default(mz); - -// println!("{:X?}", dh.magic); -// } - +// release 版,关闭控制台窗口 #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use std::{env, io::Cursor, path::PathBuf}; +mod utils; +use std::io::Result; -use ico::*; use rfd::FileDialog; +use utils::{check_process_mutex, read_icon}; use wry::{ - self, application::{ event::{Event, StartCause, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::{Icon, Window, WindowBuilder}, + event_loop::ControlFlow, + window::Window, }, - http::Response, - webview::{RpcRequest, RpcResponse, WebContext, WebViewBuilder}, + webview::{RpcRequest, RpcResponse}, Value, }; -fn main() { - // 使用指定版本的Webview2 - // 前提是要用expand 指令解压cab 文件 - // cab 文件需要在https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section 下载 - // env::set_var( - // "WEBVIEW2_BROWSER_EXECUTABLE_FOLDER", - // "D:\\code\\yan\\wv2", - // ); - - // 1. 创建窗口事件循环对象 - let evl = EventLoop::new(); - - // use a window icon - let by = include_bytes!("../logo128.ico"); - let icon_dir = IconDir::read(Cursor::new(by)).unwrap(); - let image = icon_dir.entries()[0].decode().unwrap(); - let rgba = image.rgba_data(); - let icon = Icon::from_rgba(rgba.to_vec(), image.height(), image.width()).unwrap(); - - // 2. 创建窗口并绑定事件循环 - let win = WindowBuilder::new() - .with_title("My App") - .with_window_icon(Some(icon)) - .build(&evl) - .unwrap(); - // 3. 在本地窗口种创建webview,并加载 - let web = WebViewBuilder::new(win).unwrap(); - - // 调试模式下加载url - #[cfg(debug_assertions)] - let web = web.with_url("http://localhost:8341/").unwrap(); - // release 模式下加载html 文件 - // 需要自定义协议的支持 - #[cfg(not(debug_assertions))] - let web = { - use include_dir::{include_dir, Dir}; - use wry::http::{status::StatusCode, ResponseBuilder}; - const WEB_DIR: Dir = include_dir!("./web"); - web.with_custom_protocol("wry".into(), move |req| { - // 1. 获取请求的url, - // 如果协议格式为wry://index.html,程序会把index.html 做路径处理 - let path = req.uri().replace("wry://web/", ""); +use crate::utils::{create_app, create_webview}; - // 2. 获取文件内容并设置响应类型 - let (code, data, meta) = match WEB_DIR.get_file(&path) { - Some(file) => match path.split(".").last().unwrap() { - "html" => (200, file.contents(), mime::TEXT_HTML), - "js" => (200, file.contents(), mime::TEXT_JAVASCRIPT), - "css" => (200, file.contents(), mime::TEXT_CSS), - "ico" | "png" => (200, file.contents(), mime::IMAGE_PNG), - "jpg" => (200, file.contents(), mime::IMAGE_JPEG), - "bmp" => (200, file.contents(), mime::IMAGE_BMP), - _ => (404, "not found".as_bytes(), mime::TEXT_PLAIN), - }, - None => (404, "not found".as_bytes(), mime::TEXT_PLAIN), - }; - - // 3. 构建返回体返回 - ResponseBuilder::new() - .status(StatusCode::from_u16(code).unwrap()) - .mimetype(meta.essence_str()) - .body(Vec::from(data)) - }) - // 见步骤1. - .with_url("wry://web/index.html") - .unwrap() - }; - - let handler = move |window: &Window, mut req: RpcRequest| { +fn main() -> Result<()> { + let _inst = check_process_mutex()?; + let icon = read_icon(include_bytes!("../res/logo128.ico")); + let (win, evl) = create_app(icon); + let webview = create_webview(win, move |_window: &Window, mut req: RpcRequest| { let mut response = None; // 解析rpc 的参数 println!("{:?}", req.params?.get(0).unwrap().get("message")); @@ -129,21 +40,8 @@ fn main() { )); } response - }; - - // 自定义user_data_directory 路径 - // 否则会在exe 下面出现一个文件夹,比较烦人 - // 还需要添加路径是否存在的判定 - let mut user_data_dir = PathBuf::from(env::var("appdata").unwrap()); - user_data_dir.push("yan"); - let mut webctx = WebContext::new(Some(user_data_dir)); - let mut web = web - .with_rpc_handler(handler) - .with_web_context(&mut webctx) - .build() - .unwrap(); + }); - // 4. 设置事件处理 evl.run(move |evt, _, control_flow| { *control_flow = ControlFlow::Wait; @@ -156,11 +54,14 @@ fn main() { } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::Resized(_) => { - let _ = web.resize(); + let _ = webview.resize(); } _ => (), }, _ => (), } }); + + // Ok(()) } + diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..c842bf6 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,101 @@ +use ico::IconDir; +use rfd::MessageDialog; +use single_instance::SingleInstance; +use std::{ + env, + io::{Cursor, Error, ErrorKind}, + path::PathBuf, +}; +use wry::{ + application::{ + event_loop::{ EventLoop, }, + window::{Icon, Window, WindowBuilder}, + }, + webview::{RpcRequest, RpcResponse, WebContext, WebView, WebViewBuilder}, +}; + +pub fn check_process_mutex() -> Result { + let inst = SingleInstance::new("yan").unwrap(); + + match inst.is_single() { + true => Ok(inst), + _ => { + MessageDialog::new() + .set_title("警告") + .set_description("该程序已经运行!") + .show(); + Err(Error::new(ErrorKind::AlreadyExists, "进程实例已存在!")) + } + } +} + +pub fn read_icon(icon_binary: &[u8]) -> Icon { + let icon_dir = IconDir::read(Cursor::new(icon_binary)).unwrap(); + let image = icon_dir.entries()[0].decode().unwrap(); + let rgba = image.rgba_data(); + Icon::from_rgba(rgba.to_vec(), image.height(), image.width()).unwrap() +} + +pub fn create_app(icon: Icon) -> (Window, EventLoop<()>) { + let evl = EventLoop::new(); + + let win = WindowBuilder::new() + .with_title("My App") + .with_window_icon(Some(icon)) + .build(&evl) + .unwrap(); + + (win, evl) +} + +pub fn create_webview(win: Window, rpc_handler: F) -> WebView +where + F: Fn(&Window, RpcRequest) -> Option + 'static, +{ + let web = WebViewBuilder::new(win).unwrap(); + + #[cfg(not(debug_assertions))] + let web = { + use include_dir::{include_dir, Dir}; + use wry::http::{status::StatusCode, ResponseBuilder}; + const WEB_DIR: Dir = include_dir!("./web"); + web.with_custom_protocol("wry".into(), move |req| { + // 1. 获取请求的url, + // 如果协议格式为wry://index.html,程序会把index.html 做路径处理 + let path = req.uri().replace("wry://web/", ""); + + // 2. 获取文件内容并设置响应类型 + let (code, data, meta) = match WEB_DIR.get_file(&path) { + Some(file) => match path.split(".").last().unwrap() { + "html" => (200, file.contents(), mime::TEXT_HTML), + "js" => (200, file.contents(), mime::TEXT_JAVASCRIPT), + "css" => (200, file.contents(), mime::TEXT_CSS), + "ico" | "png" => (200, file.contents(), mime::IMAGE_PNG), + "jpg" => (200, file.contents(), mime::IMAGE_JPEG), + "bmp" => (200, file.contents(), mime::IMAGE_BMP), + _ => (404, "not found".as_bytes(), mime::TEXT_PLAIN), + }, + None => (404, "not found".as_bytes(), mime::TEXT_PLAIN), + }; + ResponseBuilder::new() + .status(StatusCode::from_u16(code).unwrap()) + .mimetype(meta.essence_str()) + .body(Vec::from(data)) + }) + .with_url("wry://web/index.html") + .unwrap() + }; + + #[cfg(debug_assertions)] + let web = web.with_url("http://localhost:8341/").unwrap(); + + let mut user_data_dir = PathBuf::from(env::var("appdata").unwrap()); + user_data_dir.push("yan"); + let mut webctx = WebContext::new(Some(user_data_dir)); + let web = web + .with_rpc_handler(rpc_handler) + .with_web_context(&mut webctx) + .build() + .unwrap(); + web +} diff --git "a/\350\270\251\345\235\221\346\227\245\350\256\260.md" "b/\350\270\251\345\235\221\346\227\245\350\256\260.md" new file mode 100644 index 0000000..8fb3338 --- /dev/null +++ "b/\350\270\251\345\235\221\346\227\245\350\256\260.md" @@ -0,0 +1,59 @@ +# 踩坑日记 +记录一些开发过程中遇到的问题与解决方案 + +## 指定Webview 版本 +1. 在[webview2官网](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section)下载`.cab`文件 +2. 解压`.cab`文件到指定文件夹:`expand xyz.cab -f:* D:\code\yan\wv2` +```rust +// 在rust 代码中调用 +env::set_var( + "WEBVIEW2_BROWSER_EXECUTABLE_FOLDER", + "D:\\code\\yan\\wv2", +); +``` + +## 指定user_data_directory +不能通过环境变量指定`user_data_directory`,但是可以通过设置`WebContext`来实现。 +```rust +let mut user_data_dir = PathBuf::from(env::var("appdata").unwrap()); +user_data_dir.push("yan"); +let mut webctx = WebContext::new(Some(user_data_dir)); +let mut web = web + .with_rpc_handler(handler) + .with_web_context(&mut webctx) + .build() + .unwrap(); +``` + +## include_bytes! +`include_bytes!`宏可以直接将文件引用为二进制的形式,但由于是宏命令,所以文件路径的相对起始位置应该是代码文件所在的目录。 +```rust +let by:&[u8,0] = include_bytes!("../res/logo128.ico"); +``` +而`include_dir`宏的起始目录则是项目的根目录。 + +## 以二进制形式读取文件 +通过`std::fs::File`可以以二进制的形式读取文件,但是不够灵活,更好的方案是通过`nom`或者`byteorder`库来实现 +```rust +use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; +use std::io::Read; +use std::{fs::File, mem}; + +// representation 描述、表示 +// packaged 表示紧密排列 +#[repr(C, packed)] +#[derive(Debug)] +struct DosHeader { + magic: [u8; 2], +} +fn main() { + let mut pe = File::open("yan.exe").unwrap(); + // 在堆上初始化结构 + let mut dh = unsafe { mem::zeroed::() }; + pe.read_exact(&mut dh.magic); + println!("{:X?}{:X?}", dh.magic[0] as char, dh.magic[1] as char); + // 注意读取的时候有大小端字节序的区别 + let uint16 = pe.read_u16::().unwrap(); + println!("{}",uint16); +} +```