├── .github ├── splash.png ├── fix-window-size.md ├── CODEOWNERS ├── FUNDING.yml ├── workflows │ ├── audit.yml │ ├── covector-status.yml │ ├── covector-comment-on-fork.yml │ ├── covector-version-or-publish.yml │ └── ci.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── PULL_REQUEST_TEMPLATE.md └── CODE_OF_CONDUCT.md ├── examples ├── icon.ico ├── icon.png ├── monitor_list.rs ├── video_modes.rs ├── request_redraw.rs ├── request_redraw_threaded.rs ├── reopen_event.rs ├── timer.rs ├── minimize.rs ├── window.rs ├── resizable.rs ├── decorations.rs ├── multiwindow.rs ├── custom_events.rs ├── theme.rs ├── set_ime_position.rs ├── window_run_return.rs ├── mouse_wheel.rs ├── transparent.rs ├── README.md ├── cursor_grab.rs ├── window_icon.rs ├── cursor.rs ├── drag_window.rs ├── parentwindow.rs ├── min_max_size.rs ├── progress_bar.rs ├── handling_close.rs ├── overlay.rs ├── control_flow.rs ├── fullscreen.rs ├── window_debug.rs └── multithreaded.rs ├── audits └── Radically_Open_Security-v1-report.pdf ├── src ├── platform │ ├── linux.rs │ ├── mod.rs │ ├── android.rs │ └── run_return.rs ├── platform_impl │ ├── linux │ │ ├── wayland │ │ │ ├── mod.rs │ │ │ └── header.rs │ │ ├── x11 │ │ │ ├── mod.rs │ │ │ ├── ffi.rs │ │ │ └── xdisplay.rs │ │ ├── icon.rs │ │ ├── mod.rs │ │ ├── monitor.rs │ │ ├── util.rs │ │ ├── device.rs │ │ └── taskbar.rs │ ├── ios │ │ ├── keycode.rs │ │ ├── badge.rs │ │ └── mod.rs │ ├── macos │ │ ├── badge.rs │ │ ├── icon.rs │ │ ├── mod.rs │ │ ├── dock.rs │ │ ├── util │ │ │ └── mod.rs │ │ ├── app.rs │ │ └── progress_bar.rs │ ├── mod.rs │ └── windows │ │ ├── minimal_ime.rs │ │ ├── dpi.rs │ │ ├── mod.rs │ │ ├── icon.rs │ │ ├── drop_handler.rs │ │ └── raw_input.rs ├── error.rs ├── monitor.rs └── icon.rs ├── renovate.json ├── .gitignore ├── tao-macros ├── examples │ ├── generate_package_name.rs │ └── android_fn.rs ├── Cargo.toml ├── LICENSE.spdx ├── LICENSE-MIT └── CHANGELOG.md ├── .gitmodules ├── tests ├── sync_object.rs ├── send_objects.rs └── serde_objects.rs ├── rustfmt.toml ├── .gitattributes ├── LICENSE.spdx ├── .changes ├── readme.md └── config.json ├── README.md └── Cargo.toml /.github/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tauri-apps/tao/HEAD/.github/splash.png -------------------------------------------------------------------------------- /examples/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tauri-apps/tao/HEAD/examples/icon.ico -------------------------------------------------------------------------------- /examples/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tauri-apps/tao/HEAD/examples/icon.png -------------------------------------------------------------------------------- /audits/Radically_Open_Security-v1-report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tauri-apps/tao/HEAD/audits/Radically_Open_Security-v1-report.pdf -------------------------------------------------------------------------------- /.github/fix-window-size.md: -------------------------------------------------------------------------------- 1 | --- 2 | "tao": patch 3 | --- 4 | 5 | Properly check target monitor from window initial position to determine its size with the proper scale factor on macOS. 6 | -------------------------------------------------------------------------------- /src/platform/linux.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #![cfg(target_os = "linux")] 6 | -------------------------------------------------------------------------------- /src/platform_impl/linux/wayland/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | pub mod header; 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:recommended"], 3 | "rangeStrategy": "replace", 4 | "packageRules": [ 5 | { 6 | "semanticCommitType": "chore", 7 | "matchPackageNames": ["*"] 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Current WG Code Sub Teams: 2 | # @tauri-apps/wg-webview 3 | # @tauri-apps/wg-devops 4 | 5 | # Order is important; the last matching pattern takes the most precedence. 6 | * @tauri-apps/wg-webview 7 | 8 | .github @tauri-apps/wg-devops 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2021 The winit contributors 2 | # Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | target/ 6 | rls/ 7 | .vscode/ 8 | *~ 9 | *.ts 10 | *.js 11 | #*# 12 | .DS_Store -------------------------------------------------------------------------------- /src/platform_impl/linux/x11/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | pub mod ffi; 6 | pub mod xdisplay; 7 | 8 | pub use xdisplay::XConnection; 9 | -------------------------------------------------------------------------------- /tao-macros/examples/generate_package_name.rs: -------------------------------------------------------------------------------- 1 | use tao_macros::generate_package_name; 2 | 3 | pub const PACKAGE: &str = generate_package_name!(com_example, tao_app); 4 | 5 | fn main() {} 6 | 7 | #[test] 8 | fn it_works() { 9 | assert_eq!(PACKAGE, "com/example/tao_app") 10 | } 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2021 The winit contributors 2 | # Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | [submodule "deps/apk-builder"] 6 | path = deps/apk-builder 7 | url = https://github.com/rust-windowing/android-rs-glue 8 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # 4 | patreon: # 5 | open_collective: tauri 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: # Replace with a single custom sponsorship URL 9 | -------------------------------------------------------------------------------- /src/platform_impl/linux/x11/ffi.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | pub use x11_dl::{ 6 | error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*, xrandr::*, 7 | xrender::*, 8 | }; 9 | -------------------------------------------------------------------------------- /tests/sync_object.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #[allow(dead_code)] 6 | fn needs_sync() {} 7 | 8 | #[test] 9 | fn window_sync() { 10 | // ensures that `Window` implements `Sync` 11 | needs_sync::(); 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Audit 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 0 * * *' 7 | push: 8 | paths: 9 | - "**/Cargo.lock" 10 | - "**/Cargo.toml" 11 | 12 | jobs: 13 | audit-rust: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: rustsec/audit-check@v1 18 | with: 19 | token: ${{ secrets.GITHUB_TOKEN }} 20 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 100 2 | hard_tabs = false 3 | tab_spaces = 2 4 | newline_style = "Unix" 5 | use_small_heuristics = "Default" 6 | reorder_imports = true 7 | reorder_modules = true 8 | remove_nested_parens = true 9 | edition = "2018" 10 | merge_derives = true 11 | use_try_shorthand = false 12 | use_field_init_shorthand = false 13 | force_explicit_abi = true 14 | imports_granularity = "Crate" 15 | #license_template_path = ".license_template" 16 | -------------------------------------------------------------------------------- /src/platform_impl/ios/keycode.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use crate::keyboard::{KeyCode, NativeKeyCode}; 6 | 7 | pub fn keycode_to_scancode(_code: KeyCode) -> Option { 8 | None 9 | } 10 | 11 | pub fn keycode_from_scancode(_scancode: u32) -> KeyCode { 12 | KeyCode::Unidentified(NativeKeyCode::Unidentified) 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/covector-status.yml: -------------------------------------------------------------------------------- 1 | name: covector status 2 | on: [pull_request] 3 | 4 | jobs: 5 | covector: 6 | runs-on: ubuntu-latest 7 | 8 | steps: 9 | - uses: actions/checkout@v4 10 | with: 11 | fetch-depth: 0 12 | - name: covector status 13 | uses: jbolda/covector/packages/action@covector-v0 14 | with: 15 | command: "status" 16 | token: ${{ secrets.GITHUB_TOKEN }} 17 | comment: true 18 | -------------------------------------------------------------------------------- /src/platform_impl/macos/badge.rs: -------------------------------------------------------------------------------- 1 | use super::ffi::id; 2 | use objc2_app_kit::NSApp; 3 | use objc2_foundation::{MainThreadMarker, NSString}; 4 | 5 | pub fn set_badge_label(label: Option) { 6 | // SAFETY: TODO 7 | let mtm = unsafe { MainThreadMarker::new_unchecked() }; 8 | unsafe { 9 | let label = label.map(|label| NSString::from_str(&label)); 10 | let dock_tile: id = msg_send![&NSApp(mtm), dockTile]; 11 | let _: () = msg_send![dock_tile, setBadgeLabel: label.as_deref()]; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/platform_impl/macos/icon.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use crate::icon::{BadIcon, RgbaIcon}; 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct PlatformIcon(RgbaIcon); 9 | 10 | impl PlatformIcon { 11 | pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { 12 | Ok(PlatformIcon(RgbaIcon::from_rgba(rgba, width, height)?)) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/platform_impl/ios/badge.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | 3 | use objc2::{ 4 | msg_send, 5 | runtime::{AnyClass, AnyObject}, 6 | }; 7 | 8 | pub fn set_badge_count(count: i32) { 9 | unsafe { 10 | let ui_application = AnyClass::get(CStr::from_bytes_with_nul(b"UIApplication\0").unwrap()) 11 | .expect("Failed to get UIApplication class"); 12 | let app: *mut AnyObject = msg_send![ui_application, sharedApplication]; 13 | let _: () = msg_send![app, setApplicationIconBadgeNumber:count]; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/monitor_list.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use tao::{event_loop::EventLoop, window::WindowBuilder}; 6 | 7 | fn main() { 8 | env_logger::init(); 9 | let event_loop = EventLoop::new(); 10 | let window = WindowBuilder::new().build(&event_loop).unwrap(); 11 | 12 | dbg!(window.available_monitors().collect::>()); 13 | dbg!(window.primary_monitor()); 14 | } 15 | -------------------------------------------------------------------------------- /tao-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tao-macros" 3 | description = "Proc macros for tao" 4 | version = "0.1.3" 5 | edition = "2021" 6 | authors = [ "Tauri Programme within The Commons Conservancy" ] 7 | rust-version = "1.74" 8 | license = "MIT OR Apache-2.0" 9 | readme = "../README.md" 10 | repository = "https://github.com/tauri-apps/tao" 11 | documentation = "https://docs.rs/tao-macros" 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [features] 17 | default = [ ] 18 | 19 | [dependencies] 20 | proc-macro2 = "1" 21 | quote = "1" 22 | syn = { version = "2", features = [ "full" ] } 23 | -------------------------------------------------------------------------------- /examples/video_modes.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use tao::event_loop::EventLoop; 6 | 7 | #[allow(clippy::single_match)] 8 | fn main() { 9 | env_logger::init(); 10 | let event_loop = EventLoop::new(); 11 | let monitor = match event_loop.primary_monitor() { 12 | Some(monitor) => monitor, 13 | None => { 14 | println!("No primary monitor detected."); 15 | return; 16 | } 17 | }; 18 | 19 | println!("Listing available video modes:"); 20 | 21 | for mode in monitor.video_modes() { 22 | println!("{mode}"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for tao 4 | title: '' 5 | labels: 'type: feature request' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve tao 4 | title: '' 5 | labels: 'type: bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Steps To Reproduce** 14 | Steps to reproduce the behavior. It **must** use tao directly and not tauri. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Platform and Versions (please complete the following information):** 23 | OS: 24 | Rustc: 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | 29 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2021 The winit contributors 2 | # Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | 6 | # Auto detect text files and perform LF normalization 7 | * text=auto 8 | 9 | # Custom for Visual Studio 10 | *.cs diff=csharp 11 | *.sln merge=union 12 | *.csproj merge=union 13 | *.vbproj merge=union 14 | *.fsproj merge=union 15 | *.dbproj merge=union 16 | 17 | # Standard to msysgit 18 | *.doc diff=astextplain 19 | *.DOC diff=astextplain 20 | *.docx diff=astextplain 21 | *.DOCX diff=astextplain 22 | *.dot diff=astextplain 23 | *.DOT diff=astextplain 24 | *.pdf diff=astextplain 25 | *.PDF diff=astextplain 26 | *.rtf diff=astextplain 27 | *.RTF diff=astextplain 28 | 29 | /CHANGELOG.md merge=union 30 | -------------------------------------------------------------------------------- /src/platform/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | //! Contains traits with platform-specific methods in them. 6 | //! 7 | //! Contains the follow OS-specific modules: 8 | //! 9 | //! - `android` 10 | //! - `ios` 11 | //! - `macos` 12 | //! - `unix` 13 | //! - `linux` 14 | //! - `windows` 15 | //! 16 | //! And the following platform-specific module: 17 | //! 18 | //! - `run_return` (available on `windows`, `unix`, `macos`, and `android`) 19 | //! 20 | //! However only the module corresponding to the platform you're compiling to will be available. 21 | 22 | pub mod android; 23 | pub mod ios; 24 | pub mod linux; 25 | pub mod macos; 26 | pub mod run_return; 27 | pub mod unix; 28 | pub mod windows; 29 | -------------------------------------------------------------------------------- /LICENSE.spdx: -------------------------------------------------------------------------------- 1 | SPDXVersion: SPDX-2.1 2 | DataLicense: CC0-1.0 3 | PackageName: tao 4 | DataFormat: SPDXRef-1 5 | PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy 6 | PackageHomePage: https://tauri.app 7 | PackageLicenseDeclared: Apache-2.0 8 | PackageCopyrightText: 2021-2023, The Tauri Programme in the Commons Conservancy 9 | PackageSummary: Tao is the official, rust-based window manager for Wry. 10 | 11 | PackageComment: The package includes the following libraries; see 12 | Relationship information. 13 | 14 | Created: 2020-05-20T09:00:00Z 15 | PackageDownloadLocation: git://github.com/tauri-apps/tao 16 | PackageDownloadLocation: git+https://github.com/tauri-apps/tao.git 17 | PackageDownloadLocation: git+ssh://github.com/tauri-apps/tao.git 18 | Creator: Person: Daniel Thompson-Yvetot 19 | -------------------------------------------------------------------------------- /tests/send_objects.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #[allow(dead_code)] 6 | fn needs_send() {} 7 | 8 | #[test] 9 | fn event_loop_proxy_send() { 10 | #[allow(dead_code)] 11 | fn is_send() { 12 | // ensures that `EventLoopProxy` implements `Send` 13 | needs_send::>(); 14 | } 15 | } 16 | 17 | #[test] 18 | fn window_send() { 19 | // ensures that `Window` implements `Send` 20 | needs_send::(); 21 | } 22 | 23 | #[test] 24 | fn ids_send() { 25 | // ensures that the various `..Id` types implement `Send` 26 | needs_send::(); 27 | needs_send::(); 28 | needs_send::(); 29 | } 30 | -------------------------------------------------------------------------------- /tao-macros/LICENSE.spdx: -------------------------------------------------------------------------------- 1 | SPDXVersion: SPDX-2.1 2 | DataLicense: CC0-1.0 3 | PackageName: tao-macros 4 | DataFormat: SPDXRef-1 5 | PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy 6 | PackageHomePage: https://tauri.app 7 | PackageLicenseDeclared: Apache-2.0 8 | PackageLicenseDeclared: MIT 9 | PackageCopyrightText: 2020-2023, The Tauri Programme in the Commons Conservancy 10 | PackageSummary: tao-macros is part of Tao, the official, rust-based window manager for Wry. 11 | 12 | PackageComment: The package includes the following libraries; see 13 | Relationship information. 14 | 15 | Created: 2020-05-20T09:00:00Z 16 | PackageDownloadLocation: git://github.com/tauri-apps/tao 17 | PackageDownloadLocation: git+https://github.com/tauri-apps/tao.git 18 | PackageDownloadLocation: git+ssh://github.com/tauri-apps/tao.git 19 | Creator: Person: Daniel Thompson-Yvetot 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /.github/workflows/covector-comment-on-fork.yml: -------------------------------------------------------------------------------- 1 | name: covector comment 2 | on: 3 | workflow_run: 4 | workflows: [covector status] # the `name` of the workflow run on `pull_request` running `status` with `comment: true` 5 | types: 6 | - completed 7 | 8 | # note all other permissions are set to none if not specified 9 | # and these set the permissions for `secrets.GITHUB_TOKEN` 10 | permissions: 11 | # to read the action artifacts on `covector status` workflows 12 | actions: read 13 | # to write the comment 14 | pull-requests: write 15 | 16 | jobs: 17 | download: 18 | runs-on: ubuntu-latest 19 | if: github.event.workflow_run.conclusion == 'success' && 20 | (github.event.workflow_run.head_repository.full_name != github.repository || github.actor == 'dependabot[bot]') 21 | steps: 22 | - name: covector status 23 | uses: jbolda/covector/packages/action@covector-v0 24 | with: 25 | token: ${{ secrets.GITHUB_TOKEN }} 26 | command: "status" 27 | -------------------------------------------------------------------------------- /src/platform_impl/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #[cfg(target_os = "windows")] 6 | #[path = "windows/mod.rs"] 7 | mod platform; 8 | #[cfg(any( 9 | target_os = "linux", 10 | target_os = "dragonfly", 11 | target_os = "freebsd", 12 | target_os = "netbsd", 13 | target_os = "openbsd" 14 | ))] 15 | #[path = "linux/mod.rs"] 16 | mod platform; 17 | #[cfg(target_os = "macos")] 18 | #[path = "macos/mod.rs"] 19 | mod platform; 20 | #[cfg(target_os = "android")] 21 | #[path = "android/mod.rs"] 22 | mod platform; 23 | #[cfg(target_os = "ios")] 24 | #[path = "ios/mod.rs"] 25 | mod platform; 26 | 27 | pub use platform::*; 28 | 29 | #[cfg(all( 30 | not(target_os = "ios"), 31 | not(target_os = "windows"), 32 | not(target_os = "linux"), 33 | not(target_os = "macos"), 34 | not(target_os = "android"), 35 | not(target_os = "dragonfly"), 36 | not(target_os = "freebsd"), 37 | not(target_os = "netbsd"), 38 | not(target_os = "openbsd"), 39 | ))] 40 | compile_error!("The platform you're compiling for is not supported by tao"); 41 | -------------------------------------------------------------------------------- /tao-macros/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 - Present Tauri Apps Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tao-macros/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## \[0.1.3] 4 | 5 | - [`4cd53415`](https://github.com/tauri-apps/tao/commit/4cd534151a2d7a14ade906f960ec02655a91feae) ([#964](https://github.com/tauri-apps/tao/pull/964) by [@lucasfernog](https://github.com/tauri-apps/tao/../../lucasfernog)) Allow Android domain names to include `_1` as escaped `_` characters - required because `_` is the separator for domain parts. 6 | 7 | ## \[0.1.2] 8 | 9 | - [`b7758314`](https://github.com/tauri-apps/tao/commit/b7758314abf8c6916c865d9b31eea5bd17b2fe16)([#780](https://github.com/tauri-apps/tao/pull/780)) Added support to lifetime parameters on `android_fn`. 10 | 11 | ## \[0.1.1] 12 | 13 | - Fix passing empty array for args in `android_fn!` macro 14 | - [666235b0](https://github.com/tauri-apps/tao/commit/666235b0e1fce0ca286c194aa75422021a6f0c4b) fix(tao-macros): fix using android_fn! with 0 jni args ([#688](https://github.com/tauri-apps/tao/pull/688)) on 2023-02-07 15 | 16 | ## \[0.1.0] 17 | 18 | - Publish tao-macro v0.1.0 19 | - [3cd851d1](https://github.com/tauri-apps/tao/commit/3cd851d14126c305964b957eeb4f9ed0011d96cb) Revert "Publish New Versions" ([#663](https://github.com/tauri-apps/tao/pull/663)) on 2023-01-09 20 | -------------------------------------------------------------------------------- /src/platform_impl/linux/wayland/header.rs: -------------------------------------------------------------------------------- 1 | use gtk::{prelude::*, ApplicationWindow, EventBox, HeaderBar}; 2 | 3 | pub struct WlHeader; 4 | 5 | impl WlHeader { 6 | pub fn setup(window: &ApplicationWindow, title: &str) { 7 | let header = HeaderBar::builder() 8 | .show_close_button(true) 9 | .decoration_layout("menu:minimize,maximize,close") 10 | .title(title) 11 | .build(); 12 | 13 | let event_box = EventBox::new(); 14 | event_box.set_above_child(true); 15 | event_box.set_visible(true); 16 | event_box.set_can_focus(false); 17 | event_box.add(&header); 18 | 19 | window.set_titlebar(Some(&event_box)); 20 | Self::connect_resize_window(&header, window); 21 | } 22 | 23 | fn connect_resize_window(header: &HeaderBar, window: &ApplicationWindow) { 24 | let header_weak = header.downgrade(); 25 | window.connect_resizable_notify(move |window| { 26 | if let Some(header) = header_weak.upgrade() { 27 | let is_resizable = window.is_resizable(); 28 | header.set_decoration_layout(if !is_resizable { 29 | Some("menu:minimize,close") 30 | } else { 31 | Some("menu:minimize,maximize,close") 32 | }); 33 | } 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/request_redraw.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use tao::{ 6 | event::{ElementState, Event, WindowEvent}, 7 | event_loop::{ControlFlow, EventLoop}, 8 | window::WindowBuilder, 9 | }; 10 | 11 | #[allow(clippy::single_match)] 12 | fn main() { 13 | env_logger::init(); 14 | let event_loop = EventLoop::new(); 15 | 16 | let window = WindowBuilder::new() 17 | .with_title("A fantastic window!") 18 | .build(&event_loop) 19 | .unwrap(); 20 | 21 | event_loop.run(move |event, _, control_flow| { 22 | println!("{event:?}"); 23 | 24 | *control_flow = ControlFlow::Wait; 25 | 26 | match event { 27 | Event::WindowEvent { event, .. } => match event { 28 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 29 | WindowEvent::MouseInput { 30 | state: ElementState::Released, 31 | .. 32 | } => { 33 | window.request_redraw(); 34 | } 35 | _ => (), 36 | }, 37 | Event::RedrawRequested(_) => { 38 | println!("\nredrawing!\n"); 39 | } 40 | _ => (), 41 | } 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /examples/request_redraw_threaded.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use std::{thread, time}; 6 | 7 | use tao::{ 8 | event::{Event, WindowEvent}, 9 | event_loop::{ControlFlow, EventLoop}, 10 | window::WindowBuilder, 11 | }; 12 | 13 | #[allow(clippy::single_match)] 14 | #[allow(clippy::collapsible_match)] 15 | fn main() { 16 | env_logger::init(); 17 | let event_loop = EventLoop::new(); 18 | 19 | let window = WindowBuilder::new() 20 | .with_title("A fantastic window!") 21 | .build(&event_loop) 22 | .unwrap(); 23 | 24 | thread::spawn(move || loop { 25 | thread::sleep(time::Duration::from_secs(1)); 26 | window.request_redraw(); 27 | }); 28 | 29 | event_loop.run(move |event, _, control_flow| { 30 | println!("{event:?}"); 31 | 32 | *control_flow = ControlFlow::Wait; 33 | 34 | match event { 35 | Event::WindowEvent { event, .. } => match event { 36 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 37 | _ => (), 38 | }, 39 | Event::RedrawRequested(_) => { 40 | println!("\nredrawing!\n"); 41 | } 42 | _ => (), 43 | } 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /examples/reopen_event.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use tao::{ 6 | event::{Event, WindowEvent}, 7 | event_loop::{ControlFlow, EventLoop}, 8 | window::Window, 9 | }; 10 | 11 | #[allow(clippy::single_match)] 12 | fn main() { 13 | let event_loop = EventLoop::new(); 14 | 15 | let mut window = Some(Window::new(&event_loop).unwrap()); 16 | 17 | event_loop.run(move |event, event_loop, control_flow| { 18 | *control_flow = ControlFlow::Wait; 19 | 20 | match event { 21 | Event::WindowEvent { 22 | event: WindowEvent::CloseRequested, 23 | .. 24 | } => { 25 | // drop the window 26 | window = None; 27 | } 28 | Event::Reopen { 29 | has_visible_windows, 30 | .. 31 | } => { 32 | println!("on reopen, has visible windows: {has_visible_windows}"); 33 | if !has_visible_windows { 34 | window = Some(Window::new(event_loop).unwrap()) 35 | } 36 | } 37 | Event::MainEventsCleared => { 38 | if let Some(w) = &window { 39 | w.request_redraw(); 40 | } 41 | } 42 | _ => (), 43 | } 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /tests/serde_objects.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #![cfg(feature = "serde")] 6 | 7 | use serde::{Deserialize, Serialize}; 8 | use tao::{ 9 | dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, 10 | event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase}, 11 | keyboard::{Key, KeyCode, KeyLocation, ModifiersState}, 12 | window::CursorIcon, 13 | }; 14 | 15 | #[allow(dead_code)] 16 | fn needs_serde>() {} 17 | 18 | #[test] 19 | fn window_serde() { 20 | needs_serde::(); 21 | } 22 | 23 | #[test] 24 | fn events_serde() { 25 | needs_serde::(); 26 | needs_serde::(); 27 | needs_serde::(); 28 | needs_serde::(); 29 | needs_serde::(); 30 | needs_serde::(); 31 | needs_serde::(); 32 | needs_serde::(); 33 | } 34 | 35 | #[test] 36 | fn dpi_serde() { 37 | needs_serde::>(); 38 | needs_serde::>(); 39 | needs_serde::>(); 40 | needs_serde::>(); 41 | needs_serde::>(); 42 | } 43 | -------------------------------------------------------------------------------- /examples/timer.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use std::time::{Duration, Instant}; 6 | 7 | use tao::{ 8 | event::{Event, StartCause, WindowEvent}, 9 | event_loop::{ControlFlow, EventLoop}, 10 | window::WindowBuilder, 11 | }; 12 | 13 | #[allow(clippy::single_match)] 14 | fn main() { 15 | env_logger::init(); 16 | let event_loop = EventLoop::new(); 17 | 18 | let _window = WindowBuilder::new() 19 | .with_title("A fantastic window!") 20 | .build(&event_loop) 21 | .unwrap(); 22 | 23 | let timer_length = Duration::new(1, 0); 24 | 25 | event_loop.run(move |event, _, control_flow| { 26 | println!("{event:?}"); 27 | 28 | match event { 29 | Event::NewEvents(StartCause::Init) => { 30 | *control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length) 31 | } 32 | Event::NewEvents(StartCause::ResumeTimeReached { .. }) => { 33 | *control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length); 34 | println!("\nTimer\n"); 35 | } 36 | Event::WindowEvent { 37 | event: WindowEvent::CloseRequested, 38 | .. 39 | } => *control_flow = ControlFlow::Exit, 40 | _ => (), 41 | } 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /src/platform_impl/linux/icon.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use gtk::gdk_pixbuf::{Colorspace, Pixbuf}; 6 | 7 | use crate::window::BadIcon; 8 | 9 | /// An icon used for the window titlebar, taskbar, etc. 10 | #[derive(Debug, Clone)] 11 | pub struct PlatformIcon { 12 | raw: Vec, 13 | width: i32, 14 | height: i32, 15 | row_stride: i32, 16 | } 17 | 18 | impl From for Pixbuf { 19 | fn from(icon: PlatformIcon) -> Self { 20 | Pixbuf::from_mut_slice( 21 | icon.raw, 22 | Colorspace::Rgb, 23 | true, 24 | 8, 25 | icon.width, 26 | icon.height, 27 | icon.row_stride, 28 | ) 29 | } 30 | } 31 | 32 | impl PlatformIcon { 33 | /// Creates an `Icon` from 32bpp RGBA data. 34 | /// 35 | /// The length of `rgba` must be divisible by 4, and `width * height` must equal 36 | /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error. 37 | pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { 38 | let row_stride = 39 | Pixbuf::calculate_rowstride(Colorspace::Rgb, true, 8, width as i32, height as i32); 40 | Ok(Self { 41 | raw: rgba, 42 | width: width as i32, 43 | height: height as i32, 44 | row_stride, 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. 6 | 7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 8 | 9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 10 | 11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 12 | 13 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 14 | -------------------------------------------------------------------------------- /examples/minimize.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | extern crate tao; 6 | 7 | use tao::{ 8 | event::{Event, WindowEvent}, 9 | event_loop::{ControlFlow, EventLoop}, 10 | keyboard::Key, 11 | window::WindowBuilder, 12 | }; 13 | 14 | #[allow(clippy::single_match)] 15 | fn main() { 16 | env_logger::init(); 17 | let event_loop = EventLoop::new(); 18 | 19 | let window = WindowBuilder::new() 20 | .with_title("A fantastic window!") 21 | .build(&event_loop) 22 | .unwrap(); 23 | 24 | event_loop.run(move |event, _, control_flow| { 25 | *control_flow = ControlFlow::Wait; 26 | 27 | match event { 28 | Event::WindowEvent { 29 | event: WindowEvent::CloseRequested, 30 | .. 31 | } => *control_flow = ControlFlow::Exit, 32 | 33 | // Keyboard input event to handle minimize via a hotkey 34 | Event::WindowEvent { 35 | event: WindowEvent::KeyboardInput { event, .. }, 36 | window_id, 37 | .. 38 | } if window_id == window.id() && Key::Character("m") == event.logical_key => { 39 | // Pressing the 'm' key will minimize the window 40 | // WARNING: Consider using `key_without_modifers()` if available on your platform. 41 | // See the `key_binding` example 42 | window.set_minimized(true); 43 | } 44 | _ => (), 45 | } 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /examples/window.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use tao::{ 6 | event::{Event, WindowEvent}, 7 | event_loop::{ControlFlow, EventLoop}, 8 | window::WindowBuilder, 9 | }; 10 | 11 | #[allow(clippy::single_match)] 12 | fn main() { 13 | let event_loop = EventLoop::new(); 14 | 15 | let mut window = Some( 16 | WindowBuilder::new() 17 | .with_title("A fantastic window!") 18 | .with_inner_size(tao::dpi::LogicalSize::new(300.0, 300.0)) 19 | .with_min_inner_size(tao::dpi::LogicalSize::new(200.0, 200.0)) 20 | .build(&event_loop) 21 | .unwrap(), 22 | ); 23 | 24 | event_loop.run(move |event, _, control_flow| { 25 | *control_flow = ControlFlow::Wait; 26 | println!("{event:?}"); 27 | 28 | match event { 29 | Event::WindowEvent { 30 | event: WindowEvent::CloseRequested, 31 | window_id: _, 32 | .. 33 | } => { 34 | // drop the window to fire the `Destroyed` event 35 | window = None; 36 | } 37 | Event::WindowEvent { 38 | event: WindowEvent::Destroyed, 39 | window_id: _, 40 | .. 41 | } => { 42 | *control_flow = ControlFlow::Exit; 43 | } 44 | Event::MainEventsCleared => { 45 | if let Some(w) = &window { 46 | w.request_redraw(); 47 | } 48 | } 49 | _ => (), 50 | } 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /examples/resizable.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use tao::{ 6 | dpi::LogicalSize, 7 | event::{ElementState, Event, KeyEvent, WindowEvent}, 8 | event_loop::{ControlFlow, EventLoop}, 9 | keyboard::KeyCode, 10 | window::WindowBuilder, 11 | }; 12 | 13 | #[allow(clippy::single_match)] 14 | fn main() { 15 | env_logger::init(); 16 | let event_loop = EventLoop::new(); 17 | 18 | let mut resizable = false; 19 | 20 | let window = WindowBuilder::new() 21 | .with_title("Hit space to toggle resizability.") 22 | .with_inner_size(LogicalSize::new(400.0, 200.0)) 23 | .with_resizable(resizable) 24 | .build(&event_loop) 25 | .unwrap(); 26 | 27 | event_loop.run(move |event, _, control_flow| { 28 | *control_flow = ControlFlow::Wait; 29 | 30 | match event { 31 | Event::WindowEvent { event, .. } => match event { 32 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 33 | WindowEvent::KeyboardInput { 34 | event: 35 | KeyEvent { 36 | physical_key: KeyCode::Space, 37 | state: ElementState::Released, 38 | .. 39 | }, 40 | .. 41 | } => { 42 | resizable = !resizable; 43 | println!("Resizable: {resizable}"); 44 | window.set_resizable(resizable); 45 | } 46 | _ => (), 47 | }, 48 | _ => (), 49 | }; 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /src/platform/android.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #![cfg(target_os = "android")] 6 | 7 | pub mod prelude { 8 | pub use crate::platform_impl::ndk_glue::*; 9 | pub use tao_macros::{android_fn, generate_package_name}; 10 | } 11 | use crate::{ 12 | event_loop::{EventLoop, EventLoopWindowTarget}, 13 | platform_impl::ndk_glue::Rect, 14 | window::{Window, WindowBuilder}, 15 | }; 16 | use ndk::configuration::Configuration; 17 | 18 | /// Additional methods on `EventLoop` that are specific to Android. 19 | pub trait EventLoopExtAndroid {} 20 | 21 | impl EventLoopExtAndroid for EventLoop {} 22 | 23 | /// Additional methods on `EventLoopWindowTarget` that are specific to Android. 24 | pub trait EventLoopWindowTargetExtAndroid {} 25 | 26 | /// Additional methods on `Window` that are specific to Android. 27 | pub trait WindowExtAndroid { 28 | fn content_rect(&self) -> Rect; 29 | 30 | fn config(&self) -> Configuration; 31 | } 32 | 33 | impl WindowExtAndroid for Window { 34 | fn content_rect(&self) -> Rect { 35 | self.window.content_rect() 36 | } 37 | 38 | fn config(&self) -> Configuration { 39 | self.window.config() 40 | } 41 | } 42 | 43 | impl EventLoopWindowTargetExtAndroid for EventLoopWindowTarget {} 44 | 45 | /// Additional methods on `WindowBuilder` that are specific to Android. 46 | pub trait WindowBuilderExtAndroid {} 47 | 48 | impl WindowBuilderExtAndroid for WindowBuilder {} 49 | -------------------------------------------------------------------------------- /examples/decorations.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use tao::{ 6 | dpi::LogicalSize, 7 | event::{ElementState, Event, KeyEvent, WindowEvent}, 8 | event_loop::{ControlFlow, EventLoop}, 9 | keyboard::KeyCode, 10 | window::WindowBuilder, 11 | }; 12 | 13 | #[allow(clippy::single_match)] 14 | fn main() { 15 | env_logger::init(); 16 | let event_loop = EventLoop::new(); 17 | 18 | let mut decorations = true; 19 | 20 | let window = WindowBuilder::new() 21 | .with_title("Hit space to toggle decorations.") 22 | .with_inner_size(LogicalSize::new(400.0, 200.0)) 23 | .with_decorations(decorations) 24 | .build(&event_loop) 25 | .unwrap(); 26 | 27 | event_loop.run(move |event, _, control_flow| { 28 | *control_flow = ControlFlow::Wait; 29 | 30 | match event { 31 | Event::WindowEvent { event, .. } => match event { 32 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 33 | WindowEvent::KeyboardInput { 34 | event: 35 | KeyEvent { 36 | physical_key: KeyCode::Space, 37 | state: ElementState::Released, 38 | .. 39 | }, 40 | .. 41 | } => { 42 | decorations = !decorations; 43 | println!("Decorations: {decorations}"); 44 | window.set_decorations(decorations); 45 | } 46 | _ => (), 47 | }, 48 | _ => (), 49 | }; 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /examples/multiwindow.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use std::collections::HashMap; 6 | 7 | use tao::{ 8 | event::{ElementState, Event, KeyEvent, WindowEvent}, 9 | event_loop::{ControlFlow, EventLoop}, 10 | window::Window, 11 | }; 12 | 13 | fn main() { 14 | env_logger::init(); 15 | let event_loop = EventLoop::new(); 16 | 17 | let mut windows = HashMap::new(); 18 | for _ in 0..3 { 19 | let window = Window::new(&event_loop).unwrap(); 20 | windows.insert(window.id(), window); 21 | } 22 | 23 | event_loop.run(move |event, event_loop, control_flow| { 24 | *control_flow = ControlFlow::Wait; 25 | 26 | if let Event::WindowEvent { 27 | event, window_id, .. 28 | } = event 29 | { 30 | match event { 31 | WindowEvent::CloseRequested => { 32 | println!("Window {window_id:?} has received the signal to close"); 33 | 34 | // This drops the window, causing it to close. 35 | windows.remove(&window_id); 36 | 37 | if windows.is_empty() { 38 | *control_flow = ControlFlow::Exit; 39 | } 40 | } 41 | WindowEvent::KeyboardInput { 42 | event: KeyEvent { 43 | state: ElementState::Pressed, 44 | .. 45 | }, 46 | .. 47 | } => { 48 | let window = Window::new(event_loop).unwrap(); 49 | windows.insert(window.id(), window); 50 | } 51 | _ => (), 52 | } 53 | } 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /examples/custom_events.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #[allow(clippy::single_match)] 6 | fn main() { 7 | env_logger::init(); 8 | use tao::{ 9 | event::{Event, WindowEvent}, 10 | event_loop::{ControlFlow, EventLoopBuilder}, 11 | window::WindowBuilder, 12 | }; 13 | 14 | #[derive(Debug, Clone, Copy)] 15 | enum CustomEvent { 16 | Timer, 17 | } 18 | 19 | let event_loop = EventLoopBuilder::::with_user_event().build(); 20 | 21 | let _window = WindowBuilder::new() 22 | .with_title("A fantastic window!") 23 | .build(&event_loop) 24 | .unwrap(); 25 | 26 | // `EventLoopProxy` allows you to dispatch custom events to the main Tao event 27 | // loop from any thread. 28 | let event_loop_proxy = event_loop.create_proxy(); 29 | 30 | std::thread::spawn(move || { 31 | // Wake up the `event_loop` once every second and dispatch a custom event 32 | // from a different thread. 33 | loop { 34 | std::thread::sleep(std::time::Duration::from_secs(1)); 35 | event_loop_proxy.send_event(CustomEvent::Timer).ok(); 36 | } 37 | }); 38 | 39 | event_loop.run(move |event, _, control_flow| { 40 | *control_flow = ControlFlow::Wait; 41 | 42 | match event { 43 | Event::UserEvent(event) => println!("user event: {event:?}"), 44 | Event::WindowEvent { 45 | event: WindowEvent::CloseRequested, 46 | .. 47 | } => *control_flow = ControlFlow::Exit, 48 | _ => (), 49 | } 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /examples/theme.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use tao::{event::KeyEvent, keyboard::KeyCode}; 6 | 7 | fn main() { 8 | use tao::{ 9 | event::{Event, WindowEvent}, 10 | event_loop::{ControlFlow, EventLoop}, 11 | window::{Theme, WindowBuilder}, 12 | }; 13 | 14 | env_logger::init(); 15 | let event_loop = EventLoop::new(); 16 | 17 | let window = WindowBuilder::new() 18 | .with_title("A fantastic window!") 19 | // .with_theme(Some(tao::window::Theme::Light)) 20 | .build(&event_loop) 21 | .unwrap(); 22 | 23 | println!("Initial theme: {:?}", window.theme()); 24 | println!("Press D for Dark Mode"); 25 | println!("Press L for Light Mode"); 26 | println!("Press A for Auto Mode"); 27 | 28 | event_loop.run(move |event, _, control_flow| { 29 | *control_flow = ControlFlow::Wait; 30 | 31 | if let Event::WindowEvent { event, .. } = event { 32 | match event { 33 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 34 | WindowEvent::KeyboardInput { 35 | event: KeyEvent { physical_key, .. }, 36 | .. 37 | } => match physical_key { 38 | KeyCode::KeyD => window.set_theme(Some(Theme::Dark)), 39 | KeyCode::KeyL => window.set_theme(Some(Theme::Light)), 40 | KeyCode::KeyA => window.set_theme(None), 41 | _ => {} 42 | }, 43 | WindowEvent::ThemeChanged(theme) => { 44 | println!("Theme is changed: {theme:?}") 45 | } 46 | _ => (), 47 | } 48 | } 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /examples/set_ime_position.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use tao::{ 6 | dpi::PhysicalPosition, 7 | event::{ElementState, Event, WindowEvent}, 8 | event_loop::{ControlFlow, EventLoop}, 9 | window::WindowBuilder, 10 | }; 11 | 12 | fn main() { 13 | env_logger::init(); 14 | let event_loop = EventLoop::new(); 15 | 16 | let window = WindowBuilder::new().build(&event_loop).unwrap(); 17 | window.set_title("A fantastic window!"); 18 | 19 | println!("Ime position will system default"); 20 | println!("Click to set ime position to cursor's"); 21 | 22 | let mut cursor_position = PhysicalPosition::new(0.0, 0.0); 23 | event_loop.run(move |event, _, control_flow| { 24 | *control_flow = ControlFlow::Wait; 25 | 26 | match event { 27 | Event::WindowEvent { 28 | event: WindowEvent::CursorMoved { position, .. }, 29 | .. 30 | } => { 31 | cursor_position = position; 32 | } 33 | Event::WindowEvent { 34 | event: 35 | WindowEvent::MouseInput { 36 | state: ElementState::Released, 37 | .. 38 | }, 39 | .. 40 | } => { 41 | println!( 42 | "Setting ime position to {}, {}", 43 | cursor_position.x, cursor_position.y 44 | ); 45 | window.set_ime_position(cursor_position); 46 | } 47 | Event::WindowEvent { 48 | event: WindowEvent::CloseRequested, 49 | .. 50 | } => { 51 | *control_flow = ControlFlow::Exit; 52 | } 53 | _ => (), 54 | } 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /.changes/readme.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ##### via https://github.com/jbolda/covector 4 | 5 | As you create PRs and make changes that require a version bump, please add a new markdown file in this folder. You do not note the version _number_, but rather the type of bump that you expect: major, minor, or patch. The filename is not important, as long as it is a `.md`, but we recommend that it represents the overall change for organizational purposes. 6 | 7 | When you select the version bump required, you do _not_ need to consider dependencies. Only note the package with the actual change, and any packages that depend on that package will be bumped automatically in the process. 8 | 9 | Use the following format: 10 | 11 | ```md 12 | --- 13 | "tao": patch 14 | --- 15 | 16 | Change summary goes here 17 | 18 | ``` 19 | 20 | Summaries do not have a specific character limit, but are text only. These summaries are used within the (future implementation of) changelogs. They will give context to the change and also point back to the original PR if more details and context are needed. 21 | 22 | Changes will be designated as a `major`, `minor` or `patch` as further described in [semver](https://semver.org/). 23 | 24 | Given a version number MAJOR.MINOR.PATCH, increment the: 25 | 26 | - MAJOR version when you make incompatible API changes, 27 | - MINOR version when you add functionality in a backwards compatible manner, and 28 | - PATCH version when you make backwards compatible bug fixes. 29 | 30 | Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format, but will be discussed prior to usage (as extra steps will be necessary in consideration of merging and publishing). 31 | -------------------------------------------------------------------------------- /examples/window_run_return.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Limit this example to only compatible platforms. 6 | #[cfg(not(target_os = "ios"))] 7 | #[allow(clippy::single_match)] 8 | fn main() { 9 | use std::{thread::sleep, time::Duration}; 10 | 11 | use tao::{ 12 | event::{Event, WindowEvent}, 13 | event_loop::{ControlFlow, EventLoop}, 14 | platform::run_return::EventLoopExtRunReturn, 15 | window::WindowBuilder, 16 | }; 17 | let mut event_loop = EventLoop::new(); 18 | 19 | env_logger::init(); 20 | let _window = WindowBuilder::new() 21 | .with_title("A fantastic window!") 22 | .build(&event_loop) 23 | .unwrap(); 24 | 25 | let mut quit = false; 26 | 27 | while !quit { 28 | event_loop.run_return(|event, _, control_flow| { 29 | *control_flow = ControlFlow::Wait; 30 | 31 | if let Event::WindowEvent { event, .. } = &event { 32 | // Print only Window events to reduce noise 33 | println!("{:?}", event); 34 | } 35 | 36 | match event { 37 | Event::WindowEvent { 38 | event: WindowEvent::CloseRequested, 39 | .. 40 | } => { 41 | quit = true; 42 | } 43 | Event::MainEventsCleared => { 44 | *control_flow = ControlFlow::Exit; 45 | } 46 | _ => (), 47 | } 48 | }); 49 | 50 | // Sleep for 1/60 second to simulate rendering 51 | println!("rendering"); 52 | sleep(Duration::from_millis(16)); 53 | } 54 | } 55 | 56 | #[cfg(target_os = "ios")] 57 | fn main() { 58 | println!("This platform doesn't support run_return."); 59 | } 60 | -------------------------------------------------------------------------------- /examples/mouse_wheel.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use tao::{ 6 | event::{DeviceEvent, Event, WindowEvent}, 7 | event_loop::{ControlFlow, EventLoop}, 8 | window::WindowBuilder, 9 | }; 10 | 11 | #[allow(clippy::collapsible_match)] 12 | #[allow(clippy::single_match)] 13 | fn main() { 14 | env_logger::init(); 15 | let event_loop = EventLoop::new(); 16 | 17 | let window = WindowBuilder::new() 18 | .with_title("Mouse Wheel events") 19 | .build(&event_loop) 20 | .unwrap(); 21 | 22 | event_loop.run(move |event, _, control_flow| { 23 | *control_flow = ControlFlow::Wait; 24 | 25 | match event { 26 | Event::WindowEvent { event, .. } => match event { 27 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 28 | _ => (), 29 | }, 30 | Event::DeviceEvent { event, .. } => match event { 31 | DeviceEvent::MouseWheel { delta, .. } => match delta { 32 | tao::event::MouseScrollDelta::LineDelta(x, y) => { 33 | println!("mouse wheel Line Delta: ({x},{y})"); 34 | let pixels_per_line = 120.0; 35 | let mut pos = window.outer_position().unwrap(); 36 | pos.x -= (x * pixels_per_line) as i32; 37 | pos.y -= (y * pixels_per_line) as i32; 38 | window.set_outer_position(pos) 39 | } 40 | tao::event::MouseScrollDelta::PixelDelta(p) => { 41 | println!("mouse wheel Pixel Delta: ({},{})", p.x, p.y); 42 | let mut pos = window.outer_position().unwrap(); 43 | pos.x -= p.x as i32; 44 | pos.y -= p.y as i32; 45 | window.set_outer_position(pos) 46 | } 47 | _ => (), 48 | }, 49 | _ => (), 50 | }, 51 | _ => (), 52 | } 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /examples/transparent.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #[cfg(windows)] 6 | use std::{num::NonZeroU32, rc::Rc}; 7 | 8 | use tao::{ 9 | event::{Event, WindowEvent}, 10 | event_loop::{ControlFlow, EventLoop}, 11 | window::WindowBuilder, 12 | }; 13 | 14 | #[allow(clippy::single_match)] 15 | fn main() { 16 | env_logger::init(); 17 | let event_loop = EventLoop::new(); 18 | 19 | let window = WindowBuilder::new() 20 | .with_decorations(false) 21 | .with_transparent(true) 22 | .build(&event_loop) 23 | .unwrap(); 24 | 25 | #[cfg(windows)] 26 | let (window, _context, mut surface) = { 27 | let window = Rc::new(window); 28 | let context = softbuffer::Context::new(window.clone()).unwrap(); 29 | let surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); 30 | (window, context, surface) 31 | }; 32 | 33 | window.set_title("A fantastic window!"); 34 | 35 | event_loop.run(move |event, _, control_flow| { 36 | *control_flow = ControlFlow::Wait; 37 | println!("{event:?}"); 38 | 39 | match event { 40 | Event::WindowEvent { 41 | event: WindowEvent::CloseRequested, 42 | .. 43 | } => *control_flow = ControlFlow::Exit, 44 | 45 | #[cfg(windows)] 46 | Event::RedrawRequested(_) => { 47 | let (width, height) = { 48 | let size = window.inner_size(); 49 | (size.width, size.height) 50 | }; 51 | surface 52 | .resize( 53 | NonZeroU32::new(width).unwrap(), 54 | NonZeroU32::new(height).unwrap(), 55 | ) 56 | .unwrap(); 57 | 58 | let mut buffer = surface.buffer_mut().unwrap(); 59 | buffer.fill(0); 60 | buffer.present().unwrap(); 61 | } 62 | 63 | _ => (), 64 | } 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Run the `cargo run --example ` to see how each example works. 4 | 5 | - `control_flow`: tell event loop what to do in the next iteration, after the current one's finished. 6 | - `custom_events`: user can create custom events and emit or listen to them through tao. 7 | - `fullscreen`: example for configuring different screen sizes, and video modes. 8 | - `handling_close`: close window with a warning. 9 | - `request_redraw_threaded`: same as request_redraw but multithreaded. 10 | - `request_redraw`: an event emitted when it's needed to redraw (when resizing window for example). 11 | - `timer`: an example that makes a timer which suspend the thread for some time. 12 | - `window_run_return`: similar to run function of EventLoop, but accept non-move closures and returns control flow to the caller when exit. 13 | - `window_debug`: example that debugs with eprintln. 14 | 15 | ## Quite self-explainatory examples. 16 | 17 | - `cursor_grab`: prevent the cursor from going outside the window. 18 | - `cursor`: set different cursor icons. 19 | - `drag_window`: allow dragging window when hold left mouse and move. 20 | - `min_max_size`: set smallest/largest window size you can zoom. 21 | - `minimize`: minimize window. 22 | - `monitor_list`: list all available monitors. 23 | - `mouse_wheel`: get the difference in scrolling state (MouseScrollDelta) in pixel or line. 24 | - `multithreaded`: same as multiwindow but multithreaded. 25 | - `multiwindow`: create multiple windows 26 | - `parentwindow`: a window inside another window. 27 | - `reopen_event`: handle click on dock icon on macOS 28 | - `resizable`: allow resizing window or not. 29 | - `set_ime_position`: set IME (input method editor) position when click. 30 | - `transparent`: make a transparent window. 31 | - `video_modes`: example that lists all video modes of primary monitor 32 | - `window_icon`: add window icon. 33 | - `window`: example that makes a window. 34 | -------------------------------------------------------------------------------- /tao-macros/examples/android_fn.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use tao_macros::android_fn; 4 | 5 | struct JNIEnv<'a> { 6 | _marker: &'a PhantomData<()>, 7 | } 8 | #[repr(C)] 9 | struct JClass<'a> { 10 | _marker: &'a PhantomData<()>, 11 | } 12 | 13 | android_fn![com_example, tao_app, SomeClass, add, []]; 14 | unsafe fn add(_env: JNIEnv, _class: JClass) {} 15 | 16 | android_fn![com_example, tao_app, SomeClass, add2, [i32, i32]]; 17 | unsafe fn add2(_env: JNIEnv, _class: JClass, _a: i32, _b: i32) {} 18 | 19 | android_fn![com_example, tao_app, SomeClass, add3, [i32, i32], i32]; 20 | unsafe fn add3(_env: JNIEnv, _class: JClass, a: i32, b: i32) -> i32 { 21 | a + b 22 | } 23 | 24 | android_fn![com_example, tao_app, SomeClass, add4, [], i32]; 25 | unsafe fn add4(_env: JNIEnv, _class: JClass) -> i32 { 26 | 0 27 | } 28 | 29 | android_fn![com_example, tao_app, SomeClass, add5, [], __VOID__]; 30 | unsafe fn add5(_env: JNIEnv, _class: JClass) {} 31 | 32 | android_fn![com_example, tao_app, SomeClass, add6, [i32], __VOID__]; 33 | unsafe fn add6(_env: JNIEnv, _class: JClass, _a: i32) {} 34 | 35 | fn __setup__() {} 36 | fn __store_package_name__() {} 37 | android_fn!( 38 | com_example, 39 | tao_app, 40 | SomeClass, 41 | add7, 42 | [i32, i32], 43 | __VOID__, 44 | [__setup__, main], 45 | __store_package_name__, 46 | ); 47 | unsafe fn add7(_env: JNIEnv, _class: JClass, _a: i32, _b: i32, _setup: fn(), _main: fn()) {} 48 | 49 | android_fn!( 50 | com_example, 51 | tao_app, 52 | SomeClass, 53 | add8, 54 | [i32, i32], 55 | i32, 56 | [], 57 | __store_package_name__, 58 | ); 59 | unsafe fn add8(_env: JNIEnv, _class: JClass, _a: i32, _b: i32) -> i32 { 60 | 0 61 | } 62 | 63 | android_fn![ 64 | com_example, 65 | tao_app, 66 | SomeClass, 67 | add10, 68 | [JClass<'local>, i32], 69 | JClass<'local> 70 | ]; 71 | unsafe fn add10<'local>( 72 | _env: JNIEnv<'local>, 73 | _class: JClass<'local>, 74 | a: JClass<'local>, 75 | _b: i32, 76 | ) -> JClass<'local> { 77 | a 78 | } 79 | 80 | fn main() {} 81 | -------------------------------------------------------------------------------- /.github/workflows/covector-version-or-publish.yml: -------------------------------------------------------------------------------- 1 | name: version or publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - dev 7 | - v0.16 8 | 9 | jobs: 10 | version-or-publish: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 65 13 | outputs: 14 | change: ${{ steps.covector.outputs.change }} 15 | commandRan: ${{ steps.covector.outputs.commandRan }} 16 | successfulPublish: ${{ steps.covector.outputs.successfulPublish }} 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | - name: cargo login 23 | run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }} 24 | - name: git config 25 | run: | 26 | git config --global user.name "${{ github.event.pusher.name }}" 27 | git config --global user.email "${{ github.event.pusher.email }}" 28 | - name: covector version or publish (publish when no change files present) 29 | uses: jbolda/covector/packages/action@covector-v0 30 | id: covector 31 | env: 32 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 33 | CARGO_AUDIT_OPTIONS: ${{ secrets.CARGO_AUDIT_OPTIONS }} 34 | with: 35 | token: ${{ secrets.GITHUB_TOKEN }} 36 | command: "version-or-publish" 37 | createRelease: true 38 | recognizeContributors: true 39 | 40 | - name: Sync Cargo.lock 41 | if: steps.covector.outputs.commandRan == 'version' 42 | run: cargo tree --depth 0 43 | 44 | - name: Create Pull Request With Versions Bumped 45 | id: cpr 46 | uses: peter-evans/create-pull-request@dd2324fc52d5d43c699a5636bcf19fceaa70c284 # 7.0.7 47 | if: steps.covector.outputs.commandRan == 'version' 48 | with: 49 | token: ${{ secrets.GITHUB_TOKEN }} 50 | title: "Publish New Versions" 51 | commit-message: "publish new versions" 52 | labels: "version updates" 53 | branch: "release" 54 | body: ${{ steps.covector.outputs.change }} 55 | sign-commits: true 56 | -------------------------------------------------------------------------------- /src/platform/run_return.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #![cfg(not(target_os = "ios"))] 6 | 7 | use crate::{ 8 | event::Event, 9 | event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, 10 | }; 11 | 12 | /// Additional methods on `EventLoop` to return control flow to the caller. 13 | pub trait EventLoopExtRunReturn { 14 | /// A type provided by the user that can be passed through `Event::UserEvent`. 15 | type UserEvent; 16 | 17 | /// Initializes the `tao` event loop. 18 | /// 19 | /// Unlike `run`, this function accepts non-`'static` (i.e. non-`move`) closures and returns 20 | /// control flow to the caller when `control_flow` is set to `ControlFlow::Exit`. 21 | /// 22 | /// # Caveats 23 | /// Despite its appearance at first glance, this is *not* a perfect replacement for 24 | /// `poll_events`. For example, this function will not return on Windows or macOS while a 25 | /// window is getting resized, resulting in all application logic outside of the 26 | /// `event_handler` closure not running until the resize operation ends. Other OS operations 27 | /// may also result in such freezes. This behavior is caused by fundamental limitations in the 28 | /// underlying OS APIs, which cannot be hidden by `tao` without severe stability repercussions. 29 | /// 30 | /// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary. 31 | /// 32 | /// ## Platform-specific 33 | /// 34 | /// - **Unix-alikes** (**X11** or **Wayland**): This function returns `1` upon disconnection from 35 | /// the display server. 36 | fn run_return(&mut self, event_handler: F) -> i32 37 | where 38 | F: FnMut(Event<'_, Self::UserEvent>, &EventLoopWindowTarget, &mut ControlFlow); 39 | } 40 | 41 | impl EventLoopExtRunReturn for EventLoop { 42 | type UserEvent = T; 43 | 44 | fn run_return(&mut self, event_handler: F) -> i32 45 | where 46 | F: FnMut(Event<'_, Self::UserEvent>, &EventLoopWindowTarget, &mut ControlFlow), 47 | { 48 | self.event_loop.run_return(event_handler) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/cursor_grab.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use tao::{ 6 | event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent}, 7 | event_loop::{ControlFlow, EventLoop}, 8 | keyboard::{Key, ModifiersState}, 9 | window::WindowBuilder, 10 | }; 11 | 12 | #[allow(clippy::single_match)] 13 | fn main() { 14 | env_logger::init(); 15 | let event_loop = EventLoop::new(); 16 | 17 | let window = WindowBuilder::new() 18 | .with_title("Super Cursor Grab'n'Hide Simulator 9000") 19 | .build(&event_loop) 20 | .unwrap(); 21 | 22 | let mut modifiers = ModifiersState::default(); 23 | 24 | event_loop.run(move |event, _, control_flow| { 25 | *control_flow = ControlFlow::Wait; 26 | 27 | match event { 28 | Event::WindowEvent { event, .. } => match event { 29 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 30 | WindowEvent::KeyboardInput { 31 | event: 32 | KeyEvent { 33 | logical_key: key, 34 | state: ElementState::Released, 35 | .. 36 | }, 37 | .. 38 | } => { 39 | // WARNING: Consider using `key_without_modifers()` if available on your platform. 40 | // See the `key_binding` example 41 | match key { 42 | Key::Escape => *control_flow = ControlFlow::Exit, 43 | Key::Character(ch) => match ch.to_lowercase().as_str() { 44 | "g" => window.set_cursor_grab(!modifiers.shift_key()).unwrap(), 45 | "h" => window.set_cursor_visible(modifiers.shift_key()), 46 | _ => (), 47 | }, 48 | _ => (), 49 | } 50 | } 51 | WindowEvent::ModifiersChanged(m) => modifiers = m, 52 | _ => (), 53 | }, 54 | Event::DeviceEvent { event, .. } => match event { 55 | DeviceEvent::MouseMotion { delta, .. } => println!("mouse moved: {delta:?}"), 56 | DeviceEvent::Button { button, state, .. } => match state { 57 | ElementState::Pressed => println!("mouse button {button} pressed"), 58 | ElementState::Released => println!("mouse button {button} released"), 59 | _ => (), 60 | }, 61 | _ => (), 62 | }, 63 | _ => (), 64 | } 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /.changes/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "gitSiteUrl": "https://github.com/tauri-apps/tao/", 3 | "timeout": 3600000, 4 | "additionalBumpTypes": ["housekeeping"], 5 | "pkgManagers": { 6 | "rust": { 7 | "version": true, 8 | "getPublishedVersion": { 9 | "use": "fetch:check", 10 | "options": { 11 | "url": "https://crates.io/api/v1/crates/${ pkg.pkg }/${ pkg.pkgFile.version }" 12 | } 13 | }, 14 | "prepublish": [ 15 | { 16 | "command": "cargo install cargo-audit --features=fix", 17 | "dryRunCommand": true 18 | }, 19 | { 20 | "command": "echo '
\n

Cargo Audit

\n\n```'", 21 | "dryRunCommand": true, 22 | "pipe": true 23 | }, 24 | { 25 | "command": "cargo generate-lockfile", 26 | "dryRunCommand": true, 27 | "runFromRoot": true, 28 | "pipe": true 29 | }, 30 | { 31 | "command": "cargo audit ${ process.env.CARGO_AUDIT_OPTIONS || '' }", 32 | "dryRunCommand": true, 33 | "runFromRoot": true, 34 | "pipe": true 35 | }, 36 | { 37 | "command": "echo '```\n\n
\n'", 38 | "dryRunCommand": true, 39 | "pipe": true 40 | } 41 | ], 42 | "publish": [ 43 | { 44 | "command": "echo '
\n

Cargo Publish

\n\n```'", 45 | "dryRunCommand": true, 46 | "pipe": true 47 | }, 48 | { 49 | "command": "cargo publish --no-verify --allow-dirty", 50 | "dryRunCommand": "cargo publish --no-verify --allow-dirty --dry-run", 51 | "pipe": true 52 | }, 53 | { 54 | "command": "echo '```\n\n
\n'", 55 | "dryRunCommand": true, 56 | "pipe": true 57 | } 58 | ], 59 | "postpublish": [ 60 | "git tag ${ pkg.pkg }-v${ pkgFile.versionMajor } -f", 61 | "git tag ${ pkg.pkg }-v${ pkgFile.versionMajor }.${ pkgFile.versionMinor } -f", 62 | "git push --tags -f" 63 | ] 64 | } 65 | }, 66 | "packages": { 67 | "tao-macros": { 68 | "path": "./tao-macros", 69 | "manager": "rust" 70 | }, 71 | "tao": { 72 | "path": "./", 73 | "manager": "rust", 74 | "dependencies": ["tao-macros"] 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /examples/window_icon.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | extern crate image; 6 | use std::path::Path; 7 | 8 | use tao::{ 9 | event::Event, 10 | event_loop::{ControlFlow, EventLoop}, 11 | window::{Icon, WindowBuilder}, 12 | }; 13 | 14 | #[allow(clippy::single_match)] 15 | fn main() { 16 | env_logger::init(); 17 | 18 | // You'll have to choose an icon size at your own discretion. On Linux, the icon should be 19 | // provided in whatever size it was naturally drawn; that is, don’t scale the image before passing 20 | // it to Tao. But on Windows, you will have to account for screen scaling. Here we use 32px, 21 | // since it seems to work well enough in most cases. Be careful about going too high, or 22 | // you'll be bitten by the low-quality downscaling built into the WM. 23 | let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png"); 24 | 25 | let icon = load_icon(Path::new(path)); 26 | 27 | let event_loop = EventLoop::new(); 28 | 29 | let window = WindowBuilder::new() 30 | .with_title("An iconic window!") 31 | // At present, this only does anything on Windows and Linux, so if you want to save load 32 | // time, you can put icon loading behind a function that returns `None` on other platforms. 33 | .with_window_icon(Some(icon)) 34 | .build(&event_loop) 35 | .unwrap(); 36 | 37 | event_loop.run(move |event, _, control_flow| { 38 | *control_flow = ControlFlow::Wait; 39 | 40 | if let Event::WindowEvent { event, .. } = event { 41 | use tao::event::WindowEvent::*; 42 | match event { 43 | CloseRequested => *control_flow = ControlFlow::Exit, 44 | DroppedFile(path) => { 45 | window.set_window_icon(Some(load_icon(&path))); 46 | } 47 | _ => (), 48 | } 49 | } 50 | }); 51 | } 52 | 53 | fn load_icon(path: &Path) -> Icon { 54 | let (icon_rgba, icon_width, icon_height) = { 55 | // alternatively, you can embed the icon in the binary through `include_bytes!` macro and use `image::load_from_memory` 56 | let image = image::open(path) 57 | .expect("Failed to open icon path") 58 | .into_rgba8(); 59 | let (width, height) = image.dimensions(); 60 | let rgba = image.into_raw(); 61 | (rgba, width, height) 62 | }; 63 | Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon") 64 | } 65 | -------------------------------------------------------------------------------- /examples/cursor.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use tao::{ 6 | event::{ElementState, Event, KeyEvent, WindowEvent}, 7 | event_loop::{ControlFlow, EventLoop}, 8 | window::{CursorIcon, WindowBuilder}, 9 | }; 10 | 11 | fn main() { 12 | env_logger::init(); 13 | let event_loop = EventLoop::new(); 14 | 15 | let window = WindowBuilder::new().build(&event_loop).unwrap(); 16 | window.set_title("A fantastic window!"); 17 | 18 | let mut cursor_idx = 0; 19 | 20 | event_loop.run(move |event, _, control_flow| { 21 | *control_flow = ControlFlow::Wait; 22 | 23 | match event { 24 | Event::WindowEvent { 25 | event: 26 | WindowEvent::KeyboardInput { 27 | event: 28 | KeyEvent { 29 | state: ElementState::Pressed, 30 | .. 31 | }, 32 | .. 33 | }, 34 | .. 35 | } => { 36 | println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]); 37 | window.set_cursor_icon(CURSORS[cursor_idx]); 38 | if cursor_idx < CURSORS.len() - 1 { 39 | cursor_idx += 1; 40 | } else { 41 | cursor_idx = 0; 42 | } 43 | } 44 | Event::WindowEvent { 45 | event: WindowEvent::CloseRequested, 46 | .. 47 | } => *control_flow = ControlFlow::Exit, 48 | _ => (), 49 | } 50 | }); 51 | } 52 | 53 | const CURSORS: &[CursorIcon] = &[ 54 | CursorIcon::Default, 55 | CursorIcon::Crosshair, 56 | CursorIcon::Hand, 57 | CursorIcon::Arrow, 58 | CursorIcon::Move, 59 | CursorIcon::Text, 60 | CursorIcon::Wait, 61 | CursorIcon::Help, 62 | CursorIcon::Progress, 63 | CursorIcon::NotAllowed, 64 | CursorIcon::ContextMenu, 65 | CursorIcon::Cell, 66 | CursorIcon::VerticalText, 67 | CursorIcon::Alias, 68 | CursorIcon::Copy, 69 | CursorIcon::NoDrop, 70 | CursorIcon::Grab, 71 | CursorIcon::Grabbing, 72 | CursorIcon::AllScroll, 73 | CursorIcon::ZoomIn, 74 | CursorIcon::ZoomOut, 75 | CursorIcon::EResize, 76 | CursorIcon::NResize, 77 | CursorIcon::NeResize, 78 | CursorIcon::NwResize, 79 | CursorIcon::SResize, 80 | CursorIcon::SeResize, 81 | CursorIcon::SwResize, 82 | CursorIcon::WResize, 83 | CursorIcon::EwResize, 84 | CursorIcon::NsResize, 85 | CursorIcon::NeswResize, 86 | CursorIcon::NwseResize, 87 | CursorIcon::ColResize, 88 | CursorIcon::RowResize, 89 | ]; 90 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | //! The `Error` struct and associated types. 6 | use std::{error, fmt}; 7 | 8 | use crate::platform_impl; 9 | 10 | /// An error whose cause it outside Tao's control. 11 | #[non_exhaustive] 12 | #[derive(Debug)] 13 | pub enum ExternalError { 14 | /// The operation is not supported by the backend. 15 | NotSupported(NotSupportedError), 16 | /// The OS cannot perform the operation. 17 | Os(OsError), 18 | } 19 | 20 | /// The error type for when the requested operation is not supported by the backend. 21 | #[derive(Clone)] 22 | pub struct NotSupportedError { 23 | _marker: (), 24 | } 25 | 26 | /// The error type for when the OS cannot perform the requested operation. 27 | #[derive(Debug)] 28 | pub struct OsError { 29 | line: u32, 30 | file: &'static str, 31 | error: platform_impl::OsError, 32 | } 33 | 34 | impl NotSupportedError { 35 | #[inline] 36 | #[allow(dead_code)] 37 | pub(crate) fn new() -> NotSupportedError { 38 | NotSupportedError { _marker: () } 39 | } 40 | } 41 | 42 | impl OsError { 43 | #[allow(dead_code)] 44 | pub(crate) fn new(line: u32, file: &'static str, error: platform_impl::OsError) -> OsError { 45 | OsError { line, file, error } 46 | } 47 | } 48 | 49 | #[allow(unused_macros)] 50 | macro_rules! os_error { 51 | ($error:expr) => {{ 52 | crate::error::OsError::new(line!(), file!(), $error) 53 | }}; 54 | } 55 | 56 | impl fmt::Display for OsError { 57 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 58 | f.pad(&format!( 59 | "os error at {}:{}: {}", 60 | self.file, self.line, self.error 61 | )) 62 | } 63 | } 64 | 65 | impl fmt::Display for ExternalError { 66 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 67 | match self { 68 | ExternalError::NotSupported(e) => e.fmt(f), 69 | ExternalError::Os(e) => e.fmt(f), 70 | } 71 | } 72 | } 73 | 74 | impl fmt::Debug for NotSupportedError { 75 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 76 | f.debug_struct("NotSupportedError").finish() 77 | } 78 | } 79 | 80 | impl fmt::Display for NotSupportedError { 81 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 82 | f.pad("the requested operation is not supported by Tao") 83 | } 84 | } 85 | 86 | impl error::Error for OsError {} 87 | impl error::Error for ExternalError {} 88 | impl error::Error for NotSupportedError {} 89 | -------------------------------------------------------------------------------- /examples/drag_window.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use tao::{ 6 | event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent}, 7 | event_loop::{ControlFlow, EventLoop}, 8 | keyboard::Key, 9 | window::{Window, WindowBuilder, WindowId}, 10 | }; 11 | 12 | #[allow(clippy::single_match)] 13 | fn main() { 14 | env_logger::init(); 15 | let event_loop = EventLoop::new(); 16 | 17 | let window_1 = WindowBuilder::new().build(&event_loop).unwrap(); 18 | let window_2 = WindowBuilder::new().build(&event_loop).unwrap(); 19 | 20 | let mut switched = false; 21 | let mut entered_id = window_2.id(); 22 | 23 | event_loop.run(move |event, _, control_flow| match event { 24 | Event::NewEvents(StartCause::Init) => { 25 | eprintln!("Switch which window is to be dragged by pressing \"x\".") 26 | } 27 | Event::WindowEvent { 28 | event, window_id, .. 29 | } => match event { 30 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 31 | WindowEvent::MouseInput { 32 | state: ElementState::Pressed, 33 | button: MouseButton::Left, 34 | .. 35 | } => { 36 | let window = if (window_id == window_1.id() && switched) 37 | || (window_id == window_2.id() && !switched) 38 | { 39 | &window_2 40 | } else { 41 | &window_1 42 | }; 43 | 44 | window.drag_window().unwrap() 45 | } 46 | WindowEvent::CursorEntered { .. } => { 47 | entered_id = window_id; 48 | name_windows(entered_id, switched, &window_1, &window_2) 49 | } 50 | WindowEvent::KeyboardInput { 51 | event: 52 | KeyEvent { 53 | state: ElementState::Released, 54 | logical_key: Key::Character("x"), 55 | .. 56 | }, 57 | .. 58 | } => { 59 | switched = !switched; 60 | name_windows(entered_id, switched, &window_1, &window_2); 61 | println!("Switched!") 62 | } 63 | _ => (), 64 | }, 65 | _ => (), 66 | }); 67 | } 68 | 69 | fn name_windows(window_id: WindowId, switched: bool, window_1: &Window, window_2: &Window) { 70 | let (drag_target, other) = 71 | if (window_id == window_1.id() && switched) || (window_id == window_2.id() && !switched) { 72 | (&window_2, &window_1) 73 | } else { 74 | (&window_1, &window_2) 75 | }; 76 | drag_target.set_title("drag target"); 77 | other.set_title("tao window"); 78 | } 79 | -------------------------------------------------------------------------------- /examples/parentwindow.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] 6 | fn main() { 7 | use std::collections::HashMap; 8 | #[cfg(target_os = "macos")] 9 | use tao::platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS}; 10 | #[cfg(target_os = "linux")] 11 | use tao::platform::unix::{WindowBuilderExtUnix, WindowExtUnix}; 12 | #[cfg(target_os = "windows")] 13 | use tao::platform::windows::{WindowBuilderExtWindows, WindowExtWindows}; 14 | use tao::{ 15 | dpi::LogicalSize, 16 | event::{Event, StartCause, WindowEvent}, 17 | event_loop::{ControlFlow, EventLoop}, 18 | window::WindowBuilder, 19 | }; 20 | env_logger::init(); 21 | let event_loop = EventLoop::new(); 22 | let mut windows = HashMap::new(); 23 | let main_window = WindowBuilder::new().build(&event_loop).unwrap(); 24 | 25 | #[cfg(target_os = "macos")] 26 | let parent_window = main_window.ns_window(); 27 | #[cfg(target_os = "windows")] 28 | let parent_window = main_window.hwnd(); 29 | #[cfg(target_os = "linux")] 30 | let parent_window = main_window.gtk_window(); 31 | 32 | let child_window_builder = WindowBuilder::new().with_inner_size(LogicalSize::new(200, 200)); 33 | 34 | #[cfg(any(target_os = "windows", target_os = "macos"))] 35 | let child_window_builder = child_window_builder.with_parent_window(parent_window); 36 | 37 | #[cfg(target_os = "linux")] 38 | let child_window_builder = child_window_builder.with_transient_for(parent_window); 39 | 40 | let child_window = child_window_builder.build(&event_loop).unwrap(); 41 | 42 | windows.insert(child_window.id(), child_window); 43 | windows.insert(main_window.id(), main_window); 44 | 45 | event_loop.run(move |event, _, control_flow| { 46 | *control_flow = ControlFlow::Wait; 47 | 48 | match event { 49 | Event::NewEvents(StartCause::Init) => println!("TAO application started!"), 50 | Event::WindowEvent { 51 | event: WindowEvent::CloseRequested, 52 | window_id, 53 | .. 54 | } => { 55 | println!("Window {window_id:?} has received the signal to close"); 56 | // This drop the window, causing it to close. 57 | windows.remove(&window_id); 58 | if windows.is_empty() { 59 | *control_flow = ControlFlow::Exit; 60 | } 61 | } 62 | _ => (), 63 | }; 64 | }) 65 | } 66 | 67 | #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] 68 | fn main() { 69 | println!("This platform doesn't have the parent window support."); 70 | } 71 | -------------------------------------------------------------------------------- /src/platform_impl/windows/minimal_ime.rs: -------------------------------------------------------------------------------- 1 | use std::mem::MaybeUninit; 2 | 3 | use windows::Win32::{ 4 | Foundation::{HWND, LPARAM, LRESULT, WPARAM}, 5 | UI::WindowsAndMessaging::{self as win32wm, *}, 6 | }; 7 | 8 | use crate::platform_impl::platform::event_loop::ProcResult; 9 | 10 | pub fn is_msg_ime_related(msg_kind: u32) -> bool { 11 | matches!( 12 | msg_kind, 13 | win32wm::WM_IME_COMPOSITION 14 | | win32wm::WM_IME_COMPOSITIONFULL 15 | | win32wm::WM_IME_STARTCOMPOSITION 16 | | win32wm::WM_IME_ENDCOMPOSITION 17 | | win32wm::WM_IME_CHAR 18 | | win32wm::WM_CHAR 19 | | win32wm::WM_SYSCHAR 20 | ) 21 | } 22 | 23 | pub struct MinimalIme { 24 | // True if we're currently receiving messages belonging to a finished IME session. 25 | getting_ime_text: bool, 26 | 27 | utf16parts: Vec, 28 | } 29 | impl Default for MinimalIme { 30 | fn default() -> Self { 31 | MinimalIme { 32 | getting_ime_text: false, 33 | utf16parts: Vec::with_capacity(16), 34 | } 35 | } 36 | } 37 | impl MinimalIme { 38 | pub(crate) fn process_message( 39 | &mut self, 40 | hwnd: HWND, 41 | msg_kind: u32, 42 | wparam: WPARAM, 43 | _lparam: LPARAM, 44 | result: &mut ProcResult, 45 | ) -> Option { 46 | match msg_kind { 47 | win32wm::WM_IME_ENDCOMPOSITION => { 48 | self.getting_ime_text = true; 49 | } 50 | win32wm::WM_CHAR | win32wm::WM_SYSCHAR => { 51 | *result = ProcResult::Value(LRESULT(0)); 52 | if self.getting_ime_text { 53 | self.utf16parts.push(wparam.0 as u16); 54 | 55 | let more_char_coming; 56 | unsafe { 57 | let mut next_msg = MaybeUninit::uninit(); 58 | let has_message = PeekMessageW( 59 | next_msg.as_mut_ptr(), 60 | Some(hwnd), 61 | WM_KEYFIRST, 62 | WM_KEYLAST, 63 | PM_NOREMOVE, 64 | ); 65 | let has_message = has_message.as_bool(); 66 | if !has_message { 67 | more_char_coming = false; 68 | } else { 69 | let next_msg = next_msg.assume_init().message; 70 | more_char_coming = next_msg == WM_CHAR || next_msg == WM_SYSCHAR; 71 | } 72 | } 73 | if !more_char_coming { 74 | let result = String::from_utf16(&self.utf16parts).ok(); 75 | self.utf16parts.clear(); 76 | self.getting_ime_text = false; 77 | return result; 78 | } 79 | } else { 80 | return String::from_utf16(&[wparam.0 as u16]).ok(); 81 | } 82 | } 83 | _ => (), 84 | } 85 | 86 | None 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/platform_impl/linux/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #[cfg(feature = "x11")] 6 | mod device; 7 | mod event_loop; 8 | mod icon; 9 | mod keyboard; 10 | mod keycode; 11 | mod monitor; 12 | mod util; 13 | mod window; 14 | 15 | pub mod taskbar; 16 | pub mod wayland; 17 | #[cfg(feature = "x11")] 18 | pub mod x11; 19 | 20 | pub use self::keycode::{keycode_from_scancode, keycode_to_scancode}; 21 | pub(crate) use event_loop::PlatformSpecificEventLoopAttributes; 22 | pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}; 23 | pub use icon::PlatformIcon; 24 | pub use monitor::{MonitorHandle, VideoMode}; 25 | pub use window::{Window, WindowId}; 26 | 27 | use crate::{event::DeviceId as RootDeviceId, keyboard::Key}; 28 | 29 | #[derive(Debug, Clone, Eq, PartialEq, Hash)] 30 | pub struct KeyEventExtra { 31 | pub text_with_all_modifiers: Option<&'static str>, 32 | pub key_without_modifiers: Key<'static>, 33 | } 34 | 35 | #[non_exhaustive] 36 | #[derive(Clone)] 37 | pub enum Parent { 38 | None, 39 | ChildOf(gtk::Window), 40 | } 41 | 42 | impl Default for Parent { 43 | fn default() -> Self { 44 | Parent::None 45 | } 46 | } 47 | 48 | #[derive(Clone)] 49 | pub struct PlatformSpecificWindowBuilderAttributes { 50 | pub parent: Parent, 51 | pub skip_taskbar: bool, 52 | pub auto_transparent: bool, 53 | pub double_buffered: bool, 54 | pub app_paintable: bool, 55 | pub rgba_visual: bool, 56 | pub cursor_moved: bool, 57 | pub default_vbox: bool, 58 | } 59 | 60 | impl Default for PlatformSpecificWindowBuilderAttributes { 61 | fn default() -> Self { 62 | Self { 63 | parent: Default::default(), 64 | skip_taskbar: Default::default(), 65 | auto_transparent: true, 66 | double_buffered: true, 67 | app_paintable: false, 68 | rgba_visual: false, 69 | cursor_moved: true, 70 | default_vbox: true, 71 | } 72 | } 73 | } 74 | 75 | unsafe impl Send for PlatformSpecificWindowBuilderAttributes {} 76 | unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {} 77 | 78 | #[derive(Debug, Clone)] 79 | pub struct OsError; 80 | 81 | impl std::fmt::Display for OsError { 82 | fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 83 | Ok(()) 84 | } 85 | } 86 | 87 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 88 | pub struct DeviceId(usize); 89 | 90 | impl DeviceId { 91 | pub unsafe fn dummy() -> Self { 92 | Self(0) 93 | } 94 | } 95 | 96 | // FIXME: currently we use a dummy device id, find if we can get device id from gtk 97 | pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId(0)); 98 | -------------------------------------------------------------------------------- /src/platform_impl/linux/monitor.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use gtk::gdk::{self, prelude::MonitorExt, Display}; 6 | 7 | use crate::{ 8 | dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, 9 | monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, 10 | }; 11 | 12 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] 13 | pub struct MonitorHandle { 14 | pub(crate) monitor: gdk::Monitor, 15 | } 16 | 17 | impl MonitorHandle { 18 | pub fn new(display: &gdk::Display, number: i32) -> Self { 19 | let monitor = display.monitor(number).unwrap(); 20 | Self { monitor } 21 | } 22 | 23 | #[inline] 24 | pub fn name(&self) -> Option { 25 | self.monitor.model().map(|s| s.as_str().to_string()) 26 | } 27 | 28 | #[inline] 29 | pub fn size(&self) -> PhysicalSize { 30 | let rect = self.monitor.geometry(); 31 | LogicalSize { 32 | width: rect.width() as u32, 33 | height: rect.height() as u32, 34 | } 35 | .to_physical(self.scale_factor()) 36 | } 37 | 38 | #[inline] 39 | pub fn position(&self) -> PhysicalPosition { 40 | let rect = self.monitor.geometry(); 41 | LogicalPosition { 42 | x: rect.x(), 43 | y: rect.y(), 44 | } 45 | .to_physical(self.scale_factor()) 46 | } 47 | 48 | #[inline] 49 | pub fn scale_factor(&self) -> f64 { 50 | self.monitor.scale_factor() as f64 51 | } 52 | 53 | #[inline] 54 | pub fn video_modes(&self) -> Box> { 55 | Box::new(Vec::new().into_iter()) 56 | } 57 | } 58 | 59 | unsafe impl Send for MonitorHandle {} 60 | unsafe impl Sync for MonitorHandle {} 61 | 62 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 63 | pub struct VideoMode; 64 | 65 | impl VideoMode { 66 | #[inline] 67 | pub fn size(&self) -> PhysicalSize { 68 | panic!("VideoMode is unsupported on Linux.") 69 | } 70 | 71 | #[inline] 72 | pub fn bit_depth(&self) -> u16 { 73 | panic!("VideoMode is unsupported on Linux.") 74 | } 75 | 76 | #[inline] 77 | pub fn refresh_rate(&self) -> u16 { 78 | panic!("VideoMode is unsupported on Linux.") 79 | } 80 | 81 | #[inline] 82 | pub fn monitor(&self) -> RootMonitorHandle { 83 | panic!("VideoMode is unsupported on Linux.") 84 | } 85 | } 86 | 87 | pub fn from_point(display: &Display, x: f64, y: f64) -> Option { 88 | if let Some(monitor) = display.monitor_at_point(x as i32, y as i32) { 89 | (0..display.n_monitors()) 90 | .map(|i| (i, display.monitor(i).unwrap())) 91 | .find(|cur| cur.1.geometry() == monitor.geometry()) 92 | .map(|x| MonitorHandle::new(display, x.0)) 93 | } else { 94 | None 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /examples/min_max_size.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use tao::{ 6 | dpi::LogicalUnit, 7 | event::{ElementState, Event, KeyEvent, WindowEvent}, 8 | event_loop::{ControlFlow, EventLoop}, 9 | keyboard::Key, 10 | window::{WindowBuilder, WindowSizeConstraints}, 11 | }; 12 | 13 | #[allow(clippy::single_match)] 14 | fn main() { 15 | env_logger::init(); 16 | let event_loop = EventLoop::new(); 17 | 18 | let min_width = 400.0; 19 | let max_width = 800.0; 20 | let min_height = 200.0; 21 | let max_height = 400.0; 22 | let mut size_constraints = WindowSizeConstraints::default(); 23 | 24 | let window = WindowBuilder::new().build(&event_loop).unwrap(); 25 | 26 | eprintln!("constraint keys:"); 27 | eprintln!(" (E) Toggle the min width"); 28 | eprintln!(" (F) Toggle the max width"); 29 | eprintln!(" (P) Toggle the min height"); 30 | eprintln!(" (V) Toggle the max height"); 31 | 32 | event_loop.run(move |event, _, control_flow| { 33 | *control_flow = ControlFlow::Wait; 34 | 35 | match event { 36 | Event::WindowEvent { 37 | event: WindowEvent::CloseRequested, 38 | .. 39 | } => *control_flow = ControlFlow::Exit, 40 | 41 | Event::WindowEvent { 42 | event: 43 | WindowEvent::KeyboardInput { 44 | event: 45 | KeyEvent { 46 | logical_key: Key::Character(key_str), 47 | state: ElementState::Released, 48 | .. 49 | }, 50 | .. 51 | }, 52 | .. 53 | } => match key_str { 54 | "e" => { 55 | size_constraints.min_width = size_constraints 56 | .min_width 57 | .is_none() 58 | .then_some(LogicalUnit::new(min_width).into()); 59 | window.set_inner_size_constraints(size_constraints); 60 | } 61 | "f" => { 62 | size_constraints.max_width = size_constraints 63 | .max_width 64 | .is_none() 65 | .then_some(LogicalUnit::new(max_width).into()); 66 | window.set_inner_size_constraints(size_constraints); 67 | } 68 | "p" => { 69 | size_constraints.min_height = size_constraints 70 | .min_height 71 | .is_none() 72 | .then_some(LogicalUnit::new(min_height).into()); 73 | window.set_inner_size_constraints(size_constraints); 74 | } 75 | "v" => { 76 | size_constraints.max_height = size_constraints 77 | .max_height 78 | .is_none() 79 | .then_some(LogicalUnit::new(max_height).into()); 80 | window.set_inner_size_constraints(size_constraints); 81 | } 82 | _ => {} 83 | }, 84 | _ => (), 85 | } 86 | }); 87 | } 88 | -------------------------------------------------------------------------------- /src/platform_impl/macos/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | mod app; 6 | mod app_delegate; 7 | mod app_state; 8 | mod badge; 9 | mod dock; 10 | mod event; 11 | mod event_loop; 12 | mod ffi; 13 | mod icon; 14 | mod keycode; 15 | mod monitor; 16 | mod observer; 17 | mod progress_bar; 18 | mod util; 19 | mod view; 20 | mod window; 21 | mod window_delegate; 22 | 23 | use std::{fmt, ops::Deref, sync::Arc}; 24 | 25 | pub(crate) use self::event_loop::PlatformSpecificEventLoopAttributes; 26 | pub use self::{ 27 | app_delegate::get_aux_state_mut, 28 | event::KeyEventExtra, 29 | event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy}, 30 | keycode::{keycode_from_scancode, keycode_to_scancode}, 31 | monitor::{MonitorHandle, VideoMode}, 32 | progress_bar::set_progress_indicator, 33 | window::{Id as WindowId, Parent, PlatformSpecificWindowBuilderAttributes, UnownedWindow}, 34 | }; 35 | use crate::{ 36 | error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes, 37 | }; 38 | pub(crate) use badge::set_badge_label; 39 | pub(crate) use dock::set_dock_visibility; 40 | pub(crate) use icon::PlatformIcon; 41 | 42 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 43 | pub struct DeviceId; 44 | 45 | impl DeviceId { 46 | pub unsafe fn dummy() -> Self { 47 | DeviceId 48 | } 49 | } 50 | 51 | // Constant device ID; to be removed when if backend is updated to report real device IDs. 52 | pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId); 53 | 54 | pub struct Window { 55 | window: Arc, 56 | // We keep this around so that it doesn't get dropped until the window does. 57 | #[allow(dead_code)] 58 | delegate: util::IdRef, 59 | } 60 | 61 | #[non_exhaustive] 62 | #[derive(Debug)] 63 | pub enum OsError { 64 | CGError(core_graphics::base::CGError), 65 | CreationError(&'static str), 66 | } 67 | 68 | unsafe impl Send for Window {} 69 | unsafe impl Sync for Window {} 70 | 71 | impl Deref for Window { 72 | type Target = UnownedWindow; 73 | #[inline] 74 | fn deref(&self) -> &Self::Target { 75 | &self.window 76 | } 77 | } 78 | 79 | impl Window { 80 | pub fn new( 81 | _window_target: &EventLoopWindowTarget, 82 | attributes: WindowAttributes, 83 | pl_attribs: PlatformSpecificWindowBuilderAttributes, 84 | ) -> Result { 85 | let (window, delegate) = UnownedWindow::new(attributes, pl_attribs)?; 86 | Ok(Window { window, delegate }) 87 | } 88 | } 89 | 90 | impl fmt::Display for OsError { 91 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 92 | match self { 93 | OsError::CGError(e) => f.pad(&format!("CGError {}", e)), 94 | OsError::CreationError(e) => f.pad(e), 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /examples/progress_bar.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use tao::{ 6 | event::{ElementState, Event, KeyEvent, WindowEvent}, 7 | event_loop::{ControlFlow, EventLoop}, 8 | keyboard::{Key, ModifiersState}, 9 | window::{ProgressBarState, ProgressState, WindowBuilder}, 10 | }; 11 | 12 | #[allow(clippy::single_match)] 13 | fn main() { 14 | env_logger::init(); 15 | let event_loop = EventLoop::new(); 16 | 17 | let window = WindowBuilder::new().build(&event_loop).unwrap(); 18 | 19 | let mut modifiers = ModifiersState::default(); 20 | 21 | eprintln!("Key mappings:"); 22 | eprintln!(" [1-5]: Set progress to [0%, 25%, 50%, 75%, 100%]"); 23 | eprintln!(" Ctrl+1: Set state to None"); 24 | eprintln!(" Ctrl+2: Set state to Normal"); 25 | eprintln!(" Ctrl+3: Set state to Indeterminate"); 26 | eprintln!(" Ctrl+4: Set state to Paused"); 27 | eprintln!(" Ctrl+5: Set state to Error"); 28 | 29 | event_loop.run(move |event, _, control_flow| { 30 | *control_flow = ControlFlow::Wait; 31 | 32 | match event { 33 | Event::WindowEvent { 34 | event: WindowEvent::CloseRequested, 35 | .. 36 | } => *control_flow = ControlFlow::Exit, 37 | Event::WindowEvent { event, .. } => match event { 38 | WindowEvent::ModifiersChanged(new_state) => { 39 | modifiers = new_state; 40 | } 41 | WindowEvent::KeyboardInput { 42 | event: 43 | KeyEvent { 44 | logical_key: Key::Character(key_str), 45 | state: ElementState::Released, 46 | .. 47 | }, 48 | .. 49 | } => { 50 | if modifiers.is_empty() { 51 | let mut progress: u64 = 0; 52 | match key_str { 53 | "1" => progress = 0, 54 | "2" => progress = 25, 55 | "3" => progress = 50, 56 | "4" => progress = 75, 57 | "5" => progress = 100, 58 | _ => {} 59 | } 60 | 61 | window.set_progress_bar(ProgressBarState { 62 | progress: Some(progress), 63 | state: Some(ProgressState::Normal), 64 | desktop_filename: None, 65 | }); 66 | } else if modifiers.control_key() { 67 | let mut state = ProgressState::None; 68 | match key_str { 69 | "1" => state = ProgressState::None, 70 | "2" => state = ProgressState::Normal, 71 | "3" => state = ProgressState::Indeterminate, 72 | "4" => state = ProgressState::Paused, 73 | "5" => state = ProgressState::Error, 74 | _ => {} 75 | } 76 | 77 | window.set_progress_bar(ProgressBarState { 78 | progress: None, 79 | state: Some(state), 80 | desktop_filename: None, 81 | }); 82 | } 83 | } 84 | _ => {} 85 | }, 86 | _ => {} 87 | } 88 | }); 89 | } 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TAO - Window Creation Library 2 | 3 | [![](https://img.shields.io/crates/v/tao?style=flat-square)](https://crates.io/crates/tao) 4 | [![](https://img.shields.io/docsrs/tao?style=flat-square)](https://docs.rs/tao/) 5 | [![License](https://img.shields.io/badge/License-Apache%202-green.svg)](https://opencollective.com/tauri) 6 | [![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S) 7 | [![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app) 8 | [![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation) 9 | [![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri) 10 | 11 | Cross-platform application window creation library in Rust that supports all major platforms like 12 | Windows, macOS, Linux, iOS and Android. Built for you, maintained for Tauri. 13 | 14 | ### Cargo Features 15 | 16 | TAO provides the following features, which can be enabled in your `Cargo.toml` file: 17 | 18 | - `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde). 19 | 20 | ## Platform-specific notes 21 | 22 | ### Android 23 | 24 | This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation. 25 | 26 | Running on an Android device needs a dynamic system library, add this to Cargo.toml: 27 | 28 | ```toml 29 | [[example]] 30 | name = "request_redraw_threaded" 31 | crate-type = ["cdylib"] 32 | ``` 33 | 34 | And add this to the example file to add the native activity glue: 35 | 36 | ```rust 37 | #[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))] 38 | fn main() { 39 | ... 40 | } 41 | ``` 42 | 43 | And run the application with `cargo apk run --example request_redraw_threaded` 44 | 45 | ### Linux 46 | 47 | Gtk and its related libraries are used to build the support of Linux. Be sure to install following packages before building: 48 | 49 | #### Arch Linux / Manjaro: 50 | 51 | ```bash 52 | sudo pacman -S gtk3 53 | ``` 54 | 55 | #### Debian / Ubuntu: 56 | 57 | ```bash 58 | sudo apt install libgtk-3-dev 59 | ``` 60 | 61 | ### Acknowledgement 62 | 63 | This is a fork of [winit](https://crates.io/crates/winit) which replaces Linux's port to Gtk. 64 | In the future, we want to make these features more modular as separate crates. So we can switch back to winit and also benefit the whole community. 65 | 66 | ## Partners 67 | 68 | 69 | 70 | 71 | 76 | 77 | 78 |
72 | 73 | CrabNebula 74 | 75 |
79 | 80 | For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). 81 | -------------------------------------------------------------------------------- /src/platform_impl/linux/util.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, 3 | error::ExternalError, 4 | window::WindowSizeConstraints, 5 | }; 6 | use gtk::{ 7 | gdk::{ 8 | self, 9 | prelude::{DeviceExt, SeatExt}, 10 | Display, 11 | }, 12 | glib::{self}, 13 | traits::{GtkWindowExt, WidgetExt}, 14 | }; 15 | use std::{cell::RefCell, rc::Rc}; 16 | 17 | #[inline] 18 | pub fn cursor_position(is_wayland: bool) -> Result, ExternalError> { 19 | if is_wayland { 20 | Ok((0, 0).into()) 21 | } else { 22 | Display::default() 23 | .map(|d| { 24 | ( 25 | d.default_seat().and_then(|s| s.pointer()), 26 | d.default_group(), 27 | ) 28 | }) 29 | .map(|(p, g)| { 30 | p.map(|p| { 31 | let (_, x, y) = p.position_double(); 32 | LogicalPosition::new(x, y).to_physical(g.scale_factor() as _) 33 | }) 34 | }) 35 | .map(|p| p.ok_or(ExternalError::Os(os_error!(super::OsError)))) 36 | .ok_or(ExternalError::Os(os_error!(super::OsError)))? 37 | } 38 | } 39 | 40 | pub fn set_size_constraints( 41 | window: &W, 42 | constraints: WindowSizeConstraints, 43 | ) { 44 | let mut geom_mask = gdk::WindowHints::empty(); 45 | if constraints.has_min() { 46 | geom_mask |= gdk::WindowHints::MIN_SIZE; 47 | } 48 | if constraints.has_max() { 49 | geom_mask |= gdk::WindowHints::MAX_SIZE; 50 | } 51 | 52 | let scale_factor = window.scale_factor() as f64; 53 | 54 | let min_size: LogicalSize = constraints.min_size_logical(scale_factor); 55 | let max_size: LogicalSize = constraints.max_size_logical(scale_factor); 56 | 57 | let picky_none: Option<>k::Window> = None; 58 | window.set_geometry_hints( 59 | picky_none, 60 | Some(&gdk::Geometry::new( 61 | min_size.width, 62 | min_size.height, 63 | max_size.width, 64 | max_size.height, 65 | 0, 66 | 0, 67 | 0, 68 | 0, 69 | 0f64, 70 | 0f64, 71 | gdk::Gravity::Center, 72 | )), 73 | geom_mask, 74 | ) 75 | } 76 | 77 | pub struct WindowMaximizeProcess { 78 | window: W, 79 | resizable: bool, 80 | step: u8, 81 | } 82 | 83 | impl WindowMaximizeProcess { 84 | pub fn new(window: W, resizable: bool) -> Rc> { 85 | Rc::new(RefCell::new(Self { 86 | window, 87 | resizable, 88 | step: 0, 89 | })) 90 | } 91 | 92 | pub fn next_step(&mut self) -> glib::ControlFlow { 93 | match self.step { 94 | 0 => { 95 | self.window.set_resizable(true); 96 | self.step += 1; 97 | glib::ControlFlow::Continue 98 | } 99 | 1 => { 100 | self.window.maximize(); 101 | self.step += 1; 102 | glib::ControlFlow::Continue 103 | } 104 | 2 => { 105 | self.window.set_resizable(self.resizable); 106 | glib::ControlFlow::Break 107 | } 108 | _ => glib::ControlFlow::Break, 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/platform_impl/linux/device.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | os::raw::{c_int, c_uchar}, 3 | ptr, 4 | }; 5 | 6 | use gtk::glib; 7 | use x11_dl::{xinput2, xlib}; 8 | 9 | use crate::event::{DeviceEvent, ElementState, RawKeyEvent}; 10 | 11 | use super::keycode_from_scancode; 12 | 13 | /// Spawn Device event thread. Only works on x11 since wayland doesn't have such global events. 14 | pub fn spawn(device_tx: glib::Sender) { 15 | std::thread::spawn(move || unsafe { 16 | let xlib = xlib::Xlib::open().unwrap(); 17 | let xinput2 = xinput2::XInput2::open().unwrap(); 18 | let display = (xlib.XOpenDisplay)(ptr::null()); 19 | let root = (xlib.XDefaultRootWindow)(display); 20 | // TODO Add more device event mask 21 | let mask = xinput2::XI_RawKeyPressMask | xinput2::XI_RawKeyReleaseMask; 22 | let mut event_mask = xinput2::XIEventMask { 23 | deviceid: xinput2::XIAllMasterDevices, 24 | mask: &mask as *const _ as *mut c_uchar, 25 | mask_len: std::mem::size_of_val(&mask) as c_int, 26 | }; 27 | (xinput2.XISelectEvents)(display, root, &mut event_mask as *mut _, 1); 28 | 29 | #[allow(clippy::uninit_assumed_init)] 30 | let mut event: xlib::XEvent = std::mem::MaybeUninit::uninit().assume_init(); 31 | loop { 32 | (xlib.XNextEvent)(display, &mut event); 33 | 34 | // XFilterEvent tells us when an event has been discarded by the input method. 35 | // Specifically, this involves all of the KeyPress events in compose/pre-edit sequences, 36 | // along with an extra copy of the KeyRelease events. This also prevents backspace and 37 | // arrow keys from being detected twice. 38 | if xlib::True == { 39 | (xlib.XFilterEvent)(&mut event, { 40 | let xev: &xlib::XAnyEvent = event.as_ref(); 41 | xev.window 42 | }) 43 | } { 44 | continue; 45 | } 46 | 47 | let event_type = event.get_type(); 48 | match event_type { 49 | xlib::GenericEvent => { 50 | let mut xev = event.generic_event_cookie; 51 | if (xlib.XGetEventData)(display, &mut xev) == xlib::True { 52 | match xev.evtype { 53 | xinput2::XI_RawKeyPress | xinput2::XI_RawKeyRelease => { 54 | let xev: &xinput2::XIRawEvent = &*(xev.data as *const _); 55 | let physical_key = keycode_from_scancode(xev.detail as u32); 56 | let state = match xev.evtype { 57 | xinput2::XI_RawKeyPress => ElementState::Pressed, 58 | xinput2::XI_RawKeyRelease => ElementState::Released, 59 | _ => unreachable!(), 60 | }; 61 | 62 | let event = RawKeyEvent { 63 | physical_key, 64 | state, 65 | }; 66 | 67 | if let Err(e) = device_tx.send(DeviceEvent::Key(event)) { 68 | log::info!("Failed to send device event {} since receiver is closed. Closing x11 thread along with it", e); 69 | break; 70 | } 71 | } 72 | _ => {} 73 | } 74 | } 75 | } 76 | _ => {} 77 | } 78 | } 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /src/platform_impl/macos/dock.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case, non_upper_case_globals)] 2 | 3 | use std::{ 4 | sync::Mutex, 5 | time::{Duration, Instant}, 6 | }; 7 | 8 | use objc2::{runtime::AnyObject, MainThreadMarker}; 9 | use objc2_app_kit::NSApplication; 10 | 11 | use super::get_aux_state_mut; 12 | 13 | const DOCK_SHOW_TIMEOUT: Duration = Duration::from_secs(1); 14 | 15 | #[link(name = "ApplicationServices", kind = "framework")] 16 | extern "C" { 17 | fn TransformProcessType(psn: *const ProcessSerialNumber, transformState: i32) -> i32; 18 | } 19 | 20 | #[repr(C)] 21 | struct ProcessSerialNumber { 22 | highLongOfPSN: u32, 23 | lowLongOfPSN: u32, 24 | } 25 | 26 | /// https://developer.apple.com/documentation/applicationservices/1501096-anonymous/kcurrentprocess?language=objc 27 | pub const kCurrentProcess: u32 = 2; 28 | /// https://developer.apple.com/documentation/applicationservices/1501117-anonymous/kprocesstransformtouielementapplication?language=objc 29 | pub const kProcessTransformToUIElementApplication: i32 = 4; 30 | /// https://developer.apple.com/documentation/applicationservices/1501117-anonymous/kprocesstransformtoforegroundapplication?language=objc 31 | pub const kProcessTransformToForegroundApplication: i32 = 1; 32 | 33 | pub fn set_dock_visibility(app_delegate: &AnyObject, visible: bool) { 34 | let last_dock_show = unsafe { &get_aux_state_mut(app_delegate).last_dock_show }; 35 | if visible { 36 | set_dock_show(last_dock_show); 37 | } else { 38 | set_dock_hide(last_dock_show); 39 | } 40 | } 41 | 42 | fn set_dock_hide(last_dock_show: &Mutex>) { 43 | // Transforming application state from UIElement to Foreground is an 44 | // asynchronous operation, and unfortunately there is currently no way to know 45 | // when it is finished. 46 | // So if we call DockHide => DockShow => DockHide => DockShow in a very short 47 | // time, we would trigger a bug of macOS that, there would be multiple dock 48 | // icons of the app left in system. 49 | // To work around this, we make sure DockHide does nothing if it is called 50 | // immediately after DockShow. After some experiments, 1 second seems to be 51 | // a proper interval. 52 | let now = Instant::now(); 53 | let last_dock_show = last_dock_show.lock().unwrap(); 54 | if let Some(last_dock_show_time) = *last_dock_show { 55 | if now.duration_since(last_dock_show_time) < DOCK_SHOW_TIMEOUT { 56 | return; 57 | } 58 | } 59 | 60 | unsafe { 61 | // TODO: Safety. 62 | let mtm = MainThreadMarker::new_unchecked(); 63 | let app = NSApplication::sharedApplication(mtm); 64 | let windows = app.windows(); 65 | 66 | for window in windows { 67 | window.setCanHide(false); 68 | } 69 | 70 | let psn = ProcessSerialNumber { 71 | highLongOfPSN: 0, 72 | lowLongOfPSN: kCurrentProcess, 73 | }; 74 | TransformProcessType(&psn, kProcessTransformToUIElementApplication); 75 | } 76 | } 77 | 78 | fn set_dock_show(last_dock_show: &Mutex>) { 79 | let now = Instant::now(); 80 | let mut last_dock_show = last_dock_show.lock().unwrap(); 81 | *last_dock_show = Some(now); 82 | 83 | unsafe { 84 | let psn = ProcessSerialNumber { 85 | highLongOfPSN: 0, 86 | lowLongOfPSN: kCurrentProcess, 87 | }; 88 | 89 | TransformProcessType(&psn, kProcessTransformToForegroundApplication); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /examples/handling_close.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use tao::{ 6 | event::{ElementState, Event, KeyEvent, WindowEvent}, 7 | event_loop::{ControlFlow, EventLoop}, 8 | keyboard::Key, 9 | window::WindowBuilder, 10 | }; 11 | 12 | #[allow(clippy::single_match)] 13 | fn main() { 14 | env_logger::init(); 15 | let event_loop = EventLoop::new(); 16 | 17 | let _window = WindowBuilder::new() 18 | .with_title("Your faithful window") 19 | .build(&event_loop) 20 | .unwrap(); 21 | 22 | let mut close_requested = false; 23 | 24 | event_loop.run(move |event, _, control_flow| { 25 | *control_flow = ControlFlow::Wait; 26 | 27 | match event { 28 | Event::WindowEvent { event, .. } => { 29 | match event { 30 | WindowEvent::CloseRequested => { 31 | // `CloseRequested` is sent when the close button on the window is pressed (or 32 | // through whatever other mechanisms the window manager provides for closing a 33 | // window). If you don't handle this event, the close button won't actually do 34 | // anything. 35 | 36 | // A common thing to do here is prompt the user if they have unsaved work. 37 | // Creating a proper dialog box for that is far beyond the scope of this 38 | // example, so here we'll just respond to the Y and N keys. 39 | println!("Are you ready to bid your window farewell? [Y/N]"); 40 | close_requested = true; 41 | 42 | // In applications where you can safely close the window without further 43 | // action from the user, this is generally where you'd handle cleanup before 44 | // closing the window. How to close the window is detailed in the handler for 45 | // the Y key. 46 | } 47 | WindowEvent::KeyboardInput { 48 | event: 49 | KeyEvent { 50 | logical_key: Key::Character(char), 51 | state: ElementState::Released, 52 | .. 53 | }, 54 | .. 55 | } => { 56 | // WARNING: Consider using `key_without_modifers()` if available on your platform. 57 | // See the `key_binding` example 58 | match char { 59 | "y" => { 60 | if close_requested { 61 | // This is where you'll want to do any cleanup you need. 62 | println!("Buh-bye!"); 63 | 64 | // For a single-window application like this, you'd normally just 65 | // break out of the event loop here. If you wanted to keep running the 66 | // event loop (i.e. if it's a multi-window application), you need to 67 | // drop the window. That closes it, and results in `Destroyed` being 68 | // sent. 69 | *control_flow = ControlFlow::Exit; 70 | } 71 | } 72 | "n" => { 73 | if close_requested { 74 | println!("Your window will continue to stay by your side."); 75 | close_requested = false; 76 | } 77 | } 78 | _ => (), 79 | } 80 | } 81 | _ => (), 82 | } 83 | } 84 | _ => (), 85 | } 86 | }); 87 | } 88 | -------------------------------------------------------------------------------- /src/platform_impl/ios/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | //! iOS support 6 | //! 7 | //! # Building app 8 | //! To build ios app you will need rustc built for this targets: 9 | //! 10 | //! - armv7-apple-ios 11 | //! - armv7s-apple-ios 12 | //! - i386-apple-ios 13 | //! - aarch64-apple-ios 14 | //! - x86_64-apple-ios 15 | //! 16 | //! Then 17 | //! 18 | //! ``` 19 | //! cargo build --target=... 20 | //! ``` 21 | //! The simplest way to integrate your app into xcode environment is to build it 22 | //! as a static library. Wrap your main function and export it. 23 | //! 24 | //! ```rust, ignore 25 | //! #[no_mangle] 26 | //! pub extern fn start_tao_app() { 27 | //! start_inner() 28 | //! } 29 | //! 30 | //! fn start_inner() { 31 | //! ... 32 | //! } 33 | //! ``` 34 | //! 35 | //! Compile project and then drag resulting .a into Xcode project. Add tao.h to xcode. 36 | //! 37 | //! ```ignore 38 | //! void start_tao_app(); 39 | //! ``` 40 | //! 41 | //! Use start_tao_app inside your xcode's main function. 42 | //! 43 | //! 44 | //! # App lifecycle and events 45 | //! 46 | //! iOS environment is very different from other platforms and you must be very 47 | //! careful with it's events. Familiarize yourself with 48 | //! [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/). 49 | //! 50 | //! 51 | //! This is how those event are represented in tao: 52 | //! 53 | //! - applicationDidBecomeActive is Resumed 54 | //! - applicationWillResignActive is Suspended 55 | //! - applicationWillTerminate is LoopDestroyed 56 | //! 57 | //! Keep in mind that after LoopDestroyed event is received every attempt to draw with 58 | //! opengl will result in segfault. 59 | //! 60 | //! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed. 61 | 62 | // TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be 63 | // worked around in the future by using GCD (grand central dispatch) and/or caching of values like 64 | // window size/position. 65 | macro_rules! assert_main_thread { 66 | ($($t:tt)*) => { 67 | let is_main_thread: bool = msg_send![class!(NSThread), isMainThread]; 68 | if !is_main_thread { 69 | panic!($($t)*); 70 | } 71 | }; 72 | } 73 | 74 | mod app_state; 75 | mod badge; 76 | mod event_loop; 77 | mod ffi; 78 | mod keycode; 79 | mod monitor; 80 | mod view; 81 | mod window; 82 | 83 | use std::fmt; 84 | 85 | pub(crate) use self::event_loop::PlatformSpecificEventLoopAttributes; 86 | pub use self::{ 87 | event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, 88 | keycode::{keycode_from_scancode, keycode_to_scancode}, 89 | monitor::{MonitorHandle, VideoMode}, 90 | window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId}, 91 | }; 92 | pub(crate) use crate::icon::NoIcon as PlatformIcon; 93 | pub(crate) use badge::set_badge_count; 94 | 95 | // todo: implement iOS keyboard event 96 | #[derive(Debug, Clone, Eq, PartialEq, Hash)] 97 | pub struct KeyEventExtra {} 98 | 99 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 100 | pub struct DeviceId { 101 | uiscreen: ffi::id, 102 | } 103 | 104 | impl DeviceId { 105 | pub unsafe fn dummy() -> Self { 106 | DeviceId { 107 | uiscreen: std::ptr::null_mut(), 108 | } 109 | } 110 | } 111 | 112 | unsafe impl Send for DeviceId {} 113 | unsafe impl Sync for DeviceId {} 114 | 115 | #[non_exhaustive] 116 | #[derive(Debug)] 117 | pub enum OsError {} 118 | 119 | impl fmt::Display for OsError { 120 | fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result { 121 | unreachable!() 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /examples/overlay.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use tao::{ 6 | event::{ElementState, Event, KeyEvent, WindowEvent}, 7 | event_loop::{ControlFlow, EventLoop}, 8 | keyboard::{Key, ModifiersState}, 9 | window::WindowBuilder, 10 | }; 11 | 12 | #[cfg(any( 13 | target_os = "linux", 14 | target_os = "dragonfly", 15 | target_os = "freebsd", 16 | target_os = "netbsd", 17 | target_os = "openbsd" 18 | ))] 19 | use tao::platform::unix::WindowExtUnix; 20 | 21 | #[cfg(target_os = "macos")] 22 | use tao::platform::macos::WindowExtMacOS; 23 | 24 | #[cfg(target_os = "ios")] 25 | use tao::platform::ios::WindowExtIOS; 26 | 27 | #[cfg(windows)] 28 | use tao::{ 29 | dpi::PhysicalSize, platform::windows::IconExtWindows, platform::windows::WindowExtWindows, 30 | window::Icon, 31 | }; 32 | 33 | #[allow(clippy::single_match)] 34 | fn main() { 35 | env_logger::init(); 36 | let event_loop = EventLoop::new(); 37 | 38 | let window = WindowBuilder::new().build(&event_loop).unwrap(); 39 | 40 | let mut modifiers = ModifiersState::default(); 41 | 42 | eprintln!("Key mappings:"); 43 | #[cfg(windows)] 44 | eprintln!(" [any key]: Show the Overlay Icon"); 45 | #[cfg(not(windows))] 46 | eprintln!(" [1-5]: Show a Badge count"); 47 | eprintln!(" Ctrl+1: Clear"); 48 | 49 | event_loop.run(move |event, _, control_flow| { 50 | *control_flow = ControlFlow::Wait; 51 | 52 | match event { 53 | Event::WindowEvent { 54 | event: WindowEvent::CloseRequested, 55 | .. 56 | } => *control_flow = ControlFlow::Exit, 57 | Event::WindowEvent { event, .. } => match event { 58 | WindowEvent::ModifiersChanged(new_state) => { 59 | modifiers = new_state; 60 | } 61 | WindowEvent::KeyboardInput { 62 | event: 63 | KeyEvent { 64 | logical_key: Key::Character(key_str), 65 | state: ElementState::Released, 66 | .. 67 | }, 68 | .. 69 | } => { 70 | let _count = match key_str { 71 | "1" => 1, 72 | "2" => 2, 73 | "3" => 3, 74 | "4" => 4, 75 | "5" => 5, 76 | _ => 20, 77 | }; 78 | 79 | if modifiers.is_empty() { 80 | #[cfg(windows)] 81 | { 82 | let mut path = std::env::current_dir().unwrap(); 83 | path.push("./examples/icon.ico"); 84 | let icon = Icon::from_path(path, Some(PhysicalSize::new(32, 32))).unwrap(); 85 | 86 | window.set_overlay_icon(Some(&icon)); 87 | } 88 | 89 | #[cfg(any( 90 | target_os = "linux", 91 | target_os = "dragonfly", 92 | target_os = "freebsd", 93 | target_os = "netbsd", 94 | target_os = "openbsd" 95 | ))] 96 | window.set_badge_count(Some(_count), None); 97 | 98 | #[cfg(target_os = "macos")] 99 | window.set_badge_label(_count.to_string().into()); 100 | 101 | #[cfg(target_os = "ios")] 102 | window.set_badge_count(_count); 103 | } else if modifiers.control_key() && key_str == "1" { 104 | #[cfg(windows)] 105 | window.set_overlay_icon(None); 106 | 107 | #[cfg(any( 108 | target_os = "linux", 109 | target_os = "dragonfly", 110 | target_os = "freebsd", 111 | target_os = "netbsd", 112 | target_os = "openbsd" 113 | ))] 114 | window.set_badge_count(None, None); 115 | 116 | #[cfg(target_os = "macos")] 117 | window.set_badge_label(None); 118 | 119 | #[cfg(target_os = "ios")] 120 | window.set_badge_count(0); 121 | } 122 | } 123 | _ => {} 124 | }, 125 | _ => {} 126 | } 127 | }); 128 | } 129 | -------------------------------------------------------------------------------- /examples/control_flow.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use std::{thread, time}; 6 | 7 | use tao::{ 8 | event::{ElementState, Event, KeyEvent, WindowEvent}, 9 | event_loop::{ControlFlow, EventLoop}, 10 | keyboard::Key, 11 | window::WindowBuilder, 12 | }; 13 | 14 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 15 | enum Mode { 16 | Wait, 17 | WaitUntil, 18 | Poll, 19 | } 20 | 21 | const WAIT_TIME: time::Duration = time::Duration::from_millis(100); 22 | const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100); 23 | 24 | #[allow(clippy::single_match)] 25 | fn main() { 26 | env_logger::init(); 27 | 28 | println!("Press '1' to switch to Wait mode."); 29 | println!("Press '2' to switch to WaitUntil mode."); 30 | println!("Press '3' to switch to Poll mode."); 31 | println!("Press 'R' to toggle request_redraw() calls."); 32 | println!("Press 'Esc' to close the window."); 33 | 34 | let event_loop = EventLoop::new(); 35 | let window = WindowBuilder::new() 36 | .with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.") 37 | .build(&event_loop) 38 | .unwrap(); 39 | 40 | let mut mode = Mode::Wait; 41 | let mut request_redraw = false; 42 | let mut wait_cancelled = false; 43 | let mut close_requested = false; 44 | 45 | event_loop.run(move |event, _, control_flow| { 46 | use tao::event::StartCause; 47 | println!("{event:?}"); 48 | match event { 49 | Event::NewEvents(start_cause) => { 50 | wait_cancelled = match start_cause { 51 | StartCause::WaitCancelled { .. } => mode == Mode::WaitUntil, 52 | _ => false, 53 | } 54 | } 55 | Event::WindowEvent { event, .. } => match event { 56 | WindowEvent::CloseRequested => { 57 | close_requested = true; 58 | } 59 | WindowEvent::KeyboardInput { 60 | event: 61 | KeyEvent { 62 | logical_key, 63 | state: ElementState::Pressed, 64 | .. 65 | }, 66 | .. 67 | } => { 68 | // WARNING: Consider using `key_without_modifers()` if available on your platform. 69 | // See the `key_binding` example 70 | if Key::Character("1") == logical_key { 71 | mode = Mode::Wait; 72 | println!("\nmode: {mode:?}\n"); 73 | } 74 | if Key::Character("2") == logical_key { 75 | mode = Mode::WaitUntil; 76 | println!("\nmode: {mode:?}\n"); 77 | } 78 | if Key::Character("3") == logical_key { 79 | mode = Mode::Poll; 80 | println!("\nmode: {mode:?}\n"); 81 | } 82 | if Key::Character("r") == logical_key { 83 | request_redraw = !request_redraw; 84 | println!("\nrequest_redraw: {request_redraw}\n"); 85 | } 86 | if Key::Escape == logical_key { 87 | close_requested = true; 88 | } 89 | } 90 | _ => {} 91 | }, 92 | Event::MainEventsCleared => { 93 | if request_redraw && !wait_cancelled && !close_requested { 94 | window.request_redraw(); 95 | } 96 | if close_requested { 97 | *control_flow = ControlFlow::Exit; 98 | } 99 | } 100 | Event::RedrawRequested(_window_id) => {} 101 | Event::RedrawEventsCleared => { 102 | *control_flow = match mode { 103 | Mode::Wait => ControlFlow::Wait, 104 | Mode::WaitUntil => { 105 | if wait_cancelled { 106 | *control_flow 107 | } else { 108 | ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME) 109 | } 110 | } 111 | Mode::Poll => { 112 | thread::sleep(POLL_SLEEP_TIME); 113 | ControlFlow::Poll 114 | } 115 | }; 116 | } 117 | _ => (), 118 | } 119 | }); 120 | } 121 | -------------------------------------------------------------------------------- /src/platform_impl/windows/dpi.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #![allow(non_snake_case, unused_unsafe)] 6 | 7 | use std::sync::Once; 8 | 9 | use windows::Win32::{ 10 | Foundation::HWND, 11 | Graphics::Gdi::*, 12 | UI::{HiDpi::*, WindowsAndMessaging::*}, 13 | }; 14 | 15 | use crate::platform_impl::platform::util::{ 16 | ENABLE_NON_CLIENT_DPI_SCALING, GET_DPI_FOR_MONITOR, GET_DPI_FOR_WINDOW, SET_PROCESS_DPI_AWARE, 17 | SET_PROCESS_DPI_AWARENESS, SET_PROCESS_DPI_AWARENESS_CONTEXT, 18 | }; 19 | 20 | pub fn become_dpi_aware() { 21 | static ENABLE_DPI_AWARENESS: Once = Once::new(); 22 | ENABLE_DPI_AWARENESS.call_once(|| { 23 | unsafe { 24 | if let Some(SetProcessDpiAwarenessContext) = *SET_PROCESS_DPI_AWARENESS_CONTEXT { 25 | // We are on Windows 10 Anniversary Update (1607) or later. 26 | if !SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2).as_bool() { 27 | // V2 only works with Windows 10 Creators Update (1703). Try using the older 28 | // V1 if we can't set V2. 29 | let _ = SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); 30 | } 31 | } else if let Some(SetProcessDpiAwareness) = *SET_PROCESS_DPI_AWARENESS { 32 | // We are on Windows 8.1 or later. 33 | let _ = SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); 34 | } else if let Some(SetProcessDPIAware) = *SET_PROCESS_DPI_AWARE { 35 | // We are on Vista or later. 36 | let _ = SetProcessDPIAware(); 37 | } 38 | } 39 | }); 40 | } 41 | 42 | pub fn enable_non_client_dpi_scaling(hwnd: HWND) { 43 | unsafe { 44 | if let Some(EnableNonClientDpiScaling) = *ENABLE_NON_CLIENT_DPI_SCALING { 45 | let _ = EnableNonClientDpiScaling(hwnd); 46 | } 47 | } 48 | } 49 | 50 | pub fn get_monitor_dpi(hmonitor: HMONITOR) -> Option { 51 | unsafe { 52 | if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR { 53 | // We are on Windows 8.1 or later. 54 | let mut dpi_x = 0; 55 | let mut dpi_y = 0; 56 | if GetDpiForMonitor(hmonitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y).is_ok() { 57 | // MSDN says that "the values of *dpiX and *dpiY are identical. You only need to 58 | // record one of the values to determine the DPI and respond appropriately". 59 | // https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx 60 | return Some(dpi_x); 61 | } 62 | } 63 | } 64 | None 65 | } 66 | 67 | pub fn dpi_to_scale_factor(dpi: u32) -> f64 { 68 | dpi as f64 / USER_DEFAULT_SCREEN_DPI as f64 69 | } 70 | 71 | pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 { 72 | let hdc = GetDC(Some(hwnd)); 73 | if hdc.is_invalid() { 74 | panic!("[tao] `GetDC` returned null!"); 75 | } 76 | if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW { 77 | // We are on Windows 10 Anniversary Update (1607) or later. 78 | match GetDpiForWindow(hwnd) { 79 | 0 => USER_DEFAULT_SCREEN_DPI, // 0 is returned if hwnd is invalid 80 | dpi => dpi, 81 | } 82 | } else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR { 83 | // We are on Windows 8.1 or later. 84 | let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); 85 | if monitor.is_invalid() { 86 | return USER_DEFAULT_SCREEN_DPI; 87 | } 88 | 89 | let mut dpi_x = 0; 90 | let mut dpi_y = 0; 91 | if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y).is_ok() { 92 | dpi_x 93 | } else { 94 | USER_DEFAULT_SCREEN_DPI 95 | } 96 | } else { 97 | // We are on Vista or later. 98 | if IsProcessDPIAware().as_bool() { 99 | // If the process is DPI aware, then scaling must be handled by the application using 100 | // this DPI value. 101 | GetDeviceCaps(Some(hdc), LOGPIXELSX) as u32 102 | } else { 103 | // If the process is DPI unaware, then scaling is performed by the OS; we thus return 104 | // 96 (scale factor 1.0) to prevent the window from being re-scaled by both the 105 | // application and the WM. 106 | USER_DEFAULT_SCREEN_DPI 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /examples/fullscreen.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use std::io::{stdin, stdout, Write}; 6 | 7 | use tao::{ 8 | event::{ElementState, Event, KeyEvent, WindowEvent}, 9 | event_loop::{ControlFlow, EventLoop}, 10 | keyboard::Key, 11 | monitor::{MonitorHandle, VideoMode}, 12 | window::{Fullscreen, WindowBuilder}, 13 | }; 14 | #[allow(clippy::single_match)] 15 | #[allow(clippy::ok_expect)] 16 | fn main() { 17 | env_logger::init(); 18 | let event_loop = EventLoop::new(); 19 | 20 | print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless, (3) borderless on current monitor: "); 21 | stdout().flush().unwrap(); 22 | 23 | let mut num = String::new(); 24 | stdin().read_line(&mut num).unwrap(); 25 | let num = num.trim().parse().ok().expect("Please enter a number"); 26 | 27 | let fullscreen = Some(match num { 28 | 1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))), 29 | 2 => Fullscreen::Borderless(Some(prompt_for_monitor(&event_loop))), 30 | 3 => Fullscreen::Borderless(None), 31 | _ => panic!("Please enter a valid number"), 32 | }); 33 | 34 | let mut decorations = true; 35 | 36 | let window = WindowBuilder::new() 37 | .with_title("Hello world!") 38 | .with_fullscreen(fullscreen.clone()) 39 | .build(&event_loop) 40 | .unwrap(); 41 | 42 | event_loop.run(move |event, _, control_flow| { 43 | *control_flow = ControlFlow::Wait; 44 | 45 | match event { 46 | Event::WindowEvent { event, .. } => match event { 47 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 48 | WindowEvent::KeyboardInput { 49 | event: 50 | KeyEvent { 51 | logical_key, 52 | state: ElementState::Pressed, 53 | .. 54 | }, 55 | .. 56 | } => { 57 | if Key::Escape == logical_key { 58 | *control_flow = ControlFlow::Exit 59 | } 60 | 61 | if Key::Character("f") == logical_key { 62 | if window.fullscreen().is_some() { 63 | window.set_fullscreen(None); 64 | } else { 65 | window.set_fullscreen(fullscreen.clone()); 66 | } 67 | } 68 | 69 | if Key::Character("s") == logical_key { 70 | println!("window.fullscreen {:?}", window.fullscreen()); 71 | } 72 | if Key::Character("m") == logical_key { 73 | let is_maximized = window.is_maximized(); 74 | window.set_maximized(!is_maximized); 75 | } 76 | if Key::Character("d") == logical_key { 77 | decorations = !decorations; 78 | window.set_decorations(decorations); 79 | } 80 | } 81 | _ => (), 82 | }, 83 | _ => {} 84 | } 85 | }); 86 | } 87 | 88 | // Enumerate monitors and prompt user to choose one 89 | fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle { 90 | for (num, monitor) in event_loop.available_monitors().enumerate() { 91 | println!("Monitor #{}: {:?}", num, monitor.name()); 92 | } 93 | 94 | print!("Please write the number of the monitor to use: "); 95 | stdout().flush().unwrap(); 96 | 97 | let mut num = String::new(); 98 | stdin().read_line(&mut num).unwrap(); 99 | let num = num.trim().parse().expect("Please enter a number"); 100 | let monitor = event_loop 101 | .available_monitors() 102 | .nth(num) 103 | .expect("Please enter a valid ID"); 104 | 105 | println!("Using {:?}", monitor.name()); 106 | 107 | monitor 108 | } 109 | 110 | fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode { 111 | for (i, video_mode) in monitor.video_modes().enumerate() { 112 | println!("Video mode #{i}: {video_mode}"); 113 | } 114 | 115 | print!("Please write the number of the video mode to use: "); 116 | stdout().flush().unwrap(); 117 | 118 | let mut num = String::new(); 119 | stdin().read_line(&mut num).unwrap(); 120 | let num = num.trim().parse().expect("Please enter a number"); 121 | let video_mode = monitor 122 | .video_modes() 123 | .nth(num) 124 | .expect("Please enter a valid ID"); 125 | 126 | println!("Using {video_mode}"); 127 | 128 | video_mode 129 | } 130 | -------------------------------------------------------------------------------- /src/platform_impl/windows/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use windows::Win32::{ 6 | Foundation::{HANDLE, HWND}, 7 | UI::WindowsAndMessaging::HMENU, 8 | }; 9 | 10 | pub(crate) use self::{ 11 | event_loop::{ 12 | EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes, 13 | }, 14 | icon::WinIcon, 15 | keycode::{keycode_from_scancode, keycode_to_scancode}, 16 | monitor::{MonitorHandle, VideoMode}, 17 | window::Window, 18 | }; 19 | 20 | pub use self::icon::WinIcon as PlatformIcon; 21 | 22 | use crate::{event::DeviceId as RootDeviceId, icon::Icon, keyboard::Key}; 23 | mod keycode; 24 | 25 | #[non_exhaustive] 26 | #[derive(Clone)] 27 | pub enum Parent { 28 | None, 29 | ChildOf(HWND), 30 | OwnedBy(HWND), 31 | } 32 | 33 | #[derive(Clone)] 34 | pub struct PlatformSpecificWindowBuilderAttributes { 35 | pub parent: Parent, 36 | pub menu: Option, 37 | pub taskbar_icon: Option, 38 | pub skip_taskbar: bool, 39 | pub window_classname: String, 40 | pub no_redirection_bitmap: bool, 41 | pub drag_and_drop: bool, 42 | pub decoration_shadow: bool, 43 | pub rtl: bool, 44 | } 45 | 46 | impl Default for PlatformSpecificWindowBuilderAttributes { 47 | fn default() -> Self { 48 | Self { 49 | parent: Parent::None, 50 | menu: None, 51 | taskbar_icon: None, 52 | no_redirection_bitmap: false, 53 | drag_and_drop: true, 54 | skip_taskbar: false, 55 | window_classname: "Window Class".to_string(), 56 | decoration_shadow: true, 57 | rtl: false, 58 | } 59 | } 60 | } 61 | 62 | unsafe impl Send for PlatformSpecificWindowBuilderAttributes {} 63 | unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {} 64 | 65 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 66 | pub struct DeviceId(isize); 67 | 68 | impl DeviceId { 69 | pub unsafe fn dummy() -> Self { 70 | DeviceId(0) 71 | } 72 | } 73 | 74 | impl DeviceId { 75 | pub fn persistent_identifier(&self) -> Option { 76 | if self.0 != 0 { 77 | raw_input::get_raw_input_device_name(HANDLE(self.0 as _)) 78 | } else { 79 | None 80 | } 81 | } 82 | } 83 | 84 | #[non_exhaustive] 85 | #[derive(Debug)] 86 | pub enum OsError { 87 | #[allow(unused)] 88 | CreationError(&'static str), 89 | IoError(std::io::Error), 90 | } 91 | impl std::error::Error for OsError {} 92 | 93 | impl std::fmt::Display for OsError { 94 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 95 | match self { 96 | OsError::CreationError(e) => f.pad(e), 97 | OsError::IoError(e) => f.pad(&e.to_string()), 98 | } 99 | } 100 | } 101 | 102 | impl From for OsError { 103 | fn from(value: windows::core::Error) -> Self { 104 | OsError::IoError(value.into()) 105 | } 106 | } 107 | 108 | impl From for crate::error::OsError { 109 | fn from(value: windows::core::Error) -> Self { 110 | os_error!(OsError::IoError(value.into())) 111 | } 112 | } 113 | 114 | impl From for crate::error::ExternalError { 115 | fn from(value: windows::core::Error) -> Self { 116 | crate::error::ExternalError::Os(os_error!(OsError::IoError(value.into()))) 117 | } 118 | } 119 | 120 | // Constant device ID, to be removed when this backend is updated to report real device IDs. 121 | const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId(0)); 122 | 123 | fn wrap_device_id(id: isize) -> RootDeviceId { 124 | RootDeviceId(DeviceId(id)) 125 | } 126 | 127 | #[derive(Debug, Clone, Eq, PartialEq, Hash)] 128 | pub struct KeyEventExtra { 129 | pub text_with_all_modifiers: Option<&'static str>, 130 | pub key_without_modifiers: Key<'static>, 131 | } 132 | 133 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 134 | pub struct WindowId(isize); 135 | unsafe impl Send for WindowId {} 136 | unsafe impl Sync for WindowId {} 137 | 138 | impl WindowId { 139 | pub unsafe fn dummy() -> Self { 140 | WindowId(0) 141 | } 142 | } 143 | 144 | #[macro_use] 145 | mod util; 146 | mod dark_mode; 147 | mod dpi; 148 | mod drop_handler; 149 | mod event_loop; 150 | mod icon; 151 | mod keyboard; 152 | mod keyboard_layout; 153 | mod minimal_ime; 154 | mod monitor; 155 | mod raw_input; 156 | mod window; 157 | mod window_state; 158 | -------------------------------------------------------------------------------- /src/platform_impl/macos/util/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | mod r#async; 6 | mod cursor; 7 | 8 | pub use self::{cursor::*, r#async::*}; 9 | 10 | use std::ops::{BitAnd, Deref}; 11 | 12 | use core_graphics::display::CGDisplay; 13 | use objc2::{ 14 | class, 15 | runtime::{AnyClass as Class, AnyObject as Object, Sel}, 16 | }; 17 | use objc2_app_kit::{NSApp, NSView, NSWindow, NSWindowStyleMask}; 18 | use objc2_foundation::{MainThreadMarker, NSAutoreleasePool, NSPoint, NSRange, NSRect, NSUInteger}; 19 | 20 | use crate::{ 21 | dpi::{LogicalPosition, PhysicalPosition}, 22 | error::ExternalError, 23 | platform_impl::platform::ffi::{self, id, nil, BOOL, YES}, 24 | }; 25 | 26 | // Replace with `!` once stable 27 | #[derive(Debug)] 28 | pub enum Never {} 29 | 30 | pub fn has_flag(bitset: T, flag: T) -> bool 31 | where 32 | T: Copy + PartialEq + BitAnd, 33 | { 34 | bitset & flag == flag 35 | } 36 | 37 | pub const EMPTY_RANGE: NSRange = NSRange { 38 | location: ffi::NSNotFound as NSUInteger, 39 | length: 0, 40 | }; 41 | 42 | #[derive(Debug, PartialEq)] 43 | pub struct IdRef(id); 44 | 45 | impl IdRef { 46 | pub fn new(inner: id) -> IdRef { 47 | IdRef(inner) 48 | } 49 | 50 | #[allow(dead_code)] 51 | pub fn retain(inner: id) -> IdRef { 52 | if inner != nil { 53 | let _: id = unsafe { msg_send![inner, retain] }; 54 | } 55 | IdRef(inner) 56 | } 57 | } 58 | 59 | impl Drop for IdRef { 60 | fn drop(&mut self) { 61 | if self.0 != nil { 62 | unsafe { 63 | let _pool = NSAutoreleasePool::new(); 64 | let () = msg_send![self.0, release]; 65 | }; 66 | } 67 | } 68 | } 69 | 70 | impl Deref for IdRef { 71 | type Target = id; 72 | #[allow(clippy::needless_lifetimes)] 73 | fn deref<'a>(&'a self) -> &'a id { 74 | &self.0 75 | } 76 | } 77 | 78 | impl Clone for IdRef { 79 | fn clone(&self) -> IdRef { 80 | IdRef::retain(self.0) 81 | } 82 | } 83 | 84 | // For consistency with other platforms, this will... 85 | // 1. translate the bottom-left window corner into the top-left window corner 86 | // 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one 87 | pub fn bottom_left_to_top_left(rect: NSRect) -> f64 { 88 | CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) 89 | } 90 | 91 | /// Converts from tao screen-coordinates to macOS screen-coordinates. 92 | /// Tao: top-left is (0, 0) and y increasing downwards 93 | /// macOS: bottom-left is (0, 0) and y increasing upwards 94 | pub fn window_position(position: LogicalPosition) -> NSPoint { 95 | NSPoint::new( 96 | position.x, 97 | CGDisplay::main().pixels_high() as f64 - position.y, 98 | ) 99 | } 100 | 101 | pub fn cursor_position() -> Result, ExternalError> { 102 | let point: NSPoint = unsafe { msg_send![class!(NSEvent), mouseLocation] }; 103 | let y = CGDisplay::main().pixels_high() as f64 - point.y; 104 | let point = LogicalPosition::new(point.x, y); 105 | Ok(point.to_physical(super::monitor::primary_monitor().scale_factor())) 106 | } 107 | 108 | pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class { 109 | let superclass: *const Class = msg_send![this, superclass]; 110 | &*superclass 111 | } 112 | 113 | pub unsafe fn create_input_context(view: &NSView) -> IdRef { 114 | let input_context: id = msg_send![class!(NSTextInputContext), alloc]; 115 | let input_context: id = msg_send![input_context, initWithClient: view]; 116 | IdRef::new(input_context) 117 | } 118 | 119 | #[allow(dead_code)] 120 | pub unsafe fn open_emoji_picker() { 121 | // SAFETY: TODO 122 | let mtm = unsafe { MainThreadMarker::new_unchecked() }; 123 | let () = msg_send![&NSApp(mtm), orderFrontCharacterPalette: nil]; 124 | } 125 | 126 | pub extern "C" fn yes(_: &Object, _: Sel) -> BOOL { 127 | YES 128 | } 129 | 130 | pub unsafe fn toggle_style_mask( 131 | window: &NSWindow, 132 | view: &NSView, 133 | mask: NSWindowStyleMask, 134 | on: bool, 135 | ) { 136 | let current_style_mask = window.styleMask(); 137 | if on { 138 | window.setStyleMask(current_style_mask | mask); 139 | } else { 140 | window.setStyleMask(current_style_mask & (!mask)); 141 | } 142 | 143 | // If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly! 144 | window.makeFirstResponder(Some(view)); 145 | } 146 | -------------------------------------------------------------------------------- /src/platform_impl/macos/app.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use std::{collections::VecDeque, ffi::CStr}; 6 | 7 | use objc2::runtime::{AnyClass as Class, ClassBuilder as ClassDecl, Sel}; 8 | use objc2_app_kit::{self as appkit, NSApplication, NSEvent, NSEventType}; 9 | 10 | use super::{app_state::AppState, event::EventWrapper, util, DEVICE_ID}; 11 | use crate::event::{DeviceEvent, ElementState, Event}; 12 | 13 | pub struct AppClass(pub *const Class); 14 | unsafe impl Send for AppClass {} 15 | unsafe impl Sync for AppClass {} 16 | 17 | lazy_static! { 18 | pub static ref APP_CLASS: AppClass = unsafe { 19 | let superclass = class!(NSApplication); 20 | let mut decl = 21 | ClassDecl::new(CStr::from_bytes_with_nul(b"TaoApp\0").unwrap(), superclass).unwrap(); 22 | 23 | decl.add_method(sel!(sendEvent:), send_event as extern "C" fn(_, _, _)); 24 | 25 | AppClass(decl.register()) 26 | }; 27 | } 28 | 29 | // Normally, holding Cmd + any key never sends us a `keyUp` event for that key. 30 | // Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196) 31 | // Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553) 32 | extern "C" fn send_event(this: &NSApplication, _sel: Sel, event: &NSEvent) { 33 | unsafe { 34 | // For posterity, there are some undocumented event types 35 | // (https://github.com/servo/cocoa-rs/issues/155) 36 | // but that doesn't really matter here. 37 | let event_type = event.r#type(); 38 | let modifier_flags = event.modifierFlags(); 39 | if event_type == appkit::NSKeyUp 40 | && util::has_flag(modifier_flags, appkit::NSEventModifierFlags::Command) 41 | { 42 | if let Some(key_window) = this.keyWindow() { 43 | key_window.sendEvent(event); 44 | } else { 45 | log::debug!("skip sending CMD keyEvent - app has no keyWindow"); 46 | } 47 | } else { 48 | maybe_dispatch_device_event(event); 49 | let superclass = util::superclass(this); 50 | let _: () = msg_send![super(this, superclass), sendEvent: event]; 51 | } 52 | } 53 | } 54 | 55 | unsafe fn maybe_dispatch_device_event(event: &NSEvent) { 56 | let event_type = event.r#type(); 57 | match event_type { 58 | NSEventType::MouseMoved 59 | | NSEventType::LeftMouseDragged 60 | | NSEventType::OtherMouseDragged 61 | | NSEventType::RightMouseDragged => { 62 | let mut events = VecDeque::with_capacity(3); 63 | 64 | let delta_x = event.deltaX() as f64; 65 | let delta_y = event.deltaY() as f64; 66 | 67 | if delta_x != 0.0 { 68 | events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { 69 | device_id: DEVICE_ID, 70 | event: DeviceEvent::Motion { 71 | axis: 0, 72 | value: delta_x, 73 | }, 74 | })); 75 | } 76 | 77 | if delta_y != 0.0 { 78 | events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { 79 | device_id: DEVICE_ID, 80 | event: DeviceEvent::Motion { 81 | axis: 1, 82 | value: delta_y, 83 | }, 84 | })); 85 | } 86 | 87 | if delta_x != 0.0 || delta_y != 0.0 { 88 | events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { 89 | device_id: DEVICE_ID, 90 | event: DeviceEvent::MouseMotion { 91 | delta: (delta_x, delta_y), 92 | }, 93 | })); 94 | } 95 | 96 | AppState::queue_events(events); 97 | } 98 | NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => { 99 | let mut events = VecDeque::with_capacity(1); 100 | 101 | events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { 102 | device_id: DEVICE_ID, 103 | event: DeviceEvent::Button { 104 | button: event.buttonNumber() as u32, 105 | state: ElementState::Pressed, 106 | }, 107 | })); 108 | 109 | AppState::queue_events(events); 110 | } 111 | NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => { 112 | let mut events = VecDeque::with_capacity(1); 113 | 114 | events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { 115 | device_id: DEVICE_ID, 116 | event: DeviceEvent::Button { 117 | button: event.buttonNumber() as u32, 118 | state: ElementState::Released, 119 | }, 120 | })); 121 | 122 | AppState::queue_events(events); 123 | } 124 | _ => (), 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tao" 3 | version = "0.34.5" 4 | description = "Cross-platform window manager library." 5 | authors = [ 6 | "Tauri Programme within The Commons Conservancy", 7 | "The winit contributors" 8 | ] 9 | edition = "2021" 10 | rust-version = "1.74" 11 | keywords = [ "windowing" ] 12 | license = "Apache-2.0" 13 | readme = "README.md" 14 | repository = "https://github.com/tauri-apps/tao" 15 | documentation = "https://docs.rs/tao" 16 | categories = [ "gui" ] 17 | include = ["/README.md", "src/**/*.rs", "examples/**/*.rs", "LICENSE*"] 18 | 19 | [package.metadata.docs.rs] 20 | features = [ "rwh_04", "rwh_05", "rwh_06", "serde", "x11" ] 21 | default-target = "x86_64-unknown-linux-gnu" 22 | targets = [ 23 | "i686-pc-windows-msvc", 24 | "x86_64-pc-windows-msvc", 25 | "i686-unknown-linux-gnu", 26 | "x86_64-unknown-linux-gnu", 27 | "x86_64-apple-darwin" 28 | ] 29 | 30 | [features] 31 | default = [ "rwh_06", "x11" ] 32 | serde = [ "dep:serde", "dpi/serde" ] 33 | rwh_04 = [ "dep:rwh_04" ] 34 | rwh_05 = [ "dep:rwh_05" ] 35 | rwh_06 = [ "dep:rwh_06" ] 36 | x11 = [ "dep:gdkx11-sys", "dep:x11-dl" ] 37 | 38 | [workspace] 39 | members = [ "tao-macros" ] 40 | 41 | [dependencies] 42 | lazy_static = "1" 43 | libc = "0.2" 44 | log = "0.4" 45 | serde = { version = "1", optional = true, features = [ "serde_derive" ] } 46 | rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true } 47 | rwh_05 = { package = "raw-window-handle", version = "0.5", features = [ "std" ], optional = true } 48 | rwh_06 = { package = "raw-window-handle", version = "0.6", features = [ "std" ], optional = true } 49 | bitflags = "2" 50 | crossbeam-channel = "0.5" 51 | url = "2" 52 | dpi = "0.1" 53 | 54 | [dev-dependencies] 55 | image = "0.25" 56 | env_logger = "0.11" 57 | 58 | [target."cfg(target_os = \"windows\")".dev-dependencies] 59 | softbuffer = "0.4" 60 | 61 | [target."cfg(target_os = \"windows\")".dependencies] 62 | parking_lot = "0.12" 63 | unicode-segmentation = "1.11" 64 | windows-version = "0.1" 65 | windows-core = "0.61" 66 | 67 | [target."cfg(target_os = \"windows\")".dependencies.windows] 68 | version = "0.61" 69 | features = [ 70 | "Win32_Devices_HumanInterfaceDevice", 71 | "Win32_Foundation", 72 | "Win32_Globalization", 73 | "Win32_Graphics_Dwm", 74 | "Win32_Graphics_Gdi", 75 | "Win32_System_Com", 76 | "Win32_System_Com_StructuredStorage", 77 | "Win32_System_DataExchange", 78 | "Win32_System_Diagnostics_Debug", 79 | "Win32_System_LibraryLoader", 80 | "Win32_System_Memory", 81 | "Win32_System_Ole", 82 | "Win32_System_SystemServices", 83 | "Win32_System_Threading", 84 | "Win32_System_WindowsProgramming", 85 | "Win32_System_SystemInformation", 86 | "Win32_UI_Accessibility", 87 | "Win32_UI_Controls", 88 | "Win32_UI_HiDpi", 89 | "Win32_UI_Input_Ime", 90 | "Win32_UI_Input_KeyboardAndMouse", 91 | "Win32_UI_Input_Pointer", 92 | "Win32_UI_Input_Touch", 93 | "Win32_UI_Shell", 94 | "Win32_UI_TextServices", 95 | "Win32_UI_WindowsAndMessaging" 96 | ] 97 | 98 | [target."cfg(any(target_os = \"android\", target_os = \"windows\"))".dependencies] 99 | once_cell = "1" 100 | 101 | [target."cfg(target_os = \"android\")".dependencies] 102 | jni = "0.21" 103 | ndk = "0.9" 104 | ndk-sys = "0.6" 105 | ndk-context = "0.1" 106 | tao-macros = { version = "0.1.0", path = "./tao-macros" } 107 | 108 | [target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies] 109 | objc2 = "0.6" 110 | block2 = "0.6" 111 | 112 | [target."cfg(target_os = \"macos\")".dependencies] 113 | objc2-foundation = { version = "0.3", default-features = false, features = [ 114 | "std", 115 | "NSArray", 116 | "NSAttributedString", 117 | "NSAutoreleasePool", 118 | "NSDate", 119 | "NSDictionary", 120 | "NSEnumerator", 121 | "NSGeometry", 122 | "NSObjCRuntime", 123 | "NSRange", 124 | "NSString", 125 | "NSThread", 126 | "NSURL", 127 | ] } 128 | objc2-app-kit = { version = "0.3", default-features = false, features = [ 129 | "std", 130 | "objc2-core-foundation", 131 | "NSApplication", 132 | "NSButton", 133 | "NSColor", 134 | "NSControl", 135 | "NSEvent", 136 | "NSGraphics", 137 | "NSImage", 138 | "NSOpenGLView", 139 | "NSPasteboard", 140 | "NSResponder", 141 | "NSRunningApplication", 142 | "NSScreen", 143 | "NSView", 144 | "NSWindow", 145 | "NSUserActivity" 146 | ] } 147 | core-foundation = "0.10" 148 | core-graphics = "0.24" 149 | dispatch = "0.2" 150 | scopeguard = "1.2" 151 | 152 | [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] 153 | gtk = "0.18" 154 | gdkx11-sys = { version = "0.18", optional = true } 155 | gdkwayland-sys = "0.18.0" 156 | x11-dl = { version = "2.21", optional = true } 157 | parking_lot = "0.12" 158 | dlopen2 = "0.8.0" 159 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | tests: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | platform: 11 | # Windows 12 | - { id: windows, target: x86_64-pc-windows-msvc, os: windows-latest } 13 | # Ubuntu with default features 14 | - { id: ubuntu, target: x86_64-unknown-linux-gnu, os: ubuntu-latest } 15 | # macOS 16 | - { id: macos, target: x86_64-apple-darwin, os: macos-latest } 17 | # Android on Ubuntu 18 | - { 19 | id: android, 20 | target: aarch64-linux-android, 21 | os: ubuntu-latest, 22 | cmd: "apk --", 23 | } 24 | # iOS on Ubuntu 25 | - { id: ios, target: aarch64-apple-ios, os: macos-latest } 26 | features: 27 | - "" # default features 28 | - "serde" # serde + default features 29 | - "rwh_04,rwh_05,rwh_06" # rwh features 30 | 31 | env: 32 | RUST_BACKTRACE: 1 33 | CARGO_INCREMENTAL: 0 34 | RUSTFLAGS: "-C debuginfo=0" 35 | OPTIONS: ${{ matrix.platform.options }} 36 | FEATURES: ${{ format(',{0}', matrix.features ) }} 37 | CMD: ${{ matrix.platform.cmd }} 38 | 39 | runs-on: ${{ matrix.platform.os }} 40 | steps: 41 | - uses: actions/checkout@v4 42 | 43 | - uses: dtolnay/rust-toolchain@stable 44 | 45 | - uses: Swatinem/rust-cache@v2 46 | 47 | - name: Install Gtk (ubuntu only) 48 | if: matrix.platform.os == 'ubuntu-latest' 49 | run: | 50 | sudo apt-get update 51 | sudo apt-get install -y libgtk-3-dev 52 | 53 | - name: Install GCC Multilib 54 | if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') 55 | run: sudo apt-get install gcc-multilib 56 | 57 | - name: Install cargo-apk 58 | if: contains(matrix.platform.target, 'android') 59 | run: cargo install cargo-apk 60 | 61 | - uses: dtolnay/rust-toolchain@1.74 62 | with: 63 | targets: ${{ matrix.platform.target }} 64 | 65 | - name: Check documentation 66 | shell: bash 67 | run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES 68 | 69 | - name: Build 70 | shell: bash 71 | run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES 72 | 73 | - name: Build tests 74 | shell: bash 75 | run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES 76 | 77 | - name: Run tests 78 | shell: bash 79 | if: ( 80 | !contains(matrix.platform.target, 'android') && 81 | !contains(matrix.platform.target, 'ios')) 82 | run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES 83 | 84 | - name: Build with all features enabled 85 | shell: bash 86 | run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES 87 | 88 | - name: Build tests with all features enabled 89 | shell: bash 90 | run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES 91 | 92 | - name: Run tests with all features enabled 93 | shell: bash 94 | if: ( 95 | !contains(matrix.platform.target, 'android') && 96 | !contains(matrix.platform.target, 'ios')) 97 | run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES 98 | 99 | - uses: dtolnay/rust-toolchain@nightly 100 | if: ( 101 | !contains(matrix.platform.target, 'android') && 102 | !contains(matrix.platform.target, 'ios')) 103 | with: 104 | targets: ${{ matrix.platform.target }} 105 | components: miri 106 | 107 | - name: Run tests with miri 108 | if: ( 109 | !contains(matrix.platform.target, 'android') && 110 | !contains(matrix.platform.target, 'ios')) 111 | shell: bash 112 | run: cargo +nightly miri test --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES 113 | 114 | test_tao_macros: 115 | runs-on: ubuntu-latest 116 | steps: 117 | - uses: actions/checkout@v4 118 | 119 | - uses: dtolnay/rust-toolchain@stable 120 | 121 | - uses: Swatinem/rust-cache@v2 122 | 123 | - name: Run tests 124 | shell: bash 125 | run: cargo test --package tao-macros --examples 126 | 127 | fmt: 128 | name: fmt check 129 | runs-on: ubuntu-latest 130 | steps: 131 | - uses: actions/checkout@v4 132 | - uses: dtolnay/rust-toolchain@stable 133 | - run: cargo fmt --all -- --check 134 | -------------------------------------------------------------------------------- /src/platform_impl/windows/icon.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use std::{fmt, io, iter::once, mem, os::windows::ffi::OsStrExt, path::Path, sync::Arc}; 6 | 7 | use windows::{ 8 | core::PCWSTR, 9 | Win32::{ 10 | Foundation::{HWND, LPARAM, WPARAM}, 11 | System::LibraryLoader::*, 12 | UI::WindowsAndMessaging::*, 13 | }, 14 | }; 15 | 16 | use crate::{dpi::PhysicalSize, icon::*}; 17 | 18 | impl Pixel { 19 | fn to_bgra(&mut self) { 20 | mem::swap(&mut self.r, &mut self.b); 21 | } 22 | } 23 | 24 | impl RgbaIcon { 25 | fn into_windows_icon(self) -> Result { 26 | let mut rgba = self.rgba; 27 | let pixel_count = rgba.len() / PIXEL_SIZE; 28 | let mut and_mask = Vec::with_capacity(pixel_count); 29 | let pixels = 30 | unsafe { std::slice::from_raw_parts_mut(rgba.as_mut_ptr() as *mut Pixel, pixel_count) }; 31 | for pixel in pixels { 32 | and_mask.push(pixel.a.wrapping_sub(u8::MAX)); // invert alpha channel 33 | pixel.to_bgra(); 34 | } 35 | assert_eq!(and_mask.len(), pixel_count); 36 | let handle = unsafe { 37 | CreateIcon( 38 | None, 39 | self.width as i32, 40 | self.height as i32, 41 | 1, 42 | (PIXEL_SIZE * 8) as u8, 43 | and_mask.as_ptr(), 44 | rgba.as_ptr(), 45 | ) 46 | }; 47 | Ok(WinIcon::from_handle( 48 | handle.map_err(|_| BadIcon::OsError(io::Error::last_os_error()))?, 49 | )) 50 | } 51 | } 52 | 53 | #[non_exhaustive] 54 | #[derive(Debug)] 55 | pub enum IconType { 56 | Small = ICON_SMALL as isize, 57 | Big = ICON_BIG as isize, 58 | } 59 | 60 | #[derive(Debug)] 61 | struct RaiiIcon { 62 | handle: HICON, 63 | } 64 | 65 | #[derive(Clone)] 66 | pub struct WinIcon { 67 | inner: Arc, 68 | } 69 | 70 | unsafe impl Send for WinIcon {} 71 | 72 | impl WinIcon { 73 | pub fn as_raw_handle(&self) -> HICON { 74 | self.inner.handle 75 | } 76 | 77 | pub fn from_path>( 78 | path: P, 79 | size: Option>, 80 | ) -> Result { 81 | let wide_path: Vec = path 82 | .as_ref() 83 | .as_os_str() 84 | .encode_wide() 85 | .chain(once(0)) 86 | .collect(); 87 | 88 | // width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size 89 | let (width, height) = size.map(Into::into).unwrap_or((0, 0)); 90 | 91 | let handle = unsafe { 92 | LoadImageW( 93 | None, 94 | PCWSTR::from_raw(wide_path.as_ptr()), 95 | IMAGE_ICON, 96 | width, 97 | height, 98 | LR_DEFAULTSIZE | LR_LOADFROMFILE, 99 | ) 100 | } 101 | .map(|handle| HICON(handle.0)); 102 | Ok(WinIcon::from_handle( 103 | handle.map_err(|_| BadIcon::OsError(io::Error::last_os_error()))?, 104 | )) 105 | } 106 | 107 | pub fn from_resource(resource_id: u16, size: Option>) -> Result { 108 | // width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size 109 | let (width, height) = size.map(Into::into).unwrap_or((0, 0)); 110 | let handle = unsafe { 111 | LoadImageW( 112 | GetModuleHandleW(PCWSTR::null()).map(Into::into).ok(), 113 | PCWSTR::from_raw(resource_id as usize as *const u16), 114 | IMAGE_ICON, 115 | width, 116 | height, 117 | LR_DEFAULTSIZE, 118 | ) 119 | } 120 | .map(|handle| HICON(handle.0)); 121 | Ok(WinIcon::from_handle( 122 | handle.map_err(|_| BadIcon::OsError(io::Error::last_os_error()))?, 123 | )) 124 | } 125 | 126 | pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { 127 | let rgba_icon = RgbaIcon::from_rgba(rgba, width, height)?; 128 | rgba_icon.into_windows_icon() 129 | } 130 | 131 | pub fn set_for_window(&self, hwnd: HWND, icon_type: IconType) { 132 | unsafe { 133 | SendMessageW( 134 | hwnd, 135 | WM_SETICON, 136 | Some(WPARAM(icon_type as _)), 137 | Some(LPARAM(self.as_raw_handle().0 as _)), 138 | ); 139 | } 140 | } 141 | 142 | fn from_handle(handle: HICON) -> Self { 143 | Self { 144 | inner: Arc::new(RaiiIcon { handle }), 145 | } 146 | } 147 | } 148 | 149 | impl Drop for RaiiIcon { 150 | fn drop(&mut self) { 151 | let _ = unsafe { DestroyIcon(self.handle) }; 152 | } 153 | } 154 | 155 | impl fmt::Debug for WinIcon { 156 | fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 157 | (*self.inner).fmt(formatter) 158 | } 159 | } 160 | 161 | pub fn unset_for_window(hwnd: HWND, icon_type: IconType) { 162 | unsafe { 163 | SendMessageW( 164 | hwnd, 165 | WM_SETICON, 166 | Some(WPARAM(icon_type as _)), 167 | Some(LPARAM(0)), 168 | ); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/platform_impl/linux/x11/xdisplay.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use std::{collections::HashMap, error::Error, fmt, os::raw::c_int, ptr}; 6 | 7 | use libc; 8 | use parking_lot::Mutex; 9 | 10 | use crate::window::CursorIcon; 11 | 12 | use super::ffi; 13 | 14 | /// A connection to an X server. 15 | pub struct XConnection { 16 | pub xlib: ffi::Xlib, 17 | /// Exposes XRandR functions from version < 1.5 18 | pub xrandr: ffi::Xrandr_2_2_0, 19 | /// Exposes XRandR functions from version = 1.5 20 | pub xrandr_1_5: Option, 21 | pub xcursor: ffi::Xcursor, 22 | pub xinput2: ffi::XInput2, 23 | pub xlib_xcb: ffi::Xlib_xcb, 24 | pub xrender: ffi::Xrender, 25 | pub display: *mut ffi::Display, 26 | pub x11_fd: c_int, 27 | pub latest_error: Mutex>, 28 | pub cursor_cache: Mutex, ffi::Cursor>>, 29 | } 30 | 31 | unsafe impl Send for XConnection {} 32 | unsafe impl Sync for XConnection {} 33 | 34 | pub type XErrorHandler = 35 | Option libc::c_int>; 36 | 37 | impl XConnection { 38 | pub fn new(error_handler: XErrorHandler) -> Result { 39 | // opening the libraries 40 | let xlib = ffi::Xlib::open()?; 41 | let xcursor = ffi::Xcursor::open()?; 42 | let xrandr = ffi::Xrandr_2_2_0::open()?; 43 | let xrandr_1_5 = ffi::Xrandr::open().ok(); 44 | let xinput2 = ffi::XInput2::open()?; 45 | let xlib_xcb = ffi::Xlib_xcb::open()?; 46 | let xrender = ffi::Xrender::open()?; 47 | 48 | unsafe { (xlib.XInitThreads)() }; 49 | unsafe { (xlib.XSetErrorHandler)(error_handler) }; 50 | 51 | // calling XOpenDisplay 52 | let display = unsafe { 53 | let display = (xlib.XOpenDisplay)(ptr::null()); 54 | if display.is_null() { 55 | return Err(XNotSupported::XOpenDisplayFailed); 56 | } 57 | display 58 | }; 59 | 60 | // Get X11 socket file descriptor 61 | let fd = unsafe { (xlib.XConnectionNumber)(display) }; 62 | 63 | Ok(XConnection { 64 | xlib, 65 | xrandr, 66 | xrandr_1_5, 67 | xcursor, 68 | xinput2, 69 | xlib_xcb, 70 | xrender, 71 | display, 72 | x11_fd: fd, 73 | latest_error: Mutex::new(None), 74 | cursor_cache: Default::default(), 75 | }) 76 | } 77 | 78 | /// Checks whether an error has been triggered by the previous function calls. 79 | #[inline] 80 | pub fn check_errors(&self) -> Result<(), XError> { 81 | let error = self.latest_error.lock().take(); 82 | if let Some(error) = error { 83 | Err(error) 84 | } else { 85 | Ok(()) 86 | } 87 | } 88 | 89 | /// Ignores any previous error. 90 | #[inline] 91 | pub fn ignore_error(&self) { 92 | *self.latest_error.lock() = None; 93 | } 94 | } 95 | 96 | impl fmt::Debug for XConnection { 97 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 98 | self.display.fmt(f) 99 | } 100 | } 101 | 102 | impl Drop for XConnection { 103 | #[inline] 104 | fn drop(&mut self) { 105 | unsafe { (self.xlib.XCloseDisplay)(self.display) }; 106 | } 107 | } 108 | 109 | /// Error triggered by xlib. 110 | #[derive(Debug, Clone)] 111 | pub struct XError { 112 | pub description: String, 113 | pub error_code: u8, 114 | pub request_code: u8, 115 | pub minor_code: u8, 116 | } 117 | 118 | impl Error for XError {} 119 | 120 | impl fmt::Display for XError { 121 | fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 122 | write!( 123 | formatter, 124 | "X error: {} (code: {}, request code: {}, minor code: {})", 125 | self.description, self.error_code, self.request_code, self.minor_code 126 | ) 127 | } 128 | } 129 | 130 | /// Error returned if this system doesn't have XLib or can't create an X connection. 131 | #[derive(Clone, Debug)] 132 | pub enum XNotSupported { 133 | /// Failed to load one or several shared libraries. 134 | LibraryOpenError(ffi::OpenError), 135 | /// Connecting to the X server with `XOpenDisplay` failed. 136 | XOpenDisplayFailed, // TODO: add better message 137 | } 138 | 139 | impl From for XNotSupported { 140 | #[inline] 141 | fn from(err: ffi::OpenError) -> XNotSupported { 142 | XNotSupported::LibraryOpenError(err) 143 | } 144 | } 145 | 146 | impl XNotSupported { 147 | fn description(&self) -> &'static str { 148 | match self { 149 | XNotSupported::LibraryOpenError(_) => "Failed to load one of xlib's shared libraries", 150 | XNotSupported::XOpenDisplayFailed => "Failed to open connection to X server", 151 | } 152 | } 153 | } 154 | 155 | impl Error for XNotSupported { 156 | #[inline] 157 | fn source(&self) -> Option<&(dyn Error + 'static)> { 158 | match *self { 159 | XNotSupported::LibraryOpenError(ref err) => Some(err), 160 | _ => None, 161 | } 162 | } 163 | } 164 | 165 | impl fmt::Display for XNotSupported { 166 | fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 167 | formatter.write_str(self.description()) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/platform_impl/linux/taskbar.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_char, CString}; 2 | 3 | use dlopen2::wrapper::{Container, WrapperApi}; 4 | 5 | use crate::window::{ProgressBarState, ProgressState}; 6 | 7 | #[derive(WrapperApi)] 8 | struct UnityLib { 9 | unity_launcher_entry_get_for_desktop_id: unsafe extern "C" fn(id: *const c_char) -> *const isize, 10 | unity_inspector_get_default: unsafe extern "C" fn() -> *const isize, 11 | unity_inspector_get_unity_running: unsafe extern "C" fn(inspector: *const isize) -> i32, 12 | unity_launcher_entry_set_progress: unsafe extern "C" fn(entry: *const isize, value: f64) -> i32, 13 | unity_launcher_entry_set_progress_visible: 14 | unsafe extern "C" fn(entry: *const isize, value: i32) -> i32, 15 | unity_launcher_entry_set_count: unsafe extern "C" fn(entry: *const isize, value: i64) -> i32, 16 | unity_launcher_entry_set_count_visible: 17 | unsafe extern "C" fn(entry: *const isize, value: bool) -> bool, 18 | } 19 | 20 | pub struct TaskbarIndicator { 21 | desktop_filename: Option, 22 | desktop_filename_c_str: Option, 23 | 24 | unity_lib: Option>, 25 | attempted_load: bool, 26 | 27 | unity_inspector: Option<*const isize>, 28 | unity_entry: Option<*const isize>, 29 | } 30 | 31 | impl TaskbarIndicator { 32 | pub fn new() -> Self { 33 | Self { 34 | desktop_filename: None, 35 | desktop_filename_c_str: None, 36 | 37 | unity_lib: None, 38 | attempted_load: false, 39 | 40 | unity_inspector: None, 41 | unity_entry: None, 42 | } 43 | } 44 | 45 | fn ensure_lib_load(&mut self) { 46 | if self.attempted_load { 47 | return; 48 | } 49 | 50 | self.attempted_load = true; 51 | 52 | self.unity_lib = unsafe { 53 | Container::load("libunity.so.4") 54 | .or_else(|_| Container::load("libunity.so.6")) 55 | .or_else(|_| Container::load("libunity.so.9")) 56 | .ok() 57 | }; 58 | 59 | if let Some(unity_lib) = &self.unity_lib { 60 | let handle = unsafe { unity_lib.unity_inspector_get_default() }; 61 | if !handle.is_null() { 62 | self.unity_inspector = Some(handle); 63 | } 64 | } 65 | } 66 | 67 | fn ensure_entry_load(&mut self) { 68 | if let Some(unity_lib) = &self.unity_lib { 69 | if let Some(id) = &self.desktop_filename_c_str { 70 | let handle = unsafe { unity_lib.unity_launcher_entry_get_for_desktop_id(id.as_ptr()) }; 71 | if !handle.is_null() { 72 | self.unity_entry = Some(handle); 73 | } 74 | } 75 | } 76 | } 77 | 78 | fn is_unity_running(&self) -> bool { 79 | if let Some(inspector) = self.unity_inspector { 80 | if let Some(unity_lib) = &self.unity_lib { 81 | return unsafe { unity_lib.unity_inspector_get_unity_running(inspector) } == 1; 82 | } 83 | } 84 | 85 | false 86 | } 87 | 88 | pub fn update(&mut self, progress: ProgressBarState) { 89 | if let Some(uri) = progress.desktop_filename { 90 | self.desktop_filename = Some(uri); 91 | } 92 | 93 | self.ensure_lib_load(); 94 | 95 | if !self.is_unity_running() { 96 | return; 97 | } 98 | 99 | if let Some(uri) = &self.desktop_filename { 100 | self.desktop_filename_c_str = Some(CString::new(uri.as_str()).unwrap_or_default()); 101 | } 102 | 103 | if self.unity_entry.is_none() { 104 | self.ensure_entry_load(); 105 | } 106 | if let Some(unity_lib) = &self.unity_lib { 107 | if let Some(unity_entry) = &self.unity_entry { 108 | if let Some(progress) = progress.progress { 109 | let progress = if progress > 100 { 100 } else { progress }; 110 | let progress = progress as f64 / 100.0; 111 | unsafe { (unity_lib.unity_launcher_entry_set_progress)(*unity_entry, progress) }; 112 | } 113 | 114 | if let Some(state) = progress.state { 115 | let is_visible = !matches!(state, ProgressState::None); 116 | unsafe { 117 | (unity_lib.unity_launcher_entry_set_progress_visible)( 118 | *unity_entry, 119 | if is_visible { 1 } else { 0 }, 120 | ) 121 | }; 122 | } 123 | } 124 | } 125 | } 126 | 127 | pub fn update_count(&mut self, count: Option, desktop_filename: Option) { 128 | if let Some(uri) = desktop_filename { 129 | self.desktop_filename = Some(uri); 130 | } 131 | 132 | self.ensure_lib_load(); 133 | 134 | if !self.is_unity_running() { 135 | return; 136 | } 137 | 138 | if let Some(uri) = &self.desktop_filename { 139 | self.desktop_filename_c_str = Some(CString::new(uri.as_str()).unwrap_or_default()); 140 | } 141 | 142 | if self.unity_entry.is_none() { 143 | self.ensure_entry_load(); 144 | } 145 | 146 | if let Some(unity_lib) = &self.unity_lib { 147 | if let Some(unity_entry) = &self.unity_entry { 148 | // Sets count 149 | if let Some(count) = count { 150 | unsafe { (unity_lib.unity_launcher_entry_set_count)(*unity_entry, count) }; 151 | unsafe { (unity_lib.unity_launcher_entry_set_count_visible)(*unity_entry, true) }; 152 | } 153 | // removes the count 154 | else { 155 | unsafe { (unity_lib.unity_launcher_entry_set_count)(*unity_entry, 0) }; 156 | unsafe { (unity_lib.unity_launcher_entry_set_count_visible)(*unity_entry, false) }; 157 | } 158 | } 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/monitor.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | //! Types useful for interacting with a user's monitors. 6 | //! 7 | //! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_handle] 8 | //! type. This is retrieved from one of the following methods, which return an iterator of 9 | //! [`MonitorHandle`][monitor_handle]: 10 | //! - [`EventLoopWindowTarget::available_monitors`][loop_get] 11 | //! - [`Window::available_monitors`][window_get]. 12 | //! 13 | //! [monitor_handle]: crate::monitor::MonitorHandle 14 | //! [loop_get]: crate::event_loop::EventLoopWindowTarget::available_monitors 15 | //! [window_get]: crate::window::Window::available_monitors 16 | use crate::{ 17 | dpi::{PhysicalPosition, PhysicalSize}, 18 | platform_impl, 19 | }; 20 | 21 | /// Describes a fullscreen video mode of a monitor. 22 | /// 23 | /// Can be acquired with: 24 | /// - [`MonitorHandle::video_modes`][monitor_get]. 25 | /// 26 | /// [monitor_get]: crate::monitor::MonitorHandle::video_modes 27 | #[derive(Clone, PartialEq, Eq, Hash)] 28 | pub struct VideoMode { 29 | pub(crate) video_mode: platform_impl::VideoMode, 30 | } 31 | 32 | impl std::fmt::Debug for VideoMode { 33 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 34 | self.video_mode.fmt(f) 35 | } 36 | } 37 | 38 | impl PartialOrd for VideoMode { 39 | fn partial_cmp(&self, other: &VideoMode) -> Option { 40 | Some(self.cmp(other)) 41 | } 42 | } 43 | 44 | impl Ord for VideoMode { 45 | fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering { 46 | // TODO: we can impl `Ord` for `PhysicalSize` once we switch from `f32` 47 | // to `u32` there 48 | let size: (u32, u32) = self.size().into(); 49 | let other_size: (u32, u32) = other.size().into(); 50 | self.monitor().cmp(&other.monitor()).then( 51 | size 52 | .cmp(&other_size) 53 | .then( 54 | self 55 | .refresh_rate() 56 | .cmp(&other.refresh_rate()) 57 | .then(self.bit_depth().cmp(&other.bit_depth())), 58 | ) 59 | .reverse(), 60 | ) 61 | } 62 | } 63 | 64 | impl VideoMode { 65 | /// Returns the resolution of this video mode. 66 | #[inline] 67 | pub fn size(&self) -> PhysicalSize { 68 | self.video_mode.size() 69 | } 70 | 71 | /// Returns the bit depth of this video mode, as in how many bits you have 72 | /// available per color. This is generally 24 bits or 32 bits on modern 73 | /// systems, depending on whether the alpha channel is counted or not. 74 | /// 75 | /// ## Platform-specific 76 | /// - **iOS:** Always returns 32. 77 | #[inline] 78 | pub fn bit_depth(&self) -> u16 { 79 | self.video_mode.bit_depth() 80 | } 81 | 82 | /// Returns the refresh rate of this video mode. **Note**: the returned 83 | /// refresh rate is an integer approximation, and you shouldn't rely on this 84 | /// value to be exact. 85 | #[inline] 86 | pub fn refresh_rate(&self) -> u16 { 87 | self.video_mode.refresh_rate() 88 | } 89 | 90 | /// Returns the monitor that this video mode is valid for. Each monitor has 91 | /// a separate set of valid video modes. 92 | #[inline] 93 | pub fn monitor(&self) -> MonitorHandle { 94 | self.video_mode.monitor() 95 | } 96 | } 97 | 98 | impl std::fmt::Display for VideoMode { 99 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 100 | write!( 101 | f, 102 | "{}x{} @ {} Hz ({} bpp)", 103 | self.size().width, 104 | self.size().height, 105 | self.refresh_rate(), 106 | self.bit_depth() 107 | ) 108 | } 109 | } 110 | 111 | /// Handle to a monitor. 112 | /// 113 | /// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation. 114 | /// 115 | /// [`Window`]: crate::window::Window 116 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] 117 | pub struct MonitorHandle { 118 | pub(crate) inner: platform_impl::MonitorHandle, 119 | } 120 | 121 | impl MonitorHandle { 122 | /// Returns a human-readable name of the monitor. 123 | /// 124 | /// Returns `None` if the monitor doesn't exist anymore. 125 | #[inline] 126 | pub fn name(&self) -> Option { 127 | self.inner.name() 128 | } 129 | 130 | /// Returns the monitor's resolution. 131 | #[inline] 132 | pub fn size(&self) -> PhysicalSize { 133 | self.inner.size() 134 | } 135 | 136 | /// Returns the top-left corner position of the monitor relative to the larger full 137 | /// screen area. 138 | #[inline] 139 | pub fn position(&self) -> PhysicalPosition { 140 | self.inner.position() 141 | } 142 | 143 | /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. 144 | /// 145 | /// See the [`dpi`](crate::dpi) module for more information. 146 | /// 147 | /// ## Platform-specific 148 | /// 149 | /// - **Android:** Always returns 1.0. 150 | #[inline] 151 | pub fn scale_factor(&self) -> f64 { 152 | self.inner.scale_factor() 153 | } 154 | 155 | /// Returns all fullscreen video modes supported by this monitor. 156 | /// 157 | /// ## Platform-specific 158 | /// - **Linux:** Unsupported. This will always return empty iterator. 159 | #[inline] 160 | pub fn video_modes(&self) -> impl Iterator { 161 | self.inner.video_modes() 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/platform_impl/macos/progress_bar.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CStr, sync::Once}; 2 | 3 | use objc2::{ 4 | msg_send, 5 | rc::Retained, 6 | runtime::{AnyClass as Class, AnyObject as Object, ClassBuilder as ClassDecl, Sel}, 7 | }; 8 | use objc2_foundation::{NSArray, NSInsetRect, NSPoint, NSRect, NSSize}; 9 | 10 | use super::ffi::{id, nil, NO}; 11 | use crate::window::{ProgressBarState, ProgressState}; 12 | 13 | /// Set progress indicator in the Dock. 14 | pub fn set_progress_indicator(progress_state: ProgressBarState) { 15 | unsafe { 16 | let ns_app: id = msg_send![class!(NSApplication), sharedApplication]; 17 | let dock_tile: id = msg_send![ns_app, dockTile]; 18 | if dock_tile == nil { 19 | return; 20 | } 21 | 22 | // check progress indicator is already set or create new one 23 | let progress_indicator: id = get_exist_progress_indicator(dock_tile) 24 | .unwrap_or_else(|| create_progress_indicator(ns_app, dock_tile)); 25 | 26 | // set progress indicator state 27 | if let Some(progress) = progress_state.progress { 28 | let progress = progress.clamp(0, 100) as f64; 29 | let _: () = msg_send![progress_indicator, setDoubleValue: progress]; 30 | let _: () = msg_send![progress_indicator, setHidden: NO]; 31 | } 32 | #[allow(deprecated)] // TODO: Use define_class! 33 | if let Some(state) = progress_state.state { 34 | *(*progress_indicator).get_mut_ivar("state") = state as u8; 35 | let _: () = msg_send![ 36 | progress_indicator, 37 | setHidden: matches!(state, ProgressState::None) 38 | ]; 39 | } 40 | 41 | let _: () = msg_send![dock_tile, display]; 42 | } 43 | } 44 | 45 | fn create_progress_indicator(ns_app: id, dock_tile: id) -> id { 46 | unsafe { 47 | let mut image_view: id = msg_send![dock_tile, contentView]; 48 | if image_view == nil { 49 | // create new dock tile view with current app icon 50 | let app_icon_image: id = msg_send![ns_app, applicationIconImage]; 51 | image_view = msg_send![class!(NSImageView), imageViewWithImage: app_icon_image]; 52 | let _: () = msg_send![dock_tile, setContentView: image_view]; 53 | } 54 | 55 | // create custom progress indicator 56 | let dock_tile_size: NSSize = msg_send![dock_tile, size]; 57 | let frame = NSRect::new( 58 | NSPoint::new(0.0, 0.0), 59 | NSSize::new(dock_tile_size.width, 15.0), 60 | ); 61 | let progress_class = create_progress_indicator_class(); 62 | let progress_indicator: id = msg_send![progress_class, alloc]; 63 | let progress_indicator: id = msg_send![progress_indicator, initWithFrame: frame]; 64 | let _: id = msg_send![progress_indicator, autorelease]; 65 | 66 | // set progress indicator to the dock tile 67 | let _: () = msg_send![image_view, addSubview: progress_indicator]; 68 | 69 | progress_indicator 70 | } 71 | } 72 | 73 | fn get_exist_progress_indicator(dock_tile: id) -> Option { 74 | unsafe { 75 | let content_view: id = msg_send![dock_tile, contentView]; 76 | if content_view == nil { 77 | return None; 78 | } 79 | let subviews: Option> = msg_send![content_view, subviews]; 80 | let subviews = subviews?; 81 | 82 | for idx in 0..subviews.count() { 83 | let subview: id = msg_send![&subviews, objectAtIndex: idx]; 84 | 85 | let is_progress_indicator: bool = 86 | msg_send![subview, isKindOfClass: class!(NSProgressIndicator)]; 87 | if is_progress_indicator { 88 | return Some(subview); 89 | } 90 | } 91 | } 92 | None 93 | } 94 | 95 | fn create_progress_indicator_class() -> *const Class { 96 | static mut APP_CLASS: *const Class = 0 as *const Class; 97 | static INIT: Once = Once::new(); 98 | 99 | INIT.call_once(|| unsafe { 100 | let superclass = class!(NSProgressIndicator); 101 | let mut decl = ClassDecl::new( 102 | CStr::from_bytes_with_nul(b"TaoProgressIndicator\0").unwrap(), 103 | superclass, 104 | ) 105 | .unwrap(); 106 | 107 | decl.add_method(sel!(drawRect:), draw_progress_bar as extern "C" fn(_, _, _)); 108 | 109 | // progress bar states, follows ProgressState 110 | decl.add_ivar::(CStr::from_bytes_with_nul(b"state\0").unwrap()); 111 | 112 | APP_CLASS = decl.register(); 113 | }); 114 | 115 | unsafe { APP_CLASS } 116 | } 117 | 118 | extern "C" fn draw_progress_bar(this: &Object, _: Sel, rect: NSRect) { 119 | #[allow(deprecated)] // TODO: Use define_class! 120 | unsafe { 121 | let bar = NSRect::new( 122 | NSPoint { x: 0.0, y: 4.0 }, 123 | NSSize { 124 | width: rect.size.width, 125 | height: 8.0, 126 | }, 127 | ); 128 | let bar_inner = NSInsetRect(bar, 0.5, 0.5); 129 | let mut bar_progress = NSInsetRect(bar, 1.0, 1.0); 130 | 131 | // set progress width 132 | let current_progress: f64 = msg_send![this, doubleValue]; 133 | let normalized_progress: f64 = (current_progress / 100.0).clamp(0.0, 1.0); 134 | bar_progress.size.width *= normalized_progress; 135 | 136 | // draw outer bar 137 | let bg_color: id = msg_send![class!(NSColor), colorWithWhite:1.0 alpha:0.05]; 138 | let _: () = msg_send![bg_color, set]; 139 | draw_rounded_rect(bar); 140 | // draw inner bar 141 | draw_rounded_rect(bar_inner); 142 | 143 | // draw progress 144 | let state: u8 = *(this.get_ivar("state")); 145 | let progress_color: id = match state { 146 | x if x == ProgressState::Paused as u8 => msg_send![class!(NSColor), systemYellowColor], 147 | x if x == ProgressState::Error as u8 => msg_send![class!(NSColor), systemRedColor], 148 | _ => msg_send![class!(NSColor), systemBlueColor], 149 | }; 150 | let _: () = msg_send![progress_color, set]; 151 | draw_rounded_rect(bar_progress); 152 | } 153 | } 154 | 155 | fn draw_rounded_rect(rect: NSRect) { 156 | unsafe { 157 | let raduis = rect.size.height / 2.0; 158 | let bezier_path: id = msg_send![class!(NSBezierPath), bezierPathWithRoundedRect:rect, xRadius:raduis, yRadius:raduis]; 159 | let _: () = msg_send![bezier_path, fill]; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/icon.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use crate::platform_impl::PlatformIcon; 6 | use std::{error::Error, fmt, io, mem}; 7 | 8 | #[repr(C)] 9 | #[derive(Debug)] 10 | pub(crate) struct Pixel { 11 | pub(crate) r: u8, 12 | pub(crate) g: u8, 13 | pub(crate) b: u8, 14 | pub(crate) a: u8, 15 | } 16 | 17 | pub(crate) const PIXEL_SIZE: usize = mem::size_of::(); 18 | 19 | #[non_exhaustive] 20 | #[derive(Debug)] 21 | /// An error produced when using `Icon::from_rgba` with invalid arguments. 22 | pub enum BadIcon { 23 | /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be 24 | /// safely interpreted as 32bpp RGBA pixels. 25 | #[non_exhaustive] 26 | ByteCountNotDivisibleBy4 { byte_count: usize }, 27 | /// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`. 28 | /// At least one of your arguments is incorrect. 29 | #[non_exhaustive] 30 | DimensionsVsPixelCount { 31 | width: u32, 32 | height: u32, 33 | width_x_height: usize, 34 | pixel_count: usize, 35 | }, 36 | /// Produced when the provided icon width or height is equal to zero. 37 | #[non_exhaustive] 38 | DimensionsZero { width: u32, height: u32 }, 39 | /// Produced when the provided icon width or height is equal to zero. 40 | #[non_exhaustive] 41 | DimensionsMultiplyOverflow { width: u32, height: u32 }, 42 | /// Produced when underlying OS functionality failed to create the icon 43 | OsError(io::Error), 44 | } 45 | 46 | impl fmt::Display for BadIcon { 47 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 48 | match self { 49 | BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!(f, 50 | "The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.", 51 | ), 52 | BadIcon::DimensionsVsPixelCount { 53 | width, 54 | height, 55 | width_x_height, 56 | pixel_count, 57 | } => write!(f, 58 | "The specified dimensions ({width:?}x{height:?}) don't match the number of pixels supplied by the `rgba` argument ({pixel_count:?}). For those dimensions, the expected pixel count is {width_x_height:?}.", 59 | ), 60 | BadIcon::DimensionsZero { 61 | width, 62 | height, 63 | } => write!(f, 64 | "The specified dimensions ({width:?}x{height:?}) must be greater than zero." 65 | ), 66 | BadIcon::DimensionsMultiplyOverflow { 67 | width, 68 | height, 69 | } => write!(f, 70 | "The specified dimensions multiplication has overflowed ({width:?}x{height:?})." 71 | ), 72 | BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {e:?}"), 73 | } 74 | } 75 | } 76 | 77 | impl Error for BadIcon { 78 | fn source(&self) -> Option<&(dyn Error + 'static)> { 79 | Some(self) 80 | } 81 | } 82 | 83 | #[derive(Debug, Clone, PartialEq, Eq)] 84 | pub(crate) struct RgbaIcon { 85 | pub(crate) rgba: Vec, 86 | pub(crate) width: u32, 87 | pub(crate) height: u32, 88 | } 89 | 90 | /// For platforms which don't have window icons (e.g. web) 91 | #[derive(Debug, Clone, PartialEq, Eq)] 92 | pub(crate) struct NoIcon; 93 | 94 | #[allow(dead_code)] // These are not used on every platform 95 | mod constructors { 96 | use super::*; 97 | 98 | impl RgbaIcon { 99 | /// Creates an `Icon` from 32bpp RGBA data. 100 | /// 101 | /// The length of `rgba` must be divisible by 4, and `width * height` must equal 102 | /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error. 103 | pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { 104 | if width == 0 || height == 0 { 105 | return Err(BadIcon::DimensionsZero { width, height }); 106 | } 107 | 108 | if rgba.len() % PIXEL_SIZE != 0 { 109 | return Err(BadIcon::ByteCountNotDivisibleBy4 { 110 | byte_count: rgba.len(), 111 | }); 112 | } 113 | let width_usize = width as usize; 114 | let height_usize = height as usize; 115 | let width_x_height = match width_usize.checked_mul(height_usize) { 116 | Some(v) => v, 117 | None => return Err(BadIcon::DimensionsMultiplyOverflow { width, height }), 118 | }; 119 | 120 | let pixel_count = rgba.len() / PIXEL_SIZE; 121 | if pixel_count != width_x_height { 122 | Err(BadIcon::DimensionsVsPixelCount { 123 | width, 124 | height, 125 | width_x_height, 126 | pixel_count, 127 | }) 128 | } else { 129 | Ok(RgbaIcon { 130 | rgba, 131 | width, 132 | height, 133 | }) 134 | } 135 | } 136 | } 137 | 138 | impl NoIcon { 139 | pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { 140 | // Create the rgba icon anyway to validate the input 141 | let _ = RgbaIcon::from_rgba(rgba, width, height)?; 142 | Ok(NoIcon) 143 | } 144 | } 145 | } 146 | 147 | /// An icon used for the window titlebar, taskbar, etc. 148 | #[derive(Clone)] 149 | pub struct Icon { 150 | pub(crate) inner: PlatformIcon, 151 | } 152 | 153 | impl fmt::Debug for Icon { 154 | fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 155 | fmt::Debug::fmt(&self.inner, formatter) 156 | } 157 | } 158 | 159 | impl Icon { 160 | /// Creates an `Icon` from 32bpp RGBA data. 161 | /// 162 | /// The length of `rgba` must be divisible by 4, and `width * height` must equal 163 | /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error. 164 | pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { 165 | Ok(Icon { 166 | inner: PlatformIcon::from_rgba(rgba, width, height)?, 167 | }) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /examples/window_debug.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // This example is used by developers to test various window functions. 6 | 7 | use tao::{ 8 | dpi::{LogicalSize, PhysicalSize}, 9 | event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, WindowEvent}, 10 | event_loop::{ControlFlow, DeviceEventFilter, EventLoop}, 11 | keyboard::{Key, KeyCode}, 12 | window::{Fullscreen, WindowBuilder}, 13 | }; 14 | 15 | #[allow(clippy::single_match)] 16 | #[allow(clippy::collapsible_match)] 17 | fn main() { 18 | env_logger::init(); 19 | let event_loop = EventLoop::new(); 20 | event_loop.set_device_event_filter(DeviceEventFilter::Never); 21 | 22 | let window = WindowBuilder::new() 23 | .with_title("A fantastic window!") 24 | .with_inner_size(LogicalSize::new(100.0, 100.0)) 25 | .build(&event_loop) 26 | .unwrap(); 27 | 28 | eprintln!("debugging keys:"); 29 | eprintln!(" (E) Enter exclusive fullscreen"); 30 | eprintln!(" (F) Toggle borderless fullscreen"); 31 | eprintln!(" (P) Toggle borderless fullscreen on system's preferred monitor"); 32 | eprintln!(" (V) Toggle visibility"); 33 | eprintln!(" (T) Toggle always on top"); 34 | eprintln!(" (B) Toggle always on bottom"); 35 | eprintln!(" (C) Toggle content protection"); 36 | eprintln!(" (R) Toggle resizable"); 37 | eprintln!(" (M) Toggle minimized"); 38 | eprintln!(" (X) Toggle maximized"); 39 | eprintln!(" (Q) Quit event loop"); 40 | eprintln!(" (Shift + M) Toggle minimizable"); 41 | eprintln!(" (Shift + X) Toggle maximizable"); 42 | eprintln!(" (Shift + Q) Toggle closable"); 43 | 44 | let mut always_on_bottom = false; 45 | let mut always_on_top = false; 46 | let mut visible = true; 47 | let mut content_protection = false; 48 | let mut resizable = false; 49 | 50 | event_loop.run(move |event, _, control_flow| { 51 | *control_flow = ControlFlow::Wait; 52 | 53 | match event { 54 | // This used to use the virtual key, but the new API 55 | // only provides the `physical_key` (`Code`). 56 | Event::DeviceEvent { 57 | event: 58 | DeviceEvent::Key(RawKeyEvent { 59 | physical_key, 60 | state: ElementState::Released, 61 | .. 62 | }), 63 | .. 64 | } => match physical_key { 65 | KeyCode::KeyM => { 66 | if window.is_minimized() { 67 | window.set_minimized(false); 68 | window.set_focus() 69 | } 70 | } 71 | KeyCode::KeyV => { 72 | if !visible { 73 | visible = !visible; 74 | window.set_visible(visible); 75 | } 76 | } 77 | _ => (), 78 | }, 79 | Event::WindowEvent { 80 | event: 81 | WindowEvent::KeyboardInput { 82 | event: 83 | KeyEvent { 84 | logical_key: Key::Character(key_str), 85 | state: ElementState::Released, 86 | .. 87 | }, 88 | .. 89 | }, 90 | .. 91 | } => match key_str { 92 | // WARNING: Consider using `key_without_modifers()` if available on your platform. 93 | // See the `key_binding` example 94 | "e" => { 95 | fn area(size: PhysicalSize) -> u32 { 96 | size.width * size.height 97 | } 98 | 99 | let monitor = window.current_monitor().unwrap(); 100 | if let Some(mode) = monitor 101 | .video_modes() 102 | .max_by(|a, b| area(a.size()).cmp(&area(b.size()))) 103 | { 104 | window.set_fullscreen(Some(Fullscreen::Exclusive(mode))); 105 | } else { 106 | eprintln!("no video modes available"); 107 | } 108 | } 109 | "f" => { 110 | if window.fullscreen().is_some() { 111 | window.set_fullscreen(None); 112 | } else { 113 | let monitor = window.current_monitor(); 114 | window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); 115 | } 116 | } 117 | "p" => { 118 | if window.fullscreen().is_some() { 119 | window.set_fullscreen(None); 120 | } else { 121 | window.set_fullscreen(Some(Fullscreen::Borderless(None))); 122 | } 123 | } 124 | "r" => { 125 | resizable = !resizable; 126 | window.set_resizable(resizable); 127 | println!("Resizable: {resizable}"); 128 | } 129 | "m" => { 130 | window.set_minimized(!window.is_minimized()); 131 | } 132 | "q" => { 133 | *control_flow = ControlFlow::Exit; 134 | } 135 | "v" => { 136 | visible = !visible; 137 | window.set_visible(visible); 138 | } 139 | "x" => { 140 | window.set_maximized(!window.is_maximized()); 141 | } 142 | "t" => { 143 | always_on_top = !always_on_top; 144 | window.set_always_on_top(always_on_top); 145 | } 146 | "b" => { 147 | always_on_bottom = !always_on_bottom; 148 | window.set_always_on_bottom(always_on_bottom); 149 | } 150 | "c" => { 151 | content_protection = !content_protection; 152 | window.set_content_protection(content_protection); 153 | } 154 | "M" => { 155 | let minimizable = !window.is_minimizable(); 156 | window.set_minimizable(minimizable); 157 | } 158 | "X" => { 159 | let maximizable = !window.is_maximizable(); 160 | window.set_maximizable(maximizable); 161 | } 162 | "Q" => { 163 | let closable = !window.is_closable(); 164 | window.set_closable(closable); 165 | } 166 | _ => (), 167 | }, 168 | Event::WindowEvent { 169 | event: WindowEvent::CloseRequested, 170 | window_id, 171 | .. 172 | } if window_id == window.id() => *control_flow = ControlFlow::Exit, 173 | _ => (), 174 | } 175 | }); 176 | } 177 | -------------------------------------------------------------------------------- /src/platform_impl/windows/drop_handler.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use std::{cell::UnsafeCell, ffi::OsString, os::windows::ffi::OsStringExt, path::PathBuf, ptr}; 6 | 7 | use windows::{ 8 | core::implement, 9 | Win32::{ 10 | Foundation::{self as win32f, HWND, POINTL}, 11 | System::{ 12 | Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL}, 13 | Ole::{ 14 | IDropTarget, IDropTarget_Impl, CF_HDROP, DROPEFFECT, DROPEFFECT_COPY, DROPEFFECT_NONE, 15 | }, 16 | SystemServices::MODIFIERKEYS_FLAGS, 17 | }, 18 | UI::Shell::{DragFinish, DragQueryFileW, HDROP}, 19 | }, 20 | }; 21 | 22 | use crate::platform_impl::platform::WindowId; 23 | 24 | use crate::{event::Event, window::WindowId as SuperWindowId}; 25 | 26 | #[implement(IDropTarget)] 27 | pub struct FileDropHandler { 28 | window: HWND, 29 | send_event: Box)>, 30 | cursor_effect: UnsafeCell, 31 | hovered_is_valid: UnsafeCell, /* If the currently hovered item is not valid there must not be any `HoveredFileCancelled` emitted */ 32 | } 33 | 34 | impl FileDropHandler { 35 | pub fn new(window: HWND, send_event: Box)>) -> FileDropHandler { 36 | Self { 37 | window, 38 | send_event, 39 | cursor_effect: DROPEFFECT_NONE.into(), 40 | hovered_is_valid: false.into(), 41 | } 42 | } 43 | 44 | unsafe fn iterate_filenames( 45 | data_obj: windows_core::Ref<'_, IDataObject>, 46 | callback: F, 47 | ) -> Option 48 | where 49 | F: Fn(PathBuf), 50 | { 51 | let drop_format = FORMATETC { 52 | cfFormat: CF_HDROP.0, 53 | ptd: ptr::null_mut(), 54 | dwAspect: DVASPECT_CONTENT.0, 55 | lindex: -1, 56 | tymed: TYMED_HGLOBAL.0 as u32, 57 | }; 58 | 59 | match data_obj 60 | .as_ref() 61 | .expect("Received null IDataObject") 62 | .GetData(&drop_format) 63 | { 64 | Ok(medium) => { 65 | let hglobal = medium.u.hGlobal; 66 | let hdrop = HDROP(hglobal.0 as _); 67 | 68 | // The second parameter (0xFFFFFFFF) instructs the function to return the item count 69 | let mut lpsz_file = []; 70 | let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, Some(&mut lpsz_file)); 71 | 72 | for i in 0..item_count { 73 | // Get the length of the path string NOT including the terminating null character. 74 | // Previously, this was using a fixed size array of MAX_PATH length, but the 75 | // Windows API allows longer paths under certain circumstances. 76 | let character_count = DragQueryFileW(hdrop, i, Some(&mut lpsz_file)) as usize; 77 | let str_len = character_count + 1; 78 | 79 | // Fill path_buf with the null-terminated file name 80 | let mut path_buf = Vec::with_capacity(str_len); 81 | DragQueryFileW(hdrop, i, std::mem::transmute(path_buf.spare_capacity_mut())); 82 | path_buf.set_len(str_len); 83 | 84 | callback(OsString::from_wide(&path_buf[0..character_count]).into()); 85 | } 86 | 87 | Some(hdrop) 88 | } 89 | Err(error) => { 90 | debug!( 91 | "{}", 92 | match error.code() { 93 | win32f::DV_E_FORMATETC => { 94 | // If the dropped item is not a file this error will occur. 95 | // In this case it is OK to return without taking further action. 96 | "Error occured while processing dropped/hovered item: item is not a file." 97 | } 98 | _ => "Unexpected error occured while processing dropped/hovered item.", 99 | } 100 | ); 101 | None 102 | } 103 | } 104 | } 105 | } 106 | 107 | #[allow(non_snake_case)] 108 | impl IDropTarget_Impl for FileDropHandler_Impl { 109 | fn DragEnter( 110 | &self, 111 | pDataObj: windows_core::Ref<'_, IDataObject>, 112 | _grfKeyState: MODIFIERKEYS_FLAGS, 113 | _pt: &POINTL, 114 | pdwEffect: *mut DROPEFFECT, 115 | ) -> windows::core::Result<()> { 116 | use crate::event::WindowEvent::HoveredFile; 117 | unsafe { 118 | let hdrop = FileDropHandler::iterate_filenames(pDataObj, |filename| { 119 | (self.send_event)(Event::WindowEvent { 120 | window_id: SuperWindowId(WindowId(self.window.0 as _)), 121 | event: HoveredFile(filename), 122 | }); 123 | }); 124 | let hovered_is_valid = hdrop.is_some(); 125 | let cursor_effect = if hovered_is_valid { 126 | DROPEFFECT_COPY 127 | } else { 128 | DROPEFFECT_NONE 129 | }; 130 | *self.hovered_is_valid.get() = hovered_is_valid; 131 | *self.cursor_effect.get() = cursor_effect; 132 | *pdwEffect = cursor_effect; 133 | } 134 | Ok(()) 135 | } 136 | 137 | fn DragOver( 138 | &self, 139 | _grfKeyState: MODIFIERKEYS_FLAGS, 140 | _pt: &POINTL, 141 | pdwEffect: *mut DROPEFFECT, 142 | ) -> windows::core::Result<()> { 143 | unsafe { 144 | *pdwEffect = *self.cursor_effect.get(); 145 | } 146 | Ok(()) 147 | } 148 | 149 | fn DragLeave(&self) -> windows::core::Result<()> { 150 | use crate::event::WindowEvent::HoveredFileCancelled; 151 | if unsafe { *self.hovered_is_valid.get() } { 152 | (self.send_event)(Event::WindowEvent { 153 | window_id: SuperWindowId(WindowId(self.window.0 as _)), 154 | event: HoveredFileCancelled, 155 | }); 156 | } 157 | Ok(()) 158 | } 159 | 160 | fn Drop( 161 | &self, 162 | pDataObj: windows_core::Ref<'_, IDataObject>, 163 | _grfKeyState: MODIFIERKEYS_FLAGS, 164 | _pt: &POINTL, 165 | _pdwEffect: *mut DROPEFFECT, 166 | ) -> windows::core::Result<()> { 167 | use crate::event::WindowEvent::DroppedFile; 168 | unsafe { 169 | let hdrop = FileDropHandler::iterate_filenames(pDataObj, |filename| { 170 | (self.send_event)(Event::WindowEvent { 171 | window_id: SuperWindowId(WindowId(self.window.0 as _)), 172 | event: DroppedFile(filename), 173 | }); 174 | }); 175 | if let Some(hdrop) = hdrop { 176 | DragFinish(hdrop); 177 | } 178 | } 179 | Ok(()) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/platform_impl/windows/raw_input.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | use std::mem::{self, size_of}; 6 | 7 | use windows::Win32::{ 8 | Devices::HumanInterfaceDevice::*, 9 | Foundation::{HANDLE, HWND}, 10 | UI::{ 11 | Input::{self as win32i, *}, 12 | WindowsAndMessaging::*, 13 | }, 14 | }; 15 | 16 | use crate::{event::ElementState, event_loop::DeviceEventFilter, platform_impl::platform::util}; 17 | 18 | #[allow(dead_code)] 19 | pub fn get_raw_input_device_list() -> Option> { 20 | let list_size = size_of::() as u32; 21 | 22 | let mut num_devices = 0; 23 | let status = unsafe { GetRawInputDeviceList(None, &mut num_devices, list_size) }; 24 | 25 | if status == u32::MAX { 26 | return None; 27 | } 28 | 29 | let mut buffer = Vec::with_capacity(num_devices as _); 30 | 31 | let num_stored = 32 | unsafe { GetRawInputDeviceList(Some(buffer.as_ptr() as _), &mut num_devices, list_size) }; 33 | 34 | if num_stored == u32::MAX { 35 | return None; 36 | } 37 | 38 | debug_assert_eq!(num_devices, num_stored); 39 | 40 | unsafe { buffer.set_len(num_devices as _) }; 41 | 42 | Some(buffer) 43 | } 44 | 45 | #[non_exhaustive] 46 | #[allow(dead_code)] 47 | pub enum RawDeviceInfo { 48 | Mouse(RID_DEVICE_INFO_MOUSE), 49 | Keyboard(RID_DEVICE_INFO_KEYBOARD), 50 | Hid(RID_DEVICE_INFO_HID), 51 | } 52 | 53 | impl From for RawDeviceInfo { 54 | fn from(info: RID_DEVICE_INFO) -> Self { 55 | unsafe { 56 | match info.dwType { 57 | win32i::RIM_TYPEMOUSE => RawDeviceInfo::Mouse(info.Anonymous.mouse), 58 | win32i::RIM_TYPEKEYBOARD => RawDeviceInfo::Keyboard(info.Anonymous.keyboard), 59 | win32i::RIM_TYPEHID => RawDeviceInfo::Hid(info.Anonymous.hid), 60 | _ => unreachable!(), 61 | } 62 | } 63 | } 64 | } 65 | 66 | #[allow(dead_code)] 67 | pub fn get_raw_input_device_info(handle: HANDLE) -> Option { 68 | let mut info: RID_DEVICE_INFO = unsafe { mem::zeroed() }; 69 | let info_size = size_of::() as u32; 70 | 71 | info.cbSize = info_size; 72 | 73 | let mut minimum_size = 0; 74 | let status = unsafe { 75 | GetRawInputDeviceInfoW( 76 | Some(handle), 77 | RIDI_DEVICEINFO, 78 | Some(&mut info as *mut _ as _), 79 | &mut minimum_size, 80 | ) 81 | }; 82 | 83 | if status == u32::MAX || status == 0 { 84 | return None; 85 | } 86 | 87 | debug_assert_eq!(info_size, status); 88 | 89 | Some(info.into()) 90 | } 91 | 92 | pub fn get_raw_input_device_name(handle: HANDLE) -> Option { 93 | let mut minimum_size = 0; 94 | let status = 95 | unsafe { GetRawInputDeviceInfoW(Some(handle), RIDI_DEVICENAME, None, &mut minimum_size) }; 96 | 97 | if status != 0 { 98 | return None; 99 | } 100 | 101 | let mut name: Vec = Vec::with_capacity(minimum_size as _); 102 | 103 | let status = unsafe { 104 | GetRawInputDeviceInfoW( 105 | Some(handle), 106 | RIDI_DEVICENAME, 107 | Some(name.as_ptr() as _), 108 | &mut minimum_size, 109 | ) 110 | }; 111 | 112 | if status == u32::MAX || status == 0 { 113 | return None; 114 | } 115 | 116 | debug_assert_eq!(minimum_size, status); 117 | 118 | unsafe { name.set_len(minimum_size as _) }; 119 | 120 | Some(util::wchar_to_string(&name)) 121 | } 122 | 123 | pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool { 124 | let device_size = size_of::() as u32; 125 | unsafe { RegisterRawInputDevices(devices, device_size) }.is_ok() 126 | } 127 | 128 | pub fn register_all_mice_and_keyboards_for_raw_input( 129 | mut window_handle: HWND, 130 | filter: DeviceEventFilter, 131 | ) -> bool { 132 | // RIDEV_DEVNOTIFY: receive hotplug events 133 | // RIDEV_INPUTSINK: receive events even if we're not in the foreground 134 | // RIDEV_REMOVE: don't receive device events (requires NULL hwndTarget) 135 | let flags = match filter { 136 | DeviceEventFilter::Always => { 137 | window_handle = HWND(std::ptr::null_mut()); 138 | RIDEV_REMOVE 139 | } 140 | DeviceEventFilter::Unfocused => RIDEV_DEVNOTIFY, 141 | DeviceEventFilter::Never => RIDEV_DEVNOTIFY | RIDEV_INPUTSINK, 142 | }; 143 | 144 | let devices: [RAWINPUTDEVICE; 2] = [ 145 | RAWINPUTDEVICE { 146 | usUsagePage: HID_USAGE_PAGE_GENERIC, 147 | usUsage: HID_USAGE_GENERIC_MOUSE, 148 | dwFlags: flags, 149 | hwndTarget: window_handle, 150 | }, 151 | RAWINPUTDEVICE { 152 | usUsagePage: HID_USAGE_PAGE_GENERIC, 153 | usUsage: HID_USAGE_GENERIC_KEYBOARD, 154 | dwFlags: flags, 155 | hwndTarget: window_handle, 156 | }, 157 | ]; 158 | 159 | register_raw_input_devices(&devices) 160 | } 161 | 162 | pub fn get_raw_input_data(handle: HRAWINPUT) -> Option { 163 | let mut data: RAWINPUT = unsafe { mem::zeroed() }; 164 | let mut data_size = size_of::() as u32; 165 | let header_size = size_of::() as u32; 166 | 167 | let status = unsafe { 168 | GetRawInputData( 169 | handle, 170 | RID_INPUT, 171 | Some(&mut data as *mut _ as _), 172 | &mut data_size, 173 | header_size, 174 | ) 175 | }; 176 | 177 | if status == u32::MAX || status == 0 { 178 | return None; 179 | } 180 | 181 | Some(data) 182 | } 183 | 184 | fn button_flags_to_element_state( 185 | button_flags: u16, 186 | down_flag: u32, 187 | up_flag: u32, 188 | ) -> Option { 189 | // We assume the same button won't be simultaneously pressed and released. 190 | if util::has_flag(button_flags, down_flag as u16) { 191 | Some(ElementState::Pressed) 192 | } else if util::has_flag(button_flags, up_flag as u16) { 193 | Some(ElementState::Released) 194 | } else { 195 | None 196 | } 197 | } 198 | 199 | pub fn get_raw_mouse_button_state(button_flags: u16) -> [Option; 3] { 200 | [ 201 | button_flags_to_element_state( 202 | button_flags, 203 | RI_MOUSE_LEFT_BUTTON_DOWN, 204 | RI_MOUSE_LEFT_BUTTON_UP, 205 | ), 206 | button_flags_to_element_state( 207 | button_flags, 208 | RI_MOUSE_MIDDLE_BUTTON_DOWN, 209 | RI_MOUSE_MIDDLE_BUTTON_UP, 210 | ), 211 | button_flags_to_element_state( 212 | button_flags, 213 | RI_MOUSE_RIGHT_BUTTON_DOWN, 214 | RI_MOUSE_RIGHT_BUTTON_UP, 215 | ), 216 | ] 217 | } 218 | -------------------------------------------------------------------------------- /examples/multithreaded.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2021 The winit contributors 2 | // Copyright 2021-2023 Tauri Programme within The Commons Conservancy 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #[allow(clippy::single_match)] 6 | #[allow(clippy::iter_nth)] 7 | fn main() { 8 | use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; 9 | 10 | use tao::{ 11 | dpi::{PhysicalPosition, PhysicalSize, Position, Size}, 12 | event::{ElementState, Event, KeyEvent, WindowEvent}, 13 | event_loop::{ControlFlow, EventLoop}, 14 | keyboard::{Key, ModifiersState}, 15 | window::{CursorIcon, Fullscreen, WindowBuilder}, 16 | }; 17 | 18 | const WINDOW_COUNT: usize = 3; 19 | const WINDOW_SIZE: PhysicalSize = PhysicalSize::new(600, 400); 20 | 21 | env_logger::init(); 22 | let event_loop = EventLoop::new(); 23 | let mut window_senders = HashMap::with_capacity(WINDOW_COUNT); 24 | for _ in 0..WINDOW_COUNT { 25 | let window = WindowBuilder::new() 26 | .with_inner_size(WINDOW_SIZE) 27 | .build(&event_loop) 28 | .unwrap(); 29 | 30 | let mut video_modes: Vec<_> = window.current_monitor().unwrap().video_modes().collect(); 31 | let mut video_mode_id = 0usize; 32 | 33 | let (tx, rx) = mpsc::channel(); 34 | window_senders.insert(window.id(), tx); 35 | let mut modifiers = ModifiersState::default(); 36 | thread::spawn(move || { 37 | while let Ok(event) = rx.recv() { 38 | match event { 39 | WindowEvent::Moved { .. } => { 40 | // We need to update our chosen video mode if the window 41 | // was moved to an another monitor, so that the window 42 | // appears on this monitor instead when we go fullscreen 43 | let previous_video_mode = video_modes.iter().nth(video_mode_id).cloned(); 44 | video_modes = window.current_monitor().unwrap().video_modes().collect(); 45 | video_mode_id = video_mode_id.min(video_modes.len()); 46 | let video_mode = video_modes.iter().nth(video_mode_id); 47 | 48 | // Different monitors may support different video modes, 49 | // and the index we chose previously may now point to a 50 | // completely different video mode, so notify the user 51 | if video_mode != previous_video_mode.as_ref() { 52 | println!( 53 | "Window moved to another monitor, picked video mode: {}", 54 | video_modes.iter().nth(video_mode_id).unwrap() 55 | ); 56 | } 57 | } 58 | WindowEvent::ModifiersChanged(mod_state) => { 59 | modifiers = mod_state; 60 | } 61 | WindowEvent::KeyboardInput { 62 | event: 63 | KeyEvent { 64 | state: ElementState::Released, 65 | logical_key: key, 66 | .. 67 | }, 68 | .. 69 | } => { 70 | use Key::{ArrowLeft, ArrowRight, Character}; 71 | window.set_title(&format!("{key:?}")); 72 | let state = !modifiers.shift_key(); 73 | match &key { 74 | // WARNING: Consider using `key_without_modifers()` if available on your platform. 75 | // See the `key_binding` example 76 | Character(string) => match string.to_lowercase().as_str() { 77 | "a" => window.set_always_on_top(state), 78 | "c" => window.set_cursor_icon(match state { 79 | true => CursorIcon::Progress, 80 | false => CursorIcon::Default, 81 | }), 82 | "d" => window.set_decorations(!state), 83 | "f" => window.set_fullscreen(match (state, modifiers.alt_key()) { 84 | (true, false) => Some(Fullscreen::Borderless(None)), 85 | (true, true) => Some(Fullscreen::Exclusive( 86 | video_modes.iter().nth(video_mode_id).unwrap().clone(), 87 | )), 88 | (false, _) => None, 89 | }), 90 | "g" => window.set_cursor_grab(state).unwrap(), 91 | "h" => window.set_cursor_visible(!state), 92 | "i" => { 93 | println!("Info:"); 94 | println!("-> outer_position : {:?}", window.outer_position()); 95 | println!("-> inner_position : {:?}", window.inner_position()); 96 | println!("-> outer_size : {:?}", window.outer_size()); 97 | println!("-> inner_size : {:?}", window.inner_size()); 98 | println!("-> fullscreen : {:?}", window.fullscreen()); 99 | } 100 | "l" => window.set_min_inner_size(match state { 101 | true => Some(WINDOW_SIZE), 102 | false => None, 103 | }), 104 | "m" => window.set_maximized(state), 105 | "p" => window.set_outer_position({ 106 | let mut position = window.outer_position().unwrap(); 107 | let sign = if state { 1 } else { -1 }; 108 | position.x += 10 * sign; 109 | position.y += 10 * sign; 110 | position 111 | }), 112 | "q" => window.request_redraw(), 113 | "r" => window.set_resizable(state), 114 | "s" => window.set_inner_size(match state { 115 | true => PhysicalSize::new(WINDOW_SIZE.width + 100, WINDOW_SIZE.height + 100), 116 | false => WINDOW_SIZE, 117 | }), 118 | "w" => { 119 | if let Size::Physical(size) = WINDOW_SIZE.into() { 120 | window 121 | .set_cursor_position(Position::Physical(PhysicalPosition::new( 122 | size.width as i32 / 2, 123 | size.height as i32 / 2, 124 | ))) 125 | .unwrap() 126 | } 127 | } 128 | "z" => { 129 | window.set_visible(false); 130 | thread::sleep(Duration::from_secs(1)); 131 | window.set_visible(true); 132 | } 133 | _ => (), 134 | }, 135 | ArrowRight | ArrowLeft => { 136 | video_mode_id = match &key { 137 | ArrowLeft => video_mode_id.saturating_sub(1), 138 | ArrowRight => (video_modes.len() - 1).min(video_mode_id + 1), 139 | _ => unreachable!(), 140 | }; 141 | println!( 142 | "Picking video mode: {}", 143 | video_modes.iter().nth(video_mode_id).unwrap() 144 | ); 145 | } 146 | _ => (), 147 | } 148 | } 149 | _ => (), 150 | } 151 | } 152 | }); 153 | } 154 | event_loop.run(move |event, _event_loop, control_flow| { 155 | *control_flow = match !window_senders.is_empty() { 156 | true => ControlFlow::Wait, 157 | false => ControlFlow::Exit, 158 | }; 159 | match event { 160 | Event::WindowEvent { 161 | event, window_id, .. 162 | } => match event { 163 | WindowEvent::CloseRequested 164 | | WindowEvent::Destroyed 165 | | WindowEvent::KeyboardInput { 166 | event: 167 | KeyEvent { 168 | state: ElementState::Released, 169 | logical_key: Key::Escape, 170 | .. 171 | }, 172 | .. 173 | } => { 174 | window_senders.remove(&window_id); 175 | } 176 | _ => { 177 | if let Some(tx) = window_senders.get(&window_id) { 178 | if let Some(event) = event.to_static() { 179 | tx.send(event).unwrap(); 180 | } 181 | } 182 | } 183 | }, 184 | _ => (), 185 | } 186 | }) 187 | } 188 | --------------------------------------------------------------------------------