window.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. /**
  5. * Provides APIs to create windows, communicate with other windows and manipulate the current window.
  6. * @packageDocumentation
  7. */
  8. import { invokeTauriCommand } from './helpers/tauri'
  9. import { EventCallback, UnlistenFn, listen, once } from './event'
  10. import { emit } from './helpers/event'
  11. /** Allows you to retrieve information about a given monitor. */
  12. interface Monitor {
  13. /** Human-readable name of the monitor */
  14. name: string | null
  15. /** The monitor's resolution. */
  16. size: PhysicalSize
  17. /** the Top-left corner position of the monitor relative to the larger full screen area. */
  18. position: PhysicalPosition
  19. /** The scale factor that can be used to map physical pixels to logical pixels. */
  20. scaleFactor: number
  21. }
  22. /** A size represented in logical pixels. */
  23. class LogicalSize {
  24. type = 'Logical'
  25. width: number
  26. height: number
  27. constructor(width: number, height: number) {
  28. this.width = width
  29. this.height = height
  30. }
  31. }
  32. /** A size represented in physical pixels. */
  33. class PhysicalSize {
  34. type = 'Physical'
  35. width: number
  36. height: number
  37. constructor(width: number, height: number) {
  38. this.width = width
  39. this.height = height
  40. }
  41. /** Converts the physical size to a logical one. */
  42. toLogical(scaleFactor: number): LogicalSize {
  43. return new LogicalSize(this.width / scaleFactor, this.height / scaleFactor)
  44. }
  45. }
  46. /** A position represented in logical pixels. */
  47. class LogicalPosition {
  48. type = 'Logical'
  49. x: number
  50. y: number
  51. constructor(x: number, y: number) {
  52. this.x = x
  53. this.y = y
  54. }
  55. }
  56. /** A position represented in physical pixels. */
  57. class PhysicalPosition {
  58. type = 'Physical'
  59. x: number
  60. y: number
  61. constructor(x: number, y: number) {
  62. this.x = x
  63. this.y = y
  64. }
  65. /** Converts the physical position to a logical one. */
  66. toLogical(scaleFactor: number): LogicalPosition {
  67. return new LogicalPosition(this.x / scaleFactor, this.y / scaleFactor)
  68. }
  69. }
  70. /** @ignore */
  71. interface WindowDef {
  72. label: string
  73. }
  74. /** @ignore */
  75. declare global {
  76. interface Window {
  77. __TAURI__: {
  78. __windows: WindowDef[]
  79. __currentWindow: WindowDef
  80. }
  81. }
  82. }
  83. /**
  84. * Get a handle to the current webview window. Allows emitting and listening to events from the backend that are tied to the window.
  85. *
  86. * @return The current window handle.
  87. */
  88. function getCurrent(): WebviewWindowHandle {
  89. return new WebviewWindowHandle(window.__TAURI__.__currentWindow.label)
  90. }
  91. /**
  92. * Gets metadata for all available webview windows.
  93. *
  94. * @return The list of webview handles.
  95. */
  96. function getAll(): WindowDef[] {
  97. return window.__TAURI__.__windows
  98. }
  99. /** @ignore */
  100. // events that are emitted right here instead of by the created webview
  101. const localTauriEvents = ['tauri://created', 'tauri://error']
  102. /**
  103. * A webview window handle allows emitting and listening to events from the backend that are tied to the window.
  104. */
  105. class WebviewWindowHandle {
  106. /** Window label. */
  107. label: string
  108. /** Local event listeners. */
  109. listeners: { [key: string]: Array<EventCallback<any>> }
  110. constructor(label: string) {
  111. this.label = label
  112. // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
  113. this.listeners = Object.create(null)
  114. }
  115. /**
  116. * Listen to an event emitted by the backend that is tied to the webview window.
  117. *
  118. * @param event Event name.
  119. * @param handler Event handler.
  120. * @returns A promise resolving to a function to unlisten to the event.
  121. */
  122. async listen<T>(
  123. event: string,
  124. handler: EventCallback<T>
  125. ): Promise<UnlistenFn> {
  126. if (this._handleTauriEvent(event, handler)) {
  127. return Promise.resolve(() => {
  128. // eslint-disable-next-line security/detect-object-injection
  129. const listeners = this.listeners[event]
  130. listeners.splice(listeners.indexOf(handler), 1)
  131. })
  132. }
  133. return listen(event, handler)
  134. }
  135. /**
  136. * Listen to an one-off event emitted by the backend that is tied to the webview window.
  137. *
  138. * @param event Event name.
  139. * @param handler Event handler.
  140. * @returns A promise resolving to a function to unlisten to the event.
  141. */
  142. async once<T>(event: string, handler: EventCallback<T>): Promise<UnlistenFn> {
  143. if (this._handleTauriEvent(event, handler)) {
  144. return Promise.resolve(() => {
  145. // eslint-disable-next-line security/detect-object-injection
  146. const listeners = this.listeners[event]
  147. listeners.splice(listeners.indexOf(handler), 1)
  148. })
  149. }
  150. return once(event, handler)
  151. }
  152. /**
  153. * Emits an event to the backend, tied to the webview window.
  154. *
  155. * @param event Event name.
  156. * @param payload Event payload.
  157. */
  158. async emit(event: string, payload?: string): Promise<void> {
  159. if (localTauriEvents.includes(event)) {
  160. // eslint-disable-next-line
  161. for (const handler of this.listeners[event] || []) {
  162. handler({ event, id: -1, payload })
  163. }
  164. return Promise.resolve()
  165. }
  166. return emit(event, this.label, payload)
  167. }
  168. _handleTauriEvent<T>(event: string, handler: EventCallback<T>): boolean {
  169. if (localTauriEvents.includes(event)) {
  170. if (!(event in this.listeners)) {
  171. // eslint-disable-next-line
  172. this.listeners[event] = [handler]
  173. } else {
  174. // eslint-disable-next-line
  175. this.listeners[event].push(handler)
  176. }
  177. return true
  178. }
  179. return false
  180. }
  181. }
  182. /**
  183. * Create new webview windows and get a handle to existing ones.
  184. * @example
  185. * ```typescript
  186. * // loading embedded asset:
  187. * const webview = new WebviewWindow('theUniqueLabel', {
  188. * url: 'path/to/page.html'
  189. * })
  190. * // alternatively, load a remote URL:
  191. * const webview = new WebviewWindow('theUniqueLabel', {
  192. * url: 'https://github.com/tauri-apps/tauri'
  193. * })
  194. *
  195. * webview.once('tauri://created', function () {
  196. * // webview window successfully created
  197. * })
  198. * webview.once('tauri://error', function (e) {
  199. * // an error happened creating the webview window
  200. * })
  201. *
  202. * // emit an event to the backend
  203. * await webview.emit("some event", "data")
  204. * // listen to an event from the backend
  205. * const unlisten = await webview.listen("event name", e => {})
  206. * unlisten()
  207. * ```
  208. */
  209. class WebviewWindow extends WebviewWindowHandle {
  210. private constructor(label: string, options: WindowOptions = {}) {
  211. super(label)
  212. invokeTauriCommand({
  213. __tauriModule: 'Window',
  214. message: {
  215. cmd: 'createWebview',
  216. data: {
  217. options: {
  218. label,
  219. ...options
  220. }
  221. }
  222. }
  223. })
  224. .then(async () => this.emit('tauri://created'))
  225. .catch(async (e) => this.emit('tauri://error', e))
  226. }
  227. /**
  228. * Gets the WebviewWindow handle for the webview associated with the given label.
  229. *
  230. * @param label The webview window label.
  231. * @returns The handle to communicate with the webview or null if the webview doesn't exist.
  232. */
  233. static getByLabel(label: string): WebviewWindowHandle | null {
  234. if (getAll().some((w) => w.label === label)) {
  235. return new WebviewWindowHandle(label)
  236. }
  237. return null
  238. }
  239. }
  240. /**
  241. * Manage the current window object.
  242. */
  243. export class WindowManager {
  244. // Getters
  245. /** The scale factor that can be used to map physical pixels to logical pixels. */
  246. async scaleFactor(): Promise<number> {
  247. return invokeTauriCommand({
  248. __tauriModule: 'Window',
  249. message: {
  250. cmd: 'scaleFactor'
  251. }
  252. })
  253. }
  254. /** The position of the top-left hand corner of the window's client area relative to the top-left hand corner of the desktop. */
  255. async innerPosition(): Promise<PhysicalPosition> {
  256. return invokeTauriCommand({
  257. __tauriModule: 'Window',
  258. message: {
  259. cmd: 'innerPosition'
  260. }
  261. })
  262. }
  263. /** The position of the top-left hand corner of the window relative to the top-left hand corner of the desktop. */
  264. async outerPosition(): Promise<PhysicalPosition> {
  265. return invokeTauriCommand({
  266. __tauriModule: 'Window',
  267. message: {
  268. cmd: 'outerPosition'
  269. }
  270. })
  271. }
  272. /**
  273. * The physical size of the window's client area.
  274. * The client area is the content of the window, excluding the title bar and borders.
  275. */
  276. async innerSize(): Promise<PhysicalSize> {
  277. return invokeTauriCommand({
  278. __tauriModule: 'Window',
  279. message: {
  280. cmd: 'innerSize'
  281. }
  282. })
  283. }
  284. /**
  285. * The physical size of the entire window.
  286. * These dimensions include the title bar and borders. If you don't want that (and you usually don't), use inner_size instead.
  287. */
  288. async outerSize(): Promise<PhysicalSize> {
  289. return invokeTauriCommand({
  290. __tauriModule: 'Window',
  291. message: {
  292. cmd: 'outerSize'
  293. }
  294. })
  295. }
  296. /** Gets the window's current fullscreen state. */
  297. async isFullscreen(): Promise<boolean> {
  298. return invokeTauriCommand({
  299. __tauriModule: 'Window',
  300. message: {
  301. cmd: 'isFullscreen'
  302. }
  303. })
  304. }
  305. /** Gets the window's current maximized state. */
  306. async isMaximized(): Promise<boolean> {
  307. return invokeTauriCommand({
  308. __tauriModule: 'Window',
  309. message: {
  310. cmd: 'isMaximized'
  311. }
  312. })
  313. }
  314. // Setters
  315. /**
  316. * Updates the window resizable flag.
  317. *
  318. * @param resizable
  319. * @returns A promise indicating the success or failure of the operation.
  320. */
  321. async setResizable(resizable: boolean): Promise<void> {
  322. return invokeTauriCommand({
  323. __tauriModule: 'Window',
  324. message: {
  325. cmd: 'setResizable',
  326. data: resizable
  327. }
  328. })
  329. }
  330. /**
  331. * Sets the window title.
  332. *
  333. * @param title The new title
  334. * @returns A promise indicating the success or failure of the operation.
  335. */
  336. async setTitle(title: string): Promise<void> {
  337. return invokeTauriCommand({
  338. __tauriModule: 'Window',
  339. message: {
  340. cmd: 'setTitle',
  341. data: title
  342. }
  343. })
  344. }
  345. /**
  346. * Maximizes the window.
  347. *
  348. * @returns A promise indicating the success or failure of the operation.
  349. */
  350. async maximize(): Promise<void> {
  351. return invokeTauriCommand({
  352. __tauriModule: 'Window',
  353. message: {
  354. cmd: 'maximize'
  355. }
  356. })
  357. }
  358. /**
  359. * Unmaximizes the window.
  360. *
  361. * @returns A promise indicating the success or failure of the operation.
  362. */
  363. async unmaximize(): Promise<void> {
  364. return invokeTauriCommand({
  365. __tauriModule: 'Window',
  366. message: {
  367. cmd: 'unmaximize'
  368. }
  369. })
  370. }
  371. /**
  372. * Minimizes the window.
  373. *
  374. * @returns A promise indicating the success or failure of the operation.
  375. */
  376. async minimize(): Promise<void> {
  377. return invokeTauriCommand({
  378. __tauriModule: 'Window',
  379. message: {
  380. cmd: 'minimize'
  381. }
  382. })
  383. }
  384. /**
  385. * Unminimizes the window.
  386. *
  387. * @returns A promise indicating the success or failure of the operation.
  388. */
  389. async unminimize(): Promise<void> {
  390. return invokeTauriCommand({
  391. __tauriModule: 'Window',
  392. message: {
  393. cmd: 'unminimize'
  394. }
  395. })
  396. }
  397. /**
  398. * Sets the window visibility to true.
  399. *
  400. * @returns A promise indicating the success or failure of the operation.
  401. */
  402. async show(): Promise<void> {
  403. return invokeTauriCommand({
  404. __tauriModule: 'Window',
  405. message: {
  406. cmd: 'show'
  407. }
  408. })
  409. }
  410. /**
  411. * Sets the window visibility to false.
  412. *
  413. * @returns A promise indicating the success or failure of the operation.
  414. */
  415. async hide(): Promise<void> {
  416. return invokeTauriCommand({
  417. __tauriModule: 'Window',
  418. message: {
  419. cmd: 'hide'
  420. }
  421. })
  422. }
  423. /**
  424. * Closes the window.
  425. *
  426. * @returns A promise indicating the success or failure of the operation.
  427. */
  428. async close(): Promise<void> {
  429. return invokeTauriCommand({
  430. __tauriModule: 'Window',
  431. message: {
  432. cmd: 'close'
  433. }
  434. })
  435. }
  436. /**
  437. * Whether the window should have borders and bars.
  438. *
  439. * @param decorations Whether the window should have borders and bars.
  440. * @returns A promise indicating the success or failure of the operation.
  441. */
  442. async setDecorations(decorations: boolean): Promise<void> {
  443. return invokeTauriCommand({
  444. __tauriModule: 'Window',
  445. message: {
  446. cmd: 'setDecorations',
  447. data: decorations
  448. }
  449. })
  450. }
  451. /**
  452. * Whether the window should always be on top of other windows.
  453. *
  454. * @param alwaysOnTop Whether the window should always be on top of other windows or not.
  455. * @returns A promise indicating the success or failure of the operation.
  456. */
  457. async setAlwaysOnTop(alwaysOnTop: boolean): Promise<void> {
  458. return invokeTauriCommand({
  459. __tauriModule: 'Window',
  460. message: {
  461. cmd: 'setAlwaysOnTop',
  462. data: alwaysOnTop
  463. }
  464. })
  465. }
  466. /**
  467. * Resizes the window.
  468. *
  469. * @param size The logical or physical size.
  470. * @returns A promise indicating the success or failure of the operation.
  471. */
  472. async setSize(size: LogicalSize | PhysicalSize): Promise<void> {
  473. return invokeTauriCommand({
  474. __tauriModule: 'Window',
  475. message: {
  476. cmd: 'setSize',
  477. data: {
  478. type: size.type,
  479. data: {
  480. width: size.width,
  481. height: size.height
  482. }
  483. }
  484. }
  485. })
  486. }
  487. /**
  488. * Sets the window min size.
  489. *
  490. * @param size The logical or physical size.
  491. * @returns A promise indicating the success or failure of the operation.
  492. */
  493. async setMinSize(
  494. size: LogicalSize | PhysicalSize | undefined
  495. ): Promise<void> {
  496. return invokeTauriCommand({
  497. __tauriModule: 'Window',
  498. message: {
  499. cmd: 'setMinSize',
  500. data: size
  501. ? {
  502. type: size.type,
  503. data: {
  504. width: size.width,
  505. height: size.height
  506. }
  507. }
  508. : null
  509. }
  510. })
  511. }
  512. /**
  513. * Sets the window max size.
  514. *
  515. * @param size The logical or physical size.
  516. * @returns A promise indicating the success or failure of the operation.
  517. */
  518. async setMaxSize(
  519. size: LogicalSize | PhysicalSize | undefined
  520. ): Promise<void> {
  521. return invokeTauriCommand({
  522. __tauriModule: 'Window',
  523. message: {
  524. cmd: 'setMaxSize',
  525. data: size
  526. ? {
  527. type: size.type,
  528. data: {
  529. width: size.width,
  530. height: size.height
  531. }
  532. }
  533. : null
  534. }
  535. })
  536. }
  537. /**
  538. * Sets the window position.
  539. *
  540. * @param position The new position, in logical or physical pixels.
  541. * @returns A promise indicating the success or failure of the operation.
  542. */
  543. async setPosition(
  544. position: LogicalPosition | PhysicalPosition
  545. ): Promise<void> {
  546. return invokeTauriCommand({
  547. __tauriModule: 'Window',
  548. message: {
  549. cmd: 'setPosition',
  550. data: {
  551. type: position.type,
  552. data: {
  553. x: position.x,
  554. y: position.y
  555. }
  556. }
  557. }
  558. })
  559. }
  560. /**
  561. * Sets the window fullscreen state.
  562. *
  563. * @param fullscreen Whether the window should go to fullscreen or not.
  564. * @returns A promise indicating the success or failure of the operation.
  565. */
  566. async setFullscreen(fullscreen: boolean): Promise<void> {
  567. return invokeTauriCommand({
  568. __tauriModule: 'Window',
  569. message: {
  570. cmd: 'setFullscreen',
  571. data: fullscreen
  572. }
  573. })
  574. }
  575. /**
  576. * Sets the window icon.
  577. *
  578. * @param icon Icon bytes or path to the icon file.
  579. * @returns A promise indicating the success or failure of the operation.
  580. */
  581. async setIcon(icon: string | number[]): Promise<void> {
  582. return invokeTauriCommand({
  583. __tauriModule: 'Window',
  584. message: {
  585. cmd: 'setIcon',
  586. data: {
  587. icon
  588. }
  589. }
  590. })
  591. }
  592. /**
  593. * Starts dragging the window.
  594. *
  595. * @return A promise indicating the success or failure of the operation.
  596. */
  597. async startDragging(): Promise<void> {
  598. return invokeTauriCommand({
  599. __tauriModule: 'Window',
  600. message: {
  601. cmd: 'startDragging'
  602. }
  603. })
  604. }
  605. }
  606. /** The manager for the current window. Allows you to manipulate the window object. */
  607. const appWindow = new WindowManager()
  608. /** Configuration for the window to create. */
  609. export interface WindowOptions {
  610. /**
  611. * Remote URL or local file path to open, e.g. `https://github.com/tauri-apps` or `path/to/page.html`.
  612. */
  613. url?: string
  614. /** The initial vertical position. Only applies if `y` is also set. */
  615. x?: number
  616. /** The initial horizontal position. Only applies if `x` is also set. */
  617. y?: number
  618. /** The initial width. */
  619. width?: number
  620. /** The initial height. */
  621. height?: number
  622. /** The minimum width. Only applies if `minHeight` is also set. */
  623. minWidth?: number
  624. /** The minimum height. Only applies if `minWidth` is also set. */
  625. minHeight?: number
  626. /** The maximum width. Only applies if `maxHeight` is also set. */
  627. maxWidth?: number
  628. /** The maximum height. Only applies if `maxWidth` is also set. */
  629. maxHeight?: number
  630. /** Whether the window is resizable or not. */
  631. resizable?: boolean
  632. /** Window title. */
  633. title?: string
  634. /** Whether the window is in fullscreen mode or not. */
  635. fullscreen?: boolean
  636. /** Whether the window is transparent or not. */
  637. transparent?: boolean
  638. /** Whether the window should be maximized upon creation or not. */
  639. maximized?: boolean
  640. /** Whether the window should be immediately visible upon creation or not. */
  641. visible?: boolean
  642. /** Whether the window should have borders and bars or not. */
  643. decorations?: boolean
  644. /** Whether the window should always be on top of other windows or not. */
  645. alwaysOnTop?: boolean
  646. }
  647. /**
  648. * Returns the monitor on which the window currently resides.
  649. * Returns `null` if current monitor can't be detected.
  650. */
  651. async function currentMonitor(): Promise<Monitor | null> {
  652. return invokeTauriCommand({
  653. __tauriModule: 'Window',
  654. message: {
  655. cmd: 'currentMonitor'
  656. }
  657. })
  658. }
  659. /**
  660. * Returns the primary monitor of the system.
  661. * Returns `null` if it can't identify any monitor as a primary one.
  662. */
  663. async function primaryMonitor(): Promise<Monitor | null> {
  664. return invokeTauriCommand({
  665. __tauriModule: 'Window',
  666. message: {
  667. cmd: 'primaryMonitor'
  668. }
  669. })
  670. }
  671. /** Returns the list of all the monitors available on the system. */
  672. async function availableMonitors(): Promise<Monitor[]> {
  673. return invokeTauriCommand({
  674. __tauriModule: 'Window',
  675. message: {
  676. cmd: 'availableMonitors'
  677. }
  678. })
  679. }
  680. export {
  681. WebviewWindow,
  682. WebviewWindowHandle,
  683. getCurrent,
  684. getAll,
  685. appWindow,
  686. LogicalSize,
  687. PhysicalSize,
  688. LogicalPosition,
  689. PhysicalPosition,
  690. currentMonitor,
  691. primaryMonitor,
  692. availableMonitors
  693. }
  694. export type {
  695. Monitor
  696. }