声明
本文仅用以记录个人针对该问题的猜测及处理办法,欢迎交流探讨,但若由于采用或借鉴此方法而导致的任何问题,本人不承担任何责任。
目录
问题描述
最近用rust实现rpc服务,程序运行后是在控制台的,不需要界面,所以感觉有点杂乱,就想着放到托盘去吧
解决思路
添加依赖:
执行 cargo add tray-icon tao anyhow
或直接在Cargo.toml中添加进去
[dependencies]
tray-icon = "0.13.5"
tao = "0.28.0"
anyhow = "1.0.83"
在main.rs中添加引用
use tao::event_loop::{EventLoopBuilder, ControlFlow};
use tray_icon::{menu::{Menu, MenuItem, MenuEvent}, TrayIconBuilder, Icon, TrayIconEvent};
use anyhow::Result;
然后开始托盘部分代码
let event_loop = EventLoopBuilder::new().build();
let menu = Menu::new();
// menu.append(&MenuItem::new("打开", true, None))?;
menu.append(&MenuItem::new("退出", true, None))?;
let icon = Icon::from_resource_name("logo.ico", None).unwrap_or(
Icon::from_resource(111, None).unwrap_or(
Icon::from_rgba(vec![0,0,0,0], 1, 1).unwrap()
)
);
let _tray_icon = Some(
TrayIconBuilder::new()
.with_menu(Box::new(menu))
.with_tooltip("RPC服务")
.with_icon(icon)
.build().unwrap(),
);
let menu_channel = MenuEvent::receiver();
let _tray_channel = TrayIconEvent::receiver();
let event_loop_proxy = event_loop.create_proxy();
// std::thread::spawn(move || {
// loop {
// event_loop_proxy.send_event(()).ok();
// std::thread::sleep(time::Duration::from_millis(50));
// }
// });
tokio::spawn(async move{
loop{
event_loop_proxy.send_event(()).ok();
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
}
});
event_loop.run(move |_event, _, control_flow| {
*control_flow = ControlFlow::Wait;
if let Ok(MenuEvent { id:_ }) = menu_channel.try_recv() {
// if id.0 == "1001"{
// //点击“打开”菜单,启动主窗口进程
// // open_app();
// }else
{
//退出托盘程序
*control_flow = ControlFlow::Exit;
}
}
// if let Ok(TrayIconEvent {click_type, id: _, x: _, y: _, icon_rect: _ }) = tray_channel.try_recv(){
// if let ClickType::Left = click_type{
// //打开主窗口进程
// open_app();
// }
// }
});
参考:tray-icon,Slint多窗口 + Tray Icon + 窗口间通信
这里我直接借鉴了Slint多窗口 + Tray Icon + 窗口间通信的代码放这儿,只需要退出,所以把其他的屏蔽了
上面代码中ICON部分遇到点问题,ICON引用图标几个方法如下:
// 从图像数组获取
fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> ;
// 从路径获取
fn from_path<P: AsRef<std::path::Path>>(path: P,size: Option<(u32, u32)>,) ->Result<Self, BadIcon> ;
// 从资源文件获取,这里有两个方法
fn from_resource(ordinal: u16, size: Option<(u32, u32)>) -> Result<Self, BadIcon>
//和
fn from_resource_name(resource_name: &str, size: Option<(u32, u32)>) -> Result<Self, BadIcon>
// 从win下api句柄获取
fn from_handle(handle: isize)
最初用from_path,但是发现不是嵌入程序的,也就是图标文件需要放在执行文件目录下,万一被删掉,就异常或者失败用from_rgba生成一个黑子…………
所以考虑嵌入exe的方法,用from_resource,但不熟悉rc文件,这里icon引用一直失败,后面查找下才解决
说说rc资源文件,可以参考这里Windows 资源文件(.rc文件),说的很细
我这里最初没搞明白,直接
name ICON "logo.ico"
生成exe确实也有图标,但托盘那里拿不到,用from_resource_name获取logo.ico一直失败,没弄明白,后面改如下
#define LOGOICON 111
LOGOICON ICON "logo.ico"
然后用from_resource方法读取ordinal_id,就是#define的111,这里随意资源多的话要保证唯一性(我是这么理解的)
再说说嵌入图标,这里参考给rust写的windows程序加个图标,但这里的库版本早,跟新版本有出入,我这里贴下
执行
cargo add embed-resource
这里要将cargo.toml中embed-resource 部分移入
[build-dependencies]
embed-resource = "2.4.2"
或直接在build-dependencies中加上
在build.rs加入
fn main() -> Result<(), Box<dyn std::error::Error>> {
embed_resource::compile("./logo.rc",embed_resource::NONE);
tonic_build::configure()
// .out_dir("src/")
.build_server(true)
.compile(
&["hello.proto"],
&["protos"],
)?;
Ok(())
}
上面我用到tonic做rpc,有return result,所以这里embed_resource要放在前面
到这里似乎已经完成了,编译运行,ok,托盘有了,退出正常,但我的rpc不响应……
检查下,上面有个event_loop,这里给阻塞了,所以得给rpc那里也在前面加个线程出来;
由于tonic依赖tokio,所以我将上面托盘那里(屏蔽掉的std::thread::spawn)也改用tokio,至此完美实现托盘
这里插几句
1. 托盘那里的icon可以不用,程序就变成幽灵了,托盘有个空的……哈哈
2. 上面去掉控制台,在main.rs文件开头加入
#![windows_subsystem = "windows"]
这里呢,如果把托盘部分去掉,程序会运行在后台,无界面,任务栏也没有,要关闭得去任务管理器
3. 上面icon加载部分,可以用from_rgba生成自定义图标在托盘显示,上面我是测试,出错的话,托盘图标是黑色方块的
4. rust刚学,不熟,勿喷,上面有点乱,主要是做个记录,看的看个热闹,如果你有收获,顺手帮点个赞