瀏覽代碼

feat(core): add window event system (#1241)

Lucas Fernandes Nogueira 4 年之前
父節點
當前提交
ddcb70c8fe

+ 4 - 45
api/src/event.ts

@@ -1,49 +1,8 @@
-import { invoke, transformCallback } from './tauri'
+import { emit as emitEvent } from './helpers/event'
 
-export interface Event<T> {
-  type: string
-  payload: T
-}
-
-export type EventCallback<T> = (event: Event<T>) => void
-
-/**
- * listen to an event from the backend
- *
- * @param event the event name
- * @param handler the event handler callback
- */
-async function listen<T>(
-  event: string,
-  handler: EventCallback<T>,
-  once = false
-): Promise<void> {
-  await await invoke({
-    __tauriModule: 'Event',
-    message: {
-      cmd: 'listen',
-      event,
-      handler: transformCallback(handler, once),
-      once
-    }
-  })
-}
-
-/**
- * emits an event to the backend
- *
- * @param event the event name
- * @param [payload] the event payload
- */
 async function emit(event: string, payload?: string): Promise<void> {
-  await invoke({
-    __tauriModule: 'Event',
-    message: {
-      cmd: 'emit',
-      event,
-      payload
-    }
-  })
+  return await emitEvent(event, undefined, payload)
 }
 
-export { listen, emit }
+export { listen } from './helpers/event'
+export { emit }

+ 54 - 0
api/src/helpers/event.ts

@@ -0,0 +1,54 @@
+import { invoke, transformCallback } from '../tauri'
+
+export interface Event<T> {
+  type: string
+  payload: T
+}
+
+export type EventCallback<T> = (event: Event<T>) => void
+
+/**
+ * listen to an event from the backend
+ *
+ * @param event the event name
+ * @param handler the event handler callback
+ */
+async function listen<T>(
+  event: string,
+  handler: EventCallback<T>,
+  once = false
+): Promise<void> {
+  await invoke({
+    __tauriModule: 'Event',
+    message: {
+      cmd: 'listen',
+      event,
+      handler: transformCallback(handler, once),
+      once
+    }
+  })
+}
+
+/**
+ * emits an event to the backend
+ *
+ * @param event the event name
+ * @param [payload] the event payload
+ */
+async function emit(
+  event: string,
+  windowLabel?: string,
+  payload?: string
+): Promise<void> {
+  await invoke({
+    __tauriModule: 'Event',
+    message: {
+      cmd: 'emit',
+      event,
+      windowLabel,
+      payload
+    }
+  })
+}
+
+export { listen, emit }

+ 6 - 6
api/src/http.ts

@@ -191,10 +191,7 @@ export class Client {
    *
    * @return promise resolving to the response
    */
-  async delete<T>(
-    url: string,
-    options: RequestOptions
-  ): Promise<Response<T>> {
+  async delete<T>(url: string, options: RequestOptions): Promise<Response<T>> {
     return await this.request({
       method: 'DELETE',
       url,
@@ -210,12 +207,15 @@ async function getClient(options?: ClientOptions): Promise<Client> {
       cmd: 'createClient',
       options
     }
-  }).then(id => new Client(id))
+  }).then((id) => new Client(id))
 }
 
 let defaultClient: Client | null = null
 
-async function fetch<T>(url: string, options?: FetchOptions): Promise<Response<T>> {
+async function fetch<T>(
+  url: string,
+  options?: FetchOptions
+): Promise<Response<T>> {
   if (defaultClient === null) {
     defaultClient = await getClient()
   }

+ 7 - 5
api/src/tauri.ts

@@ -67,11 +67,13 @@ async function invoke<T>(args: any): Promise<T> {
       Reflect.deleteProperty(window, callback)
     }, true)
 
-    window.__TAURI_INVOKE_HANDLER__(JSON.stringify({
-      callback,
-      error,
-      ...args
-    }))
+    window.__TAURI_INVOKE_HANDLER__(
+      JSON.stringify({
+        callback,
+        error,
+        ...args
+      })
+    )
   })
 }
 

+ 341 - 295
api/src/window.ts

@@ -1,328 +1,374 @@
 import { invoke } from './tauri'
+import { EventCallback, emit, listen } from './helpers/event'
 
-/**
- * Updates the window resizable flag.
- */
-async function setResizable(resizable: boolean): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'setResizable',
-      resizable
-    }
-  })
+interface WindowDef {
+  label: string
 }
 
-/**
- * sets the window title
- *
- * @param title the new title
- */
-async function setTitle(title: string): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'setTitle',
-      title
-    }
-  })
+declare global {
+  interface Window {
+    __TAURI__: {
+      __windows: WindowDef[],
+      __currentWindow: WindowDef
+    };
+  }
 }
 
-/**
- * Maximizes the window.
- */
-async function maximize(): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'maximize'
-    }
-  })
+function getCurrentWindow(): WindowDef {
+  return window.__TAURI__.__currentWindow
 }
 
-/**
- * Unmaximizes the window.
- */
-async function unmaximize(): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'unmaximize'
-    }
-  })
+function getWindows(): WindowDef[] {
+  return window.__TAURI__.__windows
 }
 
-/**
- * Minimizes the window.
- */
-async function minimize(): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'minimize'
-    }
-  })
-}
+class TauriWindow {
+  label: string
+  constructor(label: string) {
+    this.label = label
+  }
 
-/**
- * Unminimizes the window.
- */
-async function unminimize(): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'unminimize'
-    }
-  })
-}
+  /**
+   * Listen to an event emitted by the webview
+   *
+   * @param event the event name
+   * @param handler the event handler callback
+   * @param once unlisten after the first trigger if true
+   */
+  async listen<T>(event: string, handler: EventCallback<T>, once = false): Promise<void> {
+    await listen(event, handler, once)
+  }
 
-/**
- * Sets the window visibility to true.
- */
-async function show(): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'show'
-    }
-  })
+  /**
+  * emits an event to the webview
+  *
+  * @param event the event name
+  * @param [payload] the event payload
+  */
+  async emit(event: string, payload?: string): Promise<void> {
+    await emit(event, this.label, payload)
+  }
 }
 
-/**
- * Sets the window visibility to false.
- */
-async function hide(): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'hide'
-    }
-  })
+function getTauriWindow(label: string = getCurrentWindow().label): TauriWindow | null {
+  if (getWindows().some(w => w.label === label)) {
+    return new TauriWindow(label)
+  } else {
+    return null
+  }
 }
 
-/**
- * Sets the window transparent flag.
- *
- * @param {boolean} transparent whether the the window should be transparent or not
+class WindowManager {
+  /**
+ * Updates the window resizable flag.
  */
-async function setTransparent(transparent: boolean): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'setTransparent',
-      transparent
-    }
-  })
-}
+  async setResizable(resizable: boolean): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'setResizable',
+        resizable
+      }
+    })
+  }
 
-/**
- * Whether the window should have borders and bars.
- *
- * @param {boolean} decorations whether the window should have borders and bars
- */
-async function setDecorations(decorations: boolean): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'setDecorations',
-      decorations
-    }
-  })
-}
+  /**
+   * sets the window title
+   *
+   * @param title the new title
+   */
+  async setTitle(title: string): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'setTitle',
+        title
+      }
+    })
+  }
 
-/**
- * Whether the window should always be on top of other windows.
- *
- * @param {boolean} alwaysOnTop whether the window should always be on top of other windows or not
- */
-async function setAlwaysOnTop(alwaysOnTop: boolean): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'setAlwaysOnTop',
-      alwaysOnTop
-    }
-  })
-}
+  /**
+   * Maximizes the window.
+   */
+  async maximize(): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'maximize'
+      }
+    })
+  }
 
-/**
- * Sets the window width.
- *
- * @param {number} width the new window width
- */
-async function setWidth(width: number): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'setWidth',
-      width
-    }
-  })
-}
+  /**
+   * Unmaximizes the window.
+   */
+  async unmaximize(): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'unmaximize'
+      }
+    })
+  }
 
-/**
- * Sets the window height.
- *
- * @param {number} height the new window height
- */
-async function setHeight(height: number): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'setHeight',
-      height
-    }
-  })
-}
+  /**
+   * Minimizes the window.
+   */
+  async minimize(): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'minimize'
+      }
+    })
+  }
 
-/**
- * Resizes the window.
- *
- * @param {number} width the new window width
- * @param {number} height the new window height
- */
-async function resize(width: number, height: number): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'resize',
-      width,
-      height
-    }
-  })
-}
+  /**
+   * Unminimizes the window.
+   */
+  async unminimize(): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'unminimize'
+      }
+    })
+  }
 
-/**
- * Sets the window min size.
- *
- * @param {number} minWidth the new window min width
- * @param {number} minHeight the new window min height
- */
-async function setMinSize(minWidth: number, minHeight: number): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'setMinSize',
-      minWidth,
-      minHeight
-    }
-  })
-}
+  /**
+   * Sets the window visibility to true.
+   */
+  async show(): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'show'
+      }
+    })
+  }
 
-/**
- * Sets the window max size.
- *
- * @param {number} maxWidth the new window max width
- * @param {number} maxHeight the new window max height
- */
-async function setMaxSize(maxWidth: number, maxHeight: number): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'setMaxSize',
-      maxWidth,
-      maxHeight
-    }
-  })
-}
+  /**
+   * Sets the window visibility to false.
+   */
+  async hide(): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'hide'
+      }
+    })
+  }
 
-/**
- * Sets the window x position.
- *
- * @param {number} x the new window x position
- */
-async function setX(x: number): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'setX',
-      x
-    }
-  })
-}
+  /**
+   * Sets the window transparent flag.
+   *
+   * @param {boolean} transparent whether the the window should be transparent or not
+   */
+  async setTransparent(transparent: boolean): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'setTransparent',
+        transparent
+      }
+    })
+  }
 
-/**
- * Sets the window y position.
- *
- * @param {number} y the new window y position
- */
-async function setY(y: number): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'setY',
-      y
-    }
-  })
-}
+  /**
+   * Whether the window should have borders and bars.
+   *
+   * @param {boolean} decorations whether the window should have borders and bars
+   */
+  async setDecorations(decorations: boolean): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'setDecorations',
+        decorations
+      }
+    })
+  }
 
-/**
- * Sets the window position.
- *
- * @param {number} x the new window x position
- * @param {number} y the new window y position
- */
-async function setPosition(x: number, y: number): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'setPosition',
-      x,
-      y
-    }
-  })
-}
+  /**
+   * Whether the window should always be on top of other windows.
+   *
+   * @param {boolean} alwaysOnTop whether the window should always be on top of other windows or not
+   */
+  async setAlwaysOnTop(alwaysOnTop: boolean): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'setAlwaysOnTop',
+        alwaysOnTop
+      }
+    })
+  }
 
-/**
- * Sets the window fullscreen state.
- *
- * @param {boolean} fullscreen whether the window should go to fullscreen or not
- */
-async function setFullscreen(fullscreen: boolean): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'setFullscreen',
-      fullscreen
-    }
-  })
-}
+  /**
+   * Sets the window width.
+   *
+   * @param {number} width the new window width
+   */
+  async setWidth(width: number): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'setWidth',
+        width
+      }
+    })
+  }
 
-/**
- * Sets the window icon
- *
- * @param {string | number[]} icon icon bytes or path to the icon file
- */
-async function setIcon(icon: 'string' | number[]): Promise<void> {
-  await invoke({
-    __tauriModule: 'Window',
-    message: {
-      cmd: 'setIcon',
-      icon
-    }
-  })
+  /**
+   * Sets the window height.
+   *
+   * @param {number} height the new window height
+   */
+  async setHeight(height: number): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'setHeight',
+        height
+      }
+    })
+  }
+
+  /**
+   * Resizes the window.
+   *
+   * @param {number} width the new window width
+   * @param {number} height the new window height
+   */
+  async resize(width: number, height: number): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'resize',
+        width,
+        height
+      }
+    })
+  }
+
+  /**
+   * Sets the window min size.
+   *
+   * @param {number} minWidth the new window min width
+   * @param {number} minHeight the new window min height
+   */
+  async setMinSize(minWidth: number, minHeight: number): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'setMinSize',
+        minWidth,
+        minHeight
+      }
+    })
+  }
+
+  /**
+   * Sets the window max size.
+   *
+   * @param {number} maxWidth the new window max width
+   * @param {number} maxHeight the new window max height
+   */
+  async setMaxSize(maxWidth: number, maxHeight: number): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'setMaxSize',
+        maxWidth,
+        maxHeight
+      }
+    })
+  }
+
+  /**
+   * Sets the window x position.
+   *
+   * @param {number} x the new window x position
+   */
+  async setX(x: number): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'setX',
+        x
+      }
+    })
+  }
+
+  /**
+   * Sets the window y position.
+   *
+   * @param {number} y the new window y position
+   */
+  async setY(y: number): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'setY',
+        y
+      }
+    })
+  }
+
+  /**
+   * Sets the window position.
+   *
+   * @param {number} x the new window x position
+   * @param {number} y the new window y position
+   */
+  async setPosition(x: number, y: number): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'setPosition',
+        x,
+        y
+      }
+    })
+  }
+
+  /**
+   * Sets the window fullscreen state.
+   *
+   * @param {boolean} fullscreen whether the window should go to fullscreen or not
+   */
+  async setFullscreen(fullscreen: boolean): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'setFullscreen',
+        fullscreen
+      }
+    })
+  }
+
+  /**
+   * Sets the window icon
+   *
+   * @param {string | number[]} icon icon bytes or path to the icon file
+   */
+  async setIcon(icon: 'string' | number[]): Promise<void> {
+    await invoke({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'setIcon',
+        icon
+      }
+    })
+  }
 }
 
+const manager = new WindowManager()
+
 export {
-  setResizable,
-  setTitle,
-  maximize,
-  unmaximize,
-  minimize,
-  unminimize,
-  show,
-  hide,
-  setTransparent,
-  setDecorations,
-  setAlwaysOnTop,
-  setWidth,
-  setHeight,
-  resize,
-  setMinSize,
-  setMaxSize,
-  setX,
-  setY,
-  setPosition,
-  setFullscreen,
-  setIcon
+  TauriWindow,
+  getTauriWindow,
+  getCurrentWindow,
+  getWindows,
+  manager
 }

+ 7 - 5
tauri/examples/api/src/components/Window.svelte

@@ -1,7 +1,11 @@
 <script>
-  import {
+  import { manager as windowManager } from "@tauri-apps/api/window";
+  import { open as openDialog } from '@tauri-apps/api/dialog'
+  import { open } from "@tauri-apps/api/shell";
+
+  const {
     setResizable,
-    setTitle as setTitle,
+    setTitle,
     maximize,
     unmaximize,
     minimize,
@@ -21,9 +25,7 @@
     // setPosition,
     setFullscreen,
     setIcon
-  } from "@tauri-apps/api/window";
-  import { open as openDialog } from '@tauri-apps/api/dialog'
-  import { open } from "@tauri-apps/api/shell";
+  } = windowManager
 
   let urlValue = "https://tauri.studio";
   let resizable = true

文件差異過大導致無法顯示
+ 0 - 0
tauri/examples/communication/dist/__tauri.js


+ 1 - 1
tauri/examples/communication/dist/window.js

@@ -14,6 +14,6 @@ addClickEnterHandler(
   document.getElementById("set-title"),
   titleInput,
   function () {
-    window.__TAURI__.window.setTitle(titleInput.value);
+    window.__TAURI__.window.manager.setTitle(titleInput.value);
   }
 );

文件差異過大導致無法顯示
+ 0 - 0
tauri/examples/multiwindow/dist/__tauri.js


+ 50 - 37
tauri/examples/multiwindow/dist/index.html

@@ -1,40 +1,53 @@
 <!DOCTYPE html>
 <html>
-  <body>
-    <div id="window-label"></div>
-    <div id="container"></div>
-    <div id="response"></div>
-
-    <script>
-      var windowLabel = window.__TAURI__.currentWindow.label;
-      var windowLabelContainer = document.getElementById("window-label");
-      windowLabelContainer.innerHTML =
-        "This is the " + windowLabel + " window.";
-
-      var responseContainer = document.getElementById("response");
-      window.__TAURI__.event.listen(
-        "window://" + windowLabel,
-        function (event) {
-          responseContainer.innerHTML = "Got " + JSON.stringify(event);
-        }
-      );
-
-      var container = document.getElementById("container");
-      for (var index in window.__TAURI__.windows) {
-        const label = window.__TAURI__.windows[index].label;
-        if (label === windowLabel) {
-          continue;
-        }
-        const button = document.createElement("button");
-        button.innerHTML = "Send message to " + label;
-        button.addEventListener("click", function () {
-          window.__TAURI__.event.emit(
-            "window://" + label,
-            "message from " + windowLabel
-          );
-        });
-        container.appendChild(button);
+
+<body>
+  <div id="window-label"></div>
+  <div id="container"></div>
+  <div id="response"></div>
+
+  <script>
+    var windowLabel = window.__TAURI__.window.getCurrentWindow().label;
+    var windowLabelContainer = document.getElementById("window-label");
+    windowLabelContainer.innerHTML =
+      "This is the " + windowLabel + " window.";
+
+    // global listener
+    window.__TAURI__.event.listen("clicked", function (event) {
+      responseContainer.innerHTML += "Got " + JSON.stringify(event) + " on global listener<br><br>";
+    })
+
+    var responseContainer = document.getElementById("response");
+    var thisTauriWindow = window.__TAURI__.window.getTauriWindow()
+    // listener tied to this window
+    thisTauriWindow.listen("clicked", function (event) {
+      responseContainer.innerHTML += "Got " + JSON.stringify(event) + " on window listener<br><br>";
+    });
+
+    var container = document.getElementById("container");
+
+    var button = document.createElement("button");
+    button.innerHTML = "Send global message";
+    button.addEventListener("click", function () {
+      // emit to all windows
+      window.__TAURI__.event.emit("clicked", "message from " + windowLabel);
+    });
+    container.appendChild(button);
+
+    for (var index in window.__TAURI__.window.getWindows()) {
+      var label = window.__TAURI__.window.getWindows()[index].label;
+      if (label === windowLabel) {
+        continue;
       }
-    </script>
-  </body>
-</html>
+      var tauriWindow = window.__TAURI__.window.getTauriWindow(label)
+      var button = document.createElement("button");
+      button.innerHTML = "Send message to " + label;
+      button.addEventListener("click", function () {
+        tauriWindow.emit("clicked", "message from " + windowLabel);
+      });
+      container.appendChild(button);
+    }
+  </script>
+</body>
+
+</html>

+ 8 - 6
tauri/examples/multiwindow/src-tauri/src/main.rs

@@ -10,13 +10,15 @@ struct Context;
 fn main() {
   tauri::AppBuilder::<tauri::flavors::Wry, Context>::new()
     .setup(|webview_manager| async move {
+      if webview_manager.current_window_label() == "Main" {
+        webview_manager.listen("clicked", move |_| {
+          println!("got 'clicked' event on global channel");
+        });
+      }
       let current_webview = webview_manager.current_webview().unwrap().clone();
-      let current_webview_ = current_webview.clone();
-      let event_name = format!("window://{}", webview_manager.current_window_label());
-      current_webview.listen(event_name.clone(), move |msg| {
-        current_webview_
-          .emit(&event_name, msg)
-          .expect("failed to emit");
+      let label = webview_manager.current_window_label().to_string();
+      current_webview.listen("clicked", move |_| {
+        println!("got 'clicked' event on window '{}'", label)
       });
     })
     .build()

+ 23 - 8
tauri/src/app/event.rs

@@ -12,6 +12,8 @@ use serde_json::Value as JsonValue;
 
 /// An event handler.
 struct EventHandler {
+  /// A event handler might be global or tied to a window.
+  window_label: Option<String>,
   /// The on event callback.
   on_event: Box<dyn FnMut(Option<String>) + Send>,
 }
@@ -26,7 +28,7 @@ lazy_static! {
 
 /// Gets the listeners map.
 fn listeners() -> &'static Listeners {
-  static LISTENERS: Lazy<Listeners> = Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));
+  static LISTENERS: Lazy<Listeners> = Lazy::new(Default::default);
   &LISTENERS
 }
 
@@ -46,11 +48,16 @@ pub fn event_queue_object_name() -> String {
 }
 
 /// Adds an event listener for JS events.
-pub fn listen<F: FnMut(Option<String>) + Send + 'static>(id: impl AsRef<str>, handler: F) {
+pub fn listen<F: FnMut(Option<String>) + Send + 'static>(
+  id: impl AsRef<str>,
+  window_label: Option<String>,
+  handler: F,
+) {
   let mut l = listeners()
     .lock()
     .expect("Failed to lock listeners: listen()");
   let handler = EventHandler {
+    window_label,
     on_event: Box::new(handler),
   };
   if let Some(listeners) = l.get_mut(id.as_ref()) {
@@ -86,7 +93,7 @@ pub fn emit<D: ApplicationDispatcherExt, S: Serialize>(
 }
 
 /// Triggers the given event with its payload.
-pub fn on_event(event: String, data: Option<String>) {
+pub fn on_event(event: String, window_label: Option<&str>, data: Option<String>) {
   let mut l = listeners()
     .lock()
     .expect("Failed to lock listeners: on_event()");
@@ -94,7 +101,15 @@ pub fn on_event(event: String, data: Option<String>) {
   if l.contains_key(&event) {
     let listeners = l.get_mut(&event).expect("Failed to get mutable handler");
     for handler in listeners {
-      (handler.on_event)(data.clone());
+      if let Some(target_window_label) = window_label {
+        // if the emitted event targets a specifid window, only triggers the listeners associated to that window
+        if handler.window_label.as_deref() == Some(target_window_label) {
+          (handler.on_event)(data.clone())
+        }
+      } else {
+        // otherwise triggers all listeners
+        (handler.on_event)(data.clone())
+      }
     }
   }
 }
@@ -117,7 +132,7 @@ mod test {
       // clone e as the key
       let key = e.clone();
       // pass e and an dummy func into listen
-      listen(e, event_fn);
+      listen(e, None, event_fn);
 
       // lock mutex
       let l = listeners().lock().unwrap();
@@ -132,7 +147,7 @@ mod test {
        // clone e as the key
        let key = e.clone();
        // pass e and an dummy func into listen
-       listen(e, event_fn);
+       listen(e, None, event_fn);
 
        // lock mutex
        let mut l = listeners().lock().unwrap();
@@ -157,9 +172,9 @@ mod test {
       // clone e as the key
       let key = e.clone();
       // call listen with e and the event_fn dummy func
-      listen(e.clone(), event_fn);
+      listen(e.clone(), None, event_fn);
       // call on event with e and d.
-      on_event(e, Some(d));
+      on_event(e, None, Some(d));
 
       // lock the mutex
       let l = listeners().lock().unwrap();

+ 3 - 3
tauri/src/app/runner.rs

@@ -325,7 +325,7 @@ fn build_webview<A: ApplicationExt + 'static>(
     let dispatcher = webview_application.dispatcher(&window);
     dispatchers.insert(
       window_config.label.to_string(),
-      WebviewDispatcher::new(dispatcher),
+      WebviewDispatcher::new(dispatcher, window_config.label.to_string()),
     );
     window_refs.push((window_config, window));
   }
@@ -395,8 +395,8 @@ fn build_webview<A: ApplicationExt + 'static>(
         .initialization_script(&initialization_script)
         .initialization_script(&format!(
           r#"
-              window.__TAURI__.windows = {window_labels_array}.map(function (label) {{ return {{ label: label }} }});
-              window.__TAURI__.currentWindow = {{ label: "{current_window_label}" }}
+              window.__TAURI__.__windows = {window_labels_array}.map(function (label) {{ return {{ label: label }} }});
+              window.__TAURI__.__currentWindow = {{ label: "{current_window_label}" }}
             "#,
           window_labels_array =
             serde_json::to_string(&dispatchers.keys().collect::<Vec<&String>>()).unwrap(),

+ 91 - 29
tauri/src/app/webview_manager.rs

@@ -6,23 +6,34 @@ use serde::Serialize;
 
 /// The webview dispatcher.
 #[derive(Clone)]
-pub struct WebviewDispatcher<A: Clone>(A);
+pub struct WebviewDispatcher<A: Clone> {
+  dispatcher: A,
+  window_label: String,
+}
 
 impl<A: ApplicationDispatcherExt> WebviewDispatcher<A> {
-  pub(crate) fn new(dispatcher: A) -> Self {
-    Self(dispatcher)
+  pub(crate) fn new(dispatcher: A, window_label: String) -> Self {
+    Self {
+      dispatcher,
+      window_label,
+    }
+  }
+
+  /// The label of the window tied to this dispatcher.
+  pub fn window_label(&self) -> &str {
+    &self.window_label
   }
 
-  /// Listen to an event.
+  /// Listen to a webview event.
   pub fn listen<F: FnMut(Option<String>) + Send + 'static>(
     &self,
     event: impl AsRef<str>,
     handler: F,
   ) {
-    super::event::listen(event, handler)
+    super::event::listen(event, Some(self.window_label.to_string()), handler)
   }
 
-  /// Emits an event.
+  /// Emits an event to the webview.
   pub fn emit<S: Serialize>(
     &self,
     event: impl AsRef<str>,
@@ -31,83 +42,100 @@ impl<A: ApplicationDispatcherExt> WebviewDispatcher<A> {
     super::event::emit(&self, event, payload)
   }
 
+  /// Emits an event from the webview.
   pub(crate) fn on_event(&self, event: String, data: Option<String>) {
-    super::event::on_event(event, data)
+    super::event::on_event(event, Some(&self.window_label), data)
   }
 
   /// Evaluates a JS script.
   pub fn eval(&self, js: &str) {
-    self.0.send_message(Message::EvalScript(js.to_string()))
+    self
+      .dispatcher
+      .send_message(Message::EvalScript(js.to_string()))
   }
 
   /// Updates the window resizable flag.
   pub fn set_resizable(&self, resizable: bool) {
-    self.0.send_message(Message::SetResizable(resizable))
+    self
+      .dispatcher
+      .send_message(Message::SetResizable(resizable))
   }
 
   /// Updates the window title.
   pub fn set_title(&self, title: &str) {
-    self.0.send_message(Message::SetTitle(title.to_string()))
+    self
+      .dispatcher
+      .send_message(Message::SetTitle(title.to_string()))
   }
 
   /// Maximizes the window.
   pub fn maximize(&self) {
-    self.0.send_message(Message::Maximize)
+    self.dispatcher.send_message(Message::Maximize)
   }
 
   /// Unmaximizes the window.
   pub fn unmaximize(&self) {
-    self.0.send_message(Message::Unmaximize)
+    self.dispatcher.send_message(Message::Unmaximize)
   }
 
   /// Minimizes the window.
   pub fn minimize(&self) {
-    self.0.send_message(Message::Minimize)
+    self.dispatcher.send_message(Message::Minimize)
   }
 
   /// Unminimizes the window.
   pub fn unminimize(&self) {
-    self.0.send_message(Message::Unminimize)
+    self.dispatcher.send_message(Message::Unminimize)
   }
 
   /// Sets the window visibility to true.
   pub fn show(&self) {
-    self.0.send_message(Message::Show)
+    self.dispatcher.send_message(Message::Show)
   }
 
   /// Sets the window visibility to false.
   pub fn hide(&self) {
-    self.0.send_message(Message::Hide)
+    self.dispatcher.send_message(Message::Hide)
   }
 
   /// Sets the window transparent flag.
   pub fn set_transparent(&self, transparent: bool) {
-    self.0.send_message(Message::SetTransparent(transparent))
+    self
+      .dispatcher
+      .send_message(Message::SetTransparent(transparent))
   }
 
   /// Whether the window should have borders and bars.
   pub fn set_decorations(&self, decorations: bool) {
-    self.0.send_message(Message::SetDecorations(decorations))
+    self
+      .dispatcher
+      .send_message(Message::SetDecorations(decorations))
   }
 
   /// Whether the window should always be on top of other windows.
   pub fn set_always_on_top(&self, always_on_top: bool) {
-    self.0.send_message(Message::SetAlwaysOnTop(always_on_top))
+    self
+      .dispatcher
+      .send_message(Message::SetAlwaysOnTop(always_on_top))
   }
 
   /// Sets the window width.
   pub fn set_width(&self, width: impl Into<f64>) {
-    self.0.send_message(Message::SetWidth(width.into()))
+    self
+      .dispatcher
+      .send_message(Message::SetWidth(width.into()))
   }
 
   /// Sets the window height.
   pub fn set_height(&self, height: impl Into<f64>) {
-    self.0.send_message(Message::SetHeight(height.into()))
+    self
+      .dispatcher
+      .send_message(Message::SetHeight(height.into()))
   }
 
   /// Resizes the window.
   pub fn resize(&self, width: impl Into<f64>, height: impl Into<f64>) {
-    self.0.send_message(Message::Resize {
+    self.dispatcher.send_message(Message::Resize {
       width: width.into(),
       height: height.into(),
     })
@@ -115,7 +143,7 @@ impl<A: ApplicationDispatcherExt> WebviewDispatcher<A> {
 
   /// Sets the window min size.
   pub fn set_min_size(&self, min_width: impl Into<f64>, min_height: impl Into<f64>) {
-    self.0.send_message(Message::SetMinSize {
+    self.dispatcher.send_message(Message::SetMinSize {
       min_width: min_width.into(),
       min_height: min_height.into(),
     })
@@ -123,7 +151,7 @@ impl<A: ApplicationDispatcherExt> WebviewDispatcher<A> {
 
   /// Sets the window max size.
   pub fn set_max_size(&self, max_width: impl Into<f64>, max_height: impl Into<f64>) {
-    self.0.send_message(Message::SetMaxSize {
+    self.dispatcher.send_message(Message::SetMaxSize {
       max_width: max_width.into(),
       max_height: max_height.into(),
     })
@@ -131,17 +159,17 @@ impl<A: ApplicationDispatcherExt> WebviewDispatcher<A> {
 
   /// Sets the window x position.
   pub fn set_x(&self, x: impl Into<f64>) {
-    self.0.send_message(Message::SetX(x.into()))
+    self.dispatcher.send_message(Message::SetX(x.into()))
   }
 
   /// Sets the window y position.
   pub fn set_y(&self, y: impl Into<f64>) {
-    self.0.send_message(Message::SetY(y.into()))
+    self.dispatcher.send_message(Message::SetY(y.into()))
   }
 
   /// Sets the window position.
   pub fn set_position(&self, x: impl Into<f64>, y: impl Into<f64>) {
-    self.0.send_message(Message::SetPosition {
+    self.dispatcher.send_message(Message::SetPosition {
       x: x.into(),
       y: y.into(),
     })
@@ -149,12 +177,14 @@ impl<A: ApplicationDispatcherExt> WebviewDispatcher<A> {
 
   /// Sets the window fullscreen state.
   pub fn set_fullscreen(&self, fullscreen: bool) {
-    self.0.send_message(Message::SetFullscreen(fullscreen))
+    self
+      .dispatcher
+      .send_message(Message::SetFullscreen(fullscreen))
   }
 
   /// Sets the window icon.
   pub fn set_icon(&self, icon: Icon) {
-    self.0.send_message(Message::SetIcon(icon))
+    self.dispatcher.send_message(Message::SetIcon(icon))
   }
 }
 
@@ -173,6 +203,11 @@ impl<A: ApplicationDispatcherExt> WebviewManager<A> {
     }
   }
 
+  /// Gets the map of the current webviews.
+  pub fn webviews(&self) -> &HashMap<String, WebviewDispatcher<A>> {
+    &self.dispatchers
+  }
+
   /// Returns the label of the window associated with the current context.
   pub fn current_window_label(&self) -> &str {
     &self.current_webview_window_label
@@ -190,4 +225,31 @@ impl<A: ApplicationDispatcherExt> WebviewManager<A> {
       .get(window_label)
       .ok_or(crate::Error::WebviewNotFound)
   }
+
+  /// Listen to a global event.
+  /// An event from any webview will trigger the handler.
+  pub fn listen<F: FnMut(Option<String>) + Send + 'static>(
+    &self,
+    event: impl AsRef<str>,
+    handler: F,
+  ) {
+    super::event::listen(event, None, handler)
+  }
+
+  /// Emits an event to all webviews.
+  pub fn emit<S: Serialize + Clone>(
+    &self,
+    event: impl AsRef<str>,
+    payload: Option<S>,
+  ) -> crate::Result<()> {
+    for dispatcher in self.dispatchers.values() {
+      super::event::emit(&dispatcher, event.as_ref(), payload.clone())?;
+    }
+    Ok(())
+  }
+
+  /// Emits a global event from the webview.
+  pub(crate) fn on_event(&self, event: String, data: Option<String>) {
+    super::event::on_event(event, None, data)
+  }
 }

+ 28 - 18
tauri/src/endpoints/event.rs

@@ -5,15 +5,18 @@ use serde_json::Value as JsonValue;
 #[derive(Deserialize)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
-  /// The event listen API.
+  /// Listen to an event.
   Listen {
     event: String,
     handler: String,
     once: bool,
   },
-  /// The event emit API.
+  /// Emit an event to the webview associated with the given window.
+  /// If the window_label is omitted, the event will be triggered on all listeners.
+  #[serde(rename_all = "camelCase")]
   Emit {
     event: String,
+    window_label: Option<String>,
     payload: Option<String>,
   },
 }
@@ -23,30 +26,37 @@ impl Cmd {
     self,
     webview_manager: &crate::WebviewManager<D>,
   ) -> crate::Result<JsonValue> {
+    #[cfg(not(event))]
+    return Err(crate::Error::ApiNotAllowlisted("event".to_string()));
+    #[cfg(event)]
     match self {
       Self::Listen {
         event,
         handler,
         once,
       } => {
-        #[cfg(event)]
-        {
-          let js_string = listen_fn(event, handler, once)?;
-          webview_manager.current_webview()?.eval(&js_string);
-          Ok(JsonValue::Null)
-        }
-        #[cfg(not(event))]
-        Err(crate::Error::ApiNotAllowlisted("event".to_string()))
+        let js_string = listen_fn(event, handler, once)?;
+        webview_manager.current_webview()?.eval(&js_string);
+        Ok(JsonValue::Null)
       }
-      Self::Emit { event, payload } => {
-        // TODO emit to optional window
-        #[cfg(event)]
-        {
-          webview_manager.current_webview()?.on_event(event, payload);
-          Ok(JsonValue::Null)
+      Self::Emit {
+        event,
+        window_label,
+        payload,
+      } => {
+        if let Some(label) = window_label {
+          let dispatcher = webview_manager.get_webview(&label)?;
+          // dispatch the event to Rust listeners
+          dispatcher.on_event(event.to_string(), payload.clone());
+          // dispatch the event to JS listeners
+          dispatcher.emit(event, payload)?;
+        } else {
+          // dispatch the event to Rust listeners
+          webview_manager.on_event(event.to_string(), payload.clone());
+          // dispatch the event to JS listeners
+          webview_manager.emit(event, payload)?;
         }
-        #[cfg(not(event))]
-        Err(crate::Error::ApiNotAllowlisted("event".to_string()))
+        Ok(JsonValue::Null)
       }
     }
   }

部分文件因文件數量過多而無法顯示