plugin.rs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use std::{collections::HashMap, sync::Mutex};
  5. use serde::{Deserialize, Serialize};
  6. use tauri_runtime::dpi::Position;
  7. use super::{sealed::ContextMenuBase, *};
  8. use crate::{
  9. command,
  10. image::JsImage,
  11. ipc::{channel::JavaScriptChannelId, Channel},
  12. plugin::{Builder, TauriPlugin},
  13. resources::ResourceId,
  14. sealed::ManagerBase,
  15. Manager, ResourceTable, RunEvent, Runtime, State, Webview, Window,
  16. };
  17. use tauri_macros::do_menu_item;
  18. #[derive(Deserialize, Serialize)]
  19. pub(crate) enum ItemKind {
  20. Menu,
  21. MenuItem,
  22. Predefined,
  23. Submenu,
  24. Check,
  25. Icon,
  26. }
  27. #[derive(Deserialize)]
  28. #[serde(rename_all = "camelCase")]
  29. pub(crate) struct AboutMetadata {
  30. pub name: Option<String>,
  31. pub version: Option<String>,
  32. pub short_version: Option<String>,
  33. pub authors: Option<Vec<String>>,
  34. pub comments: Option<String>,
  35. pub copyright: Option<String>,
  36. pub license: Option<String>,
  37. pub website: Option<String>,
  38. pub website_label: Option<String>,
  39. pub credits: Option<String>,
  40. pub icon: Option<JsImage>,
  41. }
  42. impl AboutMetadata {
  43. pub fn into_metadata(
  44. self,
  45. resources_table: &ResourceTable,
  46. ) -> crate::Result<super::AboutMetadata<'_>> {
  47. let icon = match self.icon {
  48. Some(i) => Some(i.into_img(resources_table)?.as_ref().clone()),
  49. None => None,
  50. };
  51. Ok(super::AboutMetadata {
  52. name: self.name,
  53. version: self.version,
  54. short_version: self.short_version,
  55. authors: self.authors,
  56. comments: self.comments,
  57. copyright: self.copyright,
  58. license: self.license,
  59. website: self.website,
  60. website_label: self.website_label,
  61. credits: self.credits,
  62. icon,
  63. })
  64. }
  65. }
  66. #[allow(clippy::large_enum_variant)]
  67. #[derive(Deserialize)]
  68. enum Predefined {
  69. Separator,
  70. Copy,
  71. Cut,
  72. Paste,
  73. SelectAll,
  74. Undo,
  75. Redo,
  76. Minimize,
  77. Maximize,
  78. Fullscreen,
  79. Hide,
  80. HideOthers,
  81. ShowAll,
  82. CloseWindow,
  83. Quit,
  84. About(Option<AboutMetadata>),
  85. Services,
  86. }
  87. #[derive(Deserialize)]
  88. struct SubmenuPayload {
  89. id: Option<MenuId>,
  90. text: String,
  91. enabled: Option<bool>,
  92. items: Vec<MenuItemPayloadKind>,
  93. }
  94. impl SubmenuPayload {
  95. pub fn create_item<R: Runtime>(
  96. self,
  97. webview: &Webview<R>,
  98. resources_table: &ResourceTable,
  99. ) -> crate::Result<Submenu<R>> {
  100. let mut builder = if let Some(id) = self.id {
  101. SubmenuBuilder::with_id(webview, id, self.text)
  102. } else {
  103. SubmenuBuilder::new(webview, self.text)
  104. };
  105. if let Some(enabled) = self.enabled {
  106. builder = builder.enabled(enabled);
  107. }
  108. for item in self.items {
  109. builder = item.with_item(webview, resources_table, |i| Ok(builder.item(i)))?;
  110. }
  111. builder.build()
  112. }
  113. }
  114. #[derive(Deserialize)]
  115. struct CheckMenuItemPayload {
  116. handler: Option<JavaScriptChannelId>,
  117. id: Option<MenuId>,
  118. text: String,
  119. checked: bool,
  120. enabled: Option<bool>,
  121. accelerator: Option<String>,
  122. }
  123. impl CheckMenuItemPayload {
  124. pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> crate::Result<CheckMenuItem<R>> {
  125. let mut builder = if let Some(id) = self.id {
  126. CheckMenuItemBuilder::with_id(id, self.text)
  127. } else {
  128. CheckMenuItemBuilder::new(self.text)
  129. };
  130. if let Some(accelerator) = self.accelerator {
  131. builder = builder.accelerator(accelerator);
  132. }
  133. if let Some(enabled) = self.enabled {
  134. builder = builder.enabled(enabled);
  135. }
  136. let item = builder.checked(self.checked).build(webview)?;
  137. if let Some(handler) = self.handler {
  138. let handler = handler.channel_on(webview.clone());
  139. webview
  140. .state::<MenuChannels>()
  141. .0
  142. .lock()
  143. .unwrap()
  144. .insert(item.id().clone(), handler);
  145. }
  146. Ok(item)
  147. }
  148. }
  149. #[derive(Deserialize)]
  150. #[serde(untagged)]
  151. enum Icon {
  152. Native(NativeIcon),
  153. Icon(JsImage),
  154. }
  155. #[derive(Deserialize)]
  156. #[serde(rename_all = "camelCase")]
  157. struct IconMenuItemPayload {
  158. handler: Option<JavaScriptChannelId>,
  159. id: Option<MenuId>,
  160. text: String,
  161. icon: Icon,
  162. enabled: Option<bool>,
  163. accelerator: Option<String>,
  164. }
  165. impl IconMenuItemPayload {
  166. pub fn create_item<R: Runtime>(
  167. self,
  168. webview: &Webview<R>,
  169. resources_table: &ResourceTable,
  170. ) -> crate::Result<IconMenuItem<R>> {
  171. let mut builder = if let Some(id) = self.id {
  172. IconMenuItemBuilder::with_id(id, self.text)
  173. } else {
  174. IconMenuItemBuilder::new(self.text)
  175. };
  176. if let Some(accelerator) = self.accelerator {
  177. builder = builder.accelerator(accelerator);
  178. }
  179. if let Some(enabled) = self.enabled {
  180. builder = builder.enabled(enabled);
  181. }
  182. builder = match self.icon {
  183. Icon::Native(native_icon) => builder.native_icon(native_icon),
  184. Icon::Icon(icon) => builder.icon(icon.into_img(resources_table)?.as_ref().clone()),
  185. };
  186. let item = builder.build(webview)?;
  187. if let Some(handler) = self.handler {
  188. let handler = handler.channel_on(webview.clone());
  189. webview
  190. .state::<MenuChannels>()
  191. .0
  192. .lock()
  193. .unwrap()
  194. .insert(item.id().clone(), handler);
  195. }
  196. Ok(item)
  197. }
  198. }
  199. #[derive(Deserialize)]
  200. struct MenuItemPayload {
  201. handler: Option<JavaScriptChannelId>,
  202. id: Option<MenuId>,
  203. text: String,
  204. enabled: Option<bool>,
  205. accelerator: Option<String>,
  206. }
  207. impl MenuItemPayload {
  208. pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> crate::Result<MenuItem<R>> {
  209. let mut builder = if let Some(id) = self.id {
  210. MenuItemBuilder::with_id(id, self.text)
  211. } else {
  212. MenuItemBuilder::new(self.text)
  213. };
  214. if let Some(accelerator) = self.accelerator {
  215. builder = builder.accelerator(accelerator);
  216. }
  217. if let Some(enabled) = self.enabled {
  218. builder = builder.enabled(enabled);
  219. }
  220. let item = builder.build(webview)?;
  221. if let Some(handler) = self.handler {
  222. let handler = handler.channel_on(webview.clone());
  223. webview
  224. .state::<MenuChannels>()
  225. .0
  226. .lock()
  227. .unwrap()
  228. .insert(item.id().clone(), handler);
  229. }
  230. Ok(item)
  231. }
  232. }
  233. #[derive(Deserialize)]
  234. struct PredefinedMenuItemPayload {
  235. item: Predefined,
  236. text: Option<String>,
  237. }
  238. impl PredefinedMenuItemPayload {
  239. pub fn create_item<R: Runtime>(
  240. self,
  241. webview: &Webview<R>,
  242. resources_table: &ResourceTable,
  243. ) -> crate::Result<PredefinedMenuItem<R>> {
  244. match self.item {
  245. Predefined::Separator => PredefinedMenuItem::separator(webview),
  246. Predefined::Copy => PredefinedMenuItem::copy(webview, self.text.as_deref()),
  247. Predefined::Cut => PredefinedMenuItem::cut(webview, self.text.as_deref()),
  248. Predefined::Paste => PredefinedMenuItem::paste(webview, self.text.as_deref()),
  249. Predefined::SelectAll => PredefinedMenuItem::select_all(webview, self.text.as_deref()),
  250. Predefined::Undo => PredefinedMenuItem::undo(webview, self.text.as_deref()),
  251. Predefined::Redo => PredefinedMenuItem::redo(webview, self.text.as_deref()),
  252. Predefined::Minimize => PredefinedMenuItem::minimize(webview, self.text.as_deref()),
  253. Predefined::Maximize => PredefinedMenuItem::maximize(webview, self.text.as_deref()),
  254. Predefined::Fullscreen => PredefinedMenuItem::fullscreen(webview, self.text.as_deref()),
  255. Predefined::Hide => PredefinedMenuItem::hide(webview, self.text.as_deref()),
  256. Predefined::HideOthers => PredefinedMenuItem::hide_others(webview, self.text.as_deref()),
  257. Predefined::ShowAll => PredefinedMenuItem::show_all(webview, self.text.as_deref()),
  258. Predefined::CloseWindow => PredefinedMenuItem::close_window(webview, self.text.as_deref()),
  259. Predefined::Quit => PredefinedMenuItem::quit(webview, self.text.as_deref()),
  260. Predefined::About(metadata) => {
  261. let metadata = match metadata {
  262. Some(m) => Some(m.into_metadata(resources_table)?),
  263. None => None,
  264. };
  265. PredefinedMenuItem::about(webview, self.text.as_deref(), metadata)
  266. }
  267. Predefined::Services => PredefinedMenuItem::services(webview, self.text.as_deref()),
  268. }
  269. }
  270. }
  271. #[derive(Deserialize)]
  272. #[serde(untagged)]
  273. // Note, order matters for untagged enum deserialization
  274. enum MenuItemPayloadKind {
  275. ExistingItem((ResourceId, ItemKind)),
  276. Predefined(PredefinedMenuItemPayload),
  277. Check(CheckMenuItemPayload),
  278. Icon(IconMenuItemPayload),
  279. Submenu(SubmenuPayload),
  280. MenuItem(MenuItemPayload),
  281. }
  282. impl MenuItemPayloadKind {
  283. pub fn with_item<T, R: Runtime, F: FnOnce(&dyn IsMenuItem<R>) -> crate::Result<T>>(
  284. self,
  285. webview: &Webview<R>,
  286. resources_table: &ResourceTable,
  287. f: F,
  288. ) -> crate::Result<T> {
  289. match self {
  290. Self::ExistingItem((rid, kind)) => {
  291. do_menu_item!(resources_table, rid, kind, |i| f(&*i))
  292. }
  293. Self::Submenu(i) => f(&i.create_item(webview, resources_table)?),
  294. Self::Predefined(i) => f(&i.create_item(webview, resources_table)?),
  295. Self::Check(i) => f(&i.create_item(webview)?),
  296. Self::Icon(i) => f(&i.create_item(webview, resources_table)?),
  297. Self::MenuItem(i) => f(&i.create_item(webview)?),
  298. }
  299. }
  300. }
  301. #[derive(Deserialize, Default)]
  302. #[serde(rename_all = "camelCase")]
  303. struct NewOptions {
  304. id: Option<MenuId>,
  305. text: Option<String>,
  306. enabled: Option<bool>,
  307. checked: Option<bool>,
  308. accelerator: Option<String>,
  309. #[serde(rename = "item")]
  310. predefined_item: Option<Predefined>,
  311. icon: Option<Icon>,
  312. items: Option<Vec<MenuItemPayloadKind>>,
  313. }
  314. #[command(root = "crate")]
  315. fn new<R: Runtime>(
  316. app: Webview<R>,
  317. webview: Webview<R>,
  318. kind: ItemKind,
  319. options: Option<NewOptions>,
  320. channels: State<'_, MenuChannels>,
  321. handler: Channel<MenuId>,
  322. ) -> crate::Result<(ResourceId, MenuId)> {
  323. let options = options.unwrap_or_default();
  324. let mut resources_table = app.resources_table();
  325. let (rid, id) = match kind {
  326. ItemKind::Menu => {
  327. let mut builder = MenuBuilder::new(&app);
  328. if let Some(id) = options.id {
  329. builder = builder.id(id);
  330. }
  331. if let Some(items) = options.items {
  332. for item in items {
  333. builder = item.with_item(&webview, &resources_table, |i| Ok(builder.item(i)))?;
  334. }
  335. }
  336. let menu = builder.build()?;
  337. let id = menu.id().clone();
  338. let rid = resources_table.add(menu);
  339. (rid, id)
  340. }
  341. ItemKind::Submenu => {
  342. let submenu = SubmenuPayload {
  343. id: options.id,
  344. text: options.text.unwrap_or_default(),
  345. enabled: options.enabled,
  346. items: options.items.unwrap_or_default(),
  347. }
  348. .create_item(&webview, &resources_table)?;
  349. let id = submenu.id().clone();
  350. let rid = resources_table.add(submenu);
  351. (rid, id)
  352. }
  353. ItemKind::MenuItem => {
  354. let item = MenuItemPayload {
  355. // handler managed in this function instead
  356. handler: None,
  357. id: options.id,
  358. text: options.text.unwrap_or_default(),
  359. enabled: options.enabled,
  360. accelerator: options.accelerator,
  361. }
  362. .create_item(&webview)?;
  363. let id = item.id().clone();
  364. let rid = resources_table.add(item);
  365. (rid, id)
  366. }
  367. ItemKind::Predefined => {
  368. let item = PredefinedMenuItemPayload {
  369. item: options.predefined_item.unwrap(),
  370. text: options.text,
  371. }
  372. .create_item(&webview, &resources_table)?;
  373. let id = item.id().clone();
  374. let rid = resources_table.add(item);
  375. (rid, id)
  376. }
  377. ItemKind::Check => {
  378. let item = CheckMenuItemPayload {
  379. // handler managed in this function instead
  380. handler: None,
  381. id: options.id,
  382. text: options.text.unwrap_or_default(),
  383. checked: options.checked.unwrap_or_default(),
  384. enabled: options.enabled,
  385. accelerator: options.accelerator,
  386. }
  387. .create_item(&webview)?;
  388. let id = item.id().clone();
  389. let rid = resources_table.add(item);
  390. (rid, id)
  391. }
  392. ItemKind::Icon => {
  393. let item = IconMenuItemPayload {
  394. // handler managed in this function instead
  395. handler: None,
  396. id: options.id,
  397. text: options.text.unwrap_or_default(),
  398. icon: options.icon.unwrap_or(Icon::Native(NativeIcon::User)),
  399. enabled: options.enabled,
  400. accelerator: options.accelerator,
  401. }
  402. .create_item(&webview, &resources_table)?;
  403. let id = item.id().clone();
  404. let rid = resources_table.add(item);
  405. (rid, id)
  406. }
  407. };
  408. channels.0.lock().unwrap().insert(id.clone(), handler);
  409. Ok((rid, id))
  410. }
  411. #[command(root = "crate")]
  412. fn append<R: Runtime>(
  413. webview: Webview<R>,
  414. rid: ResourceId,
  415. kind: ItemKind,
  416. items: Vec<MenuItemPayloadKind>,
  417. ) -> crate::Result<()> {
  418. let resources_table = webview.resources_table();
  419. match kind {
  420. ItemKind::Menu => {
  421. let menu = resources_table.get::<Menu<R>>(rid)?;
  422. for item in items {
  423. item.with_item(&webview, &resources_table, |i| menu.append(i))?;
  424. }
  425. }
  426. ItemKind::Submenu => {
  427. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  428. for item in items {
  429. item.with_item(&webview, &resources_table, |i| submenu.append(i))?;
  430. }
  431. }
  432. _ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
  433. };
  434. Ok(())
  435. }
  436. #[command(root = "crate")]
  437. fn prepend<R: Runtime>(
  438. webview: Webview<R>,
  439. rid: ResourceId,
  440. kind: ItemKind,
  441. items: Vec<MenuItemPayloadKind>,
  442. ) -> crate::Result<()> {
  443. let resources_table = webview.resources_table();
  444. match kind {
  445. ItemKind::Menu => {
  446. let menu = resources_table.get::<Menu<R>>(rid)?;
  447. for item in items {
  448. item.with_item(&webview, &resources_table, |i| menu.prepend(i))?;
  449. }
  450. }
  451. ItemKind::Submenu => {
  452. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  453. for item in items {
  454. item.with_item(&webview, &resources_table, |i| submenu.prepend(i))?;
  455. }
  456. }
  457. _ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
  458. };
  459. Ok(())
  460. }
  461. #[command(root = "crate")]
  462. fn insert<R: Runtime>(
  463. webview: Webview<R>,
  464. rid: ResourceId,
  465. kind: ItemKind,
  466. items: Vec<MenuItemPayloadKind>,
  467. mut position: usize,
  468. ) -> crate::Result<()> {
  469. let resources_table = webview.resources_table();
  470. match kind {
  471. ItemKind::Menu => {
  472. let menu = resources_table.get::<Menu<R>>(rid)?;
  473. for item in items {
  474. item.with_item(&webview, &resources_table, |i| menu.insert(i, position))?;
  475. position += 1
  476. }
  477. }
  478. ItemKind::Submenu => {
  479. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  480. for item in items {
  481. item.with_item(&webview, &resources_table, |i| submenu.insert(i, position))?;
  482. position += 1
  483. }
  484. }
  485. _ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
  486. };
  487. Ok(())
  488. }
  489. #[command(root = "crate")]
  490. fn remove<R: Runtime>(
  491. webview: Webview<R>,
  492. rid: ResourceId,
  493. kind: ItemKind,
  494. item: (ResourceId, ItemKind),
  495. ) -> crate::Result<()> {
  496. let resources_table = webview.resources_table();
  497. let (item_rid, item_kind) = item;
  498. match kind {
  499. ItemKind::Menu => {
  500. let menu = resources_table.get::<Menu<R>>(rid)?;
  501. do_menu_item!(resources_table, item_rid, item_kind, |i| menu.remove(&*i))?;
  502. }
  503. ItemKind::Submenu => {
  504. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  505. do_menu_item!(resources_table, item_rid, item_kind, |i| submenu
  506. .remove(&*i))?;
  507. }
  508. _ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
  509. };
  510. Ok(())
  511. }
  512. macro_rules! make_item_resource {
  513. ($resources_table:ident, $item:ident) => {{
  514. let id = $item.id().clone();
  515. let (rid, kind) = match $item {
  516. MenuItemKind::MenuItem(i) => ($resources_table.add(i), ItemKind::MenuItem),
  517. MenuItemKind::Submenu(i) => ($resources_table.add(i), ItemKind::Submenu),
  518. MenuItemKind::Predefined(i) => ($resources_table.add(i), ItemKind::Predefined),
  519. MenuItemKind::Check(i) => ($resources_table.add(i), ItemKind::Check),
  520. MenuItemKind::Icon(i) => ($resources_table.add(i), ItemKind::Icon),
  521. };
  522. (rid, id, kind)
  523. }};
  524. }
  525. #[command(root = "crate")]
  526. fn remove_at<R: Runtime>(
  527. webview: Webview<R>,
  528. rid: ResourceId,
  529. kind: ItemKind,
  530. position: usize,
  531. ) -> crate::Result<Option<(ResourceId, MenuId, ItemKind)>> {
  532. let mut resources_table = webview.resources_table();
  533. match kind {
  534. ItemKind::Menu => {
  535. let menu = resources_table.get::<Menu<R>>(rid)?;
  536. if let Some(item) = menu.remove_at(position)? {
  537. return Ok(Some(make_item_resource!(resources_table, item)));
  538. }
  539. }
  540. ItemKind::Submenu => {
  541. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  542. if let Some(item) = submenu.remove_at(position)? {
  543. return Ok(Some(make_item_resource!(resources_table, item)));
  544. }
  545. }
  546. _ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
  547. };
  548. Ok(None)
  549. }
  550. #[command(root = "crate")]
  551. fn items<R: Runtime>(
  552. webview: Webview<R>,
  553. rid: ResourceId,
  554. kind: ItemKind,
  555. ) -> crate::Result<Vec<(ResourceId, MenuId, ItemKind)>> {
  556. let mut resources_table = webview.resources_table();
  557. let items = match kind {
  558. ItemKind::Menu => resources_table.get::<Menu<R>>(rid)?.items()?,
  559. ItemKind::Submenu => resources_table.get::<Submenu<R>>(rid)?.items()?,
  560. _ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
  561. };
  562. Ok(
  563. items
  564. .into_iter()
  565. .map(|i| make_item_resource!(resources_table, i))
  566. .collect::<Vec<_>>(),
  567. )
  568. }
  569. #[command(root = "crate")]
  570. fn get<R: Runtime>(
  571. webview: Webview<R>,
  572. rid: ResourceId,
  573. kind: ItemKind,
  574. id: MenuId,
  575. ) -> crate::Result<Option<(ResourceId, MenuId, ItemKind)>> {
  576. let mut resources_table = webview.resources_table();
  577. match kind {
  578. ItemKind::Menu => {
  579. let menu = resources_table.get::<Menu<R>>(rid)?;
  580. if let Some(item) = menu.get(&id) {
  581. return Ok(Some(make_item_resource!(resources_table, item)));
  582. }
  583. }
  584. ItemKind::Submenu => {
  585. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  586. if let Some(item) = submenu.get(&id) {
  587. return Ok(Some(make_item_resource!(resources_table, item)));
  588. }
  589. }
  590. _ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
  591. };
  592. Ok(None)
  593. }
  594. #[command(root = "crate")]
  595. async fn popup<R: Runtime>(
  596. webview: Webview<R>,
  597. current_window: Window<R>,
  598. rid: ResourceId,
  599. kind: ItemKind,
  600. window: Option<String>,
  601. at: Option<Position>,
  602. ) -> crate::Result<()> {
  603. let window = window
  604. .map(|w| webview.manager().get_window(&w))
  605. .unwrap_or(Some(current_window));
  606. if let Some(window) = window {
  607. let resources_table = webview.resources_table();
  608. match kind {
  609. ItemKind::Menu => {
  610. let menu = resources_table.get::<Menu<R>>(rid)?;
  611. menu.popup_inner(window, at)?;
  612. }
  613. ItemKind::Submenu => {
  614. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  615. submenu.popup_inner(window, at)?;
  616. }
  617. _ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
  618. };
  619. }
  620. Ok(())
  621. }
  622. #[command(root = "crate")]
  623. fn create_default<R: Runtime>(
  624. app: AppHandle<R>,
  625. webview: Webview<R>,
  626. ) -> crate::Result<(ResourceId, MenuId)> {
  627. let mut resources_table = webview.resources_table();
  628. let menu = Menu::default(&app)?;
  629. let id = menu.id().clone();
  630. let rid = resources_table.add(menu);
  631. Ok((rid, id))
  632. }
  633. #[command(root = "crate")]
  634. async fn set_as_app_menu<R: Runtime>(
  635. webview: Webview<R>,
  636. rid: ResourceId,
  637. ) -> crate::Result<Option<(ResourceId, MenuId)>> {
  638. let mut resources_table = webview.resources_table();
  639. let menu = resources_table.get::<Menu<R>>(rid)?;
  640. if let Some(menu) = menu.set_as_app_menu()? {
  641. let id = menu.id().clone();
  642. let rid = resources_table.add(menu);
  643. return Ok(Some((rid, id)));
  644. }
  645. Ok(None)
  646. }
  647. #[command(root = "crate")]
  648. async fn set_as_window_menu<R: Runtime>(
  649. webview: Webview<R>,
  650. current_window: Window<R>,
  651. rid: ResourceId,
  652. window: Option<String>,
  653. ) -> crate::Result<Option<(ResourceId, MenuId)>> {
  654. let window = window
  655. .map(|w| webview.manager().get_window(&w))
  656. .unwrap_or(Some(current_window));
  657. if let Some(window) = window {
  658. let mut resources_table = webview.resources_table();
  659. let menu = resources_table.get::<Menu<R>>(rid)?;
  660. if let Some(menu) = menu.set_as_window_menu(&window)? {
  661. let id = menu.id().clone();
  662. let rid = resources_table.add(menu);
  663. return Ok(Some((rid, id)));
  664. }
  665. }
  666. Ok(None)
  667. }
  668. #[command(root = "crate")]
  669. fn text<R: Runtime>(webview: Webview<R>, rid: ResourceId, kind: ItemKind) -> crate::Result<String> {
  670. let resources_table = webview.resources_table();
  671. do_menu_item!(resources_table, rid, kind, |i| i.text())
  672. }
  673. #[command(root = "crate")]
  674. fn set_text<R: Runtime>(
  675. webview: Webview<R>,
  676. rid: ResourceId,
  677. kind: ItemKind,
  678. text: String,
  679. ) -> crate::Result<()> {
  680. let resources_table = webview.resources_table();
  681. do_menu_item!(resources_table, rid, kind, |i| i.set_text(text))
  682. }
  683. #[command(root = "crate")]
  684. fn is_enabled<R: Runtime>(
  685. webview: Webview<R>,
  686. rid: ResourceId,
  687. kind: ItemKind,
  688. ) -> crate::Result<bool> {
  689. let resources_table = webview.resources_table();
  690. do_menu_item!(resources_table, rid, kind, |i| i.is_enabled(), !Predefined)
  691. }
  692. #[command(root = "crate")]
  693. fn set_enabled<R: Runtime>(
  694. webview: Webview<R>,
  695. rid: ResourceId,
  696. kind: ItemKind,
  697. enabled: bool,
  698. ) -> crate::Result<()> {
  699. let resources_table = webview.resources_table();
  700. do_menu_item!(
  701. resources_table,
  702. rid,
  703. kind,
  704. |i| i.set_enabled(enabled),
  705. !Predefined
  706. )
  707. }
  708. #[command(root = "crate")]
  709. fn set_accelerator<R: Runtime>(
  710. webview: Webview<R>,
  711. rid: ResourceId,
  712. kind: ItemKind,
  713. accelerator: Option<String>,
  714. ) -> crate::Result<()> {
  715. let resources_table = webview.resources_table();
  716. do_menu_item!(
  717. resources_table,
  718. rid,
  719. kind,
  720. |i| i.set_accelerator(accelerator),
  721. !Predefined | !Submenu
  722. )
  723. }
  724. #[command(root = "crate")]
  725. fn set_as_windows_menu_for_nsapp<R: Runtime>(
  726. webview: Webview<R>,
  727. rid: ResourceId,
  728. ) -> crate::Result<()> {
  729. #[cfg(target_os = "macos")]
  730. {
  731. let resources_table = webview.resources_table();
  732. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  733. submenu.set_as_help_menu_for_nsapp()?;
  734. }
  735. let _ = rid;
  736. let _ = webview;
  737. Ok(())
  738. }
  739. #[command(root = "crate")]
  740. fn set_as_help_menu_for_nsapp<R: Runtime>(
  741. webview: Webview<R>,
  742. rid: ResourceId,
  743. ) -> crate::Result<()> {
  744. #[cfg(target_os = "macos")]
  745. {
  746. let resources_table = webview.resources_table();
  747. let submenu = resources_table.get::<Submenu<R>>(rid)?;
  748. submenu.set_as_help_menu_for_nsapp()?;
  749. }
  750. let _ = rid;
  751. let _ = webview;
  752. Ok(())
  753. }
  754. #[command(root = "crate")]
  755. fn is_checked<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> crate::Result<bool> {
  756. let resources_table = webview.resources_table();
  757. let check_item = resources_table.get::<CheckMenuItem<R>>(rid)?;
  758. check_item.is_checked()
  759. }
  760. #[command(root = "crate")]
  761. fn set_checked<R: Runtime>(
  762. webview: Webview<R>,
  763. rid: ResourceId,
  764. checked: bool,
  765. ) -> crate::Result<()> {
  766. let resources_table = webview.resources_table();
  767. let check_item = resources_table.get::<CheckMenuItem<R>>(rid)?;
  768. check_item.set_checked(checked)
  769. }
  770. #[command(root = "crate")]
  771. fn set_icon<R: Runtime>(
  772. webview: Webview<R>,
  773. rid: ResourceId,
  774. icon: Option<Icon>,
  775. ) -> crate::Result<()> {
  776. let resources_table = webview.resources_table();
  777. let icon_item = resources_table.get::<IconMenuItem<R>>(rid)?;
  778. match icon {
  779. Some(Icon::Native(icon)) => icon_item.set_native_icon(Some(icon)),
  780. Some(Icon::Icon(icon)) => {
  781. icon_item.set_icon(Some(icon.into_img(&resources_table)?.as_ref().clone()))
  782. }
  783. None => {
  784. icon_item.set_icon(None)?;
  785. icon_item.set_native_icon(None)?;
  786. Ok(())
  787. }
  788. }
  789. }
  790. struct MenuChannels(Mutex<HashMap<MenuId, Channel<MenuId>>>);
  791. pub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {
  792. Builder::new("menu")
  793. .setup(|app, _api| {
  794. app.manage(MenuChannels(Mutex::default()));
  795. Ok(())
  796. })
  797. .on_event(|app, e| {
  798. if let RunEvent::MenuEvent(e) = e {
  799. if let Some(channel) = app.state::<MenuChannels>().0.lock().unwrap().get(&e.id) {
  800. let _ = channel.send(e.id.clone());
  801. }
  802. }
  803. })
  804. .invoke_handler(crate::generate_handler![
  805. new,
  806. append,
  807. prepend,
  808. insert,
  809. remove,
  810. remove_at,
  811. items,
  812. get,
  813. popup,
  814. create_default,
  815. set_as_app_menu,
  816. set_as_window_menu,
  817. text,
  818. set_text,
  819. is_enabled,
  820. set_enabled,
  821. set_accelerator,
  822. set_as_windows_menu_for_nsapp,
  823. set_as_help_menu_for_nsapp,
  824. is_checked,
  825. set_checked,
  826. set_icon,
  827. ])
  828. .build()
  829. }