Compare commits

..

34 Commits

Author SHA1 Message Date
Osspial
8119a7df13 Bump version to 0.19.2 2019-07-29 16:17:34 -04:00
Simon Sapin
5c02c20b05 Update the percent-encoding crate to 2.0 (#1076) 2019-07-29 16:14:26 -04:00
Osspial
744913d482 Update README.md
For the last time on this branch, it seems. Godspeed.
2019-06-13 01:43:13 -04:00
aloucks
7dc48ed9ef Direct new contributors to the eventloop-2.0 branch (#903) 2019-06-10 01:06:55 -06:00
Osspial
30c0058a88 Update CONTRIBUTING.md (#897) 2019-06-03 16:38:15 -04:00
Bastien Orivel
8794157370 Update parking_lot to 0.8 (#881) 2019-05-25 10:05:47 -06:00
Lucas Kent
1a92c46ad7 Fix warnings on linux (#879) 2019-05-24 05:10:49 -06:00
Alex Touchet
22288ec4c1 Update URLs (#863) 2019-05-11 07:48:33 -06:00
Felix Rabe
ffa6815321 README: Link to FEATURES.md and missing features wiki page (#860)
Closes #854
2019-05-09 02:45:28 -04:00
Felix Rabe
8ddc7fdaf6 README: Use shields.io instead of Herokuapp (#859) 2019-05-09 02:44:55 -04:00
Jakub Piecuch
594cd18567 Fix monitor width & height sanity check (#861)
Fix monitor width & height sanity check
2019-05-05 14:54:57 -06:00
acheronfail
4469f29e70 Feat/fullscreen getters (#838)
* feat: [macos] add get_fullscreen and get_simple_fullscreen

* feat: [windows] add get_fullscreen

* feat: [ios] add get_fullscreen

* feat: [android] add get_fullscreen

* feat: [emscripten] add get_fullscreen

* feat: [linux] add get_fullscreen

* feedback: `get_fullscreen() -> bool` -> `get_fullscreen() -> Option<Id>`
2019-04-25 13:09:32 -04:00
Osspial
4e5321b0aa Fix CI links in README.md (#852) 2019-04-25 01:20:54 -04:00
Felix Rabe
4b2e9da4e4 Popup windows are also known as modal windows (#848) 2019-04-24 22:56:40 -04:00
Osspial
b94572621a Makes changes to CONTRIBUTING.md's table as discussed in #830 (#841)
This includes de-listing @francesca64 from the table, as I suggested. I
realize that this is a controversial decision, and it's not a decision I
make lightly, but I believe I have justification for doing so:

I contacted @francesca64 last month asking her about her inactivity as a
maintainer on this project. She replied on March 26th as follows. For the
sake of her privacy, I've removed removed certain sections of her response,
as it contains some personal details that I'm not comfortable sharing with
the world at large without her explicit permission.

> Hello! Thanks for reaching out!❤

> In short, I've moved on. <removed> ...in November, I was hired by a
> company that recently started using Rust. I'm very happily building
> infrastructure there!

> I'm simply not interested in spending more than 8 hours a day
> programming. You couldn't even pay me to do it! My time is best spent
> going on adventures with my beloved.

> I'll still be active in the Rust ecosystem, but only insofar as the
> company I work for is. We use winit on iOS, Android, and (to a limited
> extent) macOS, so I'll work on those backends as needed.

> Thank you for taking care of winit. I hope you're taking care of
> yourself too; <removed>.

I don't begrudge her for her decision, and others shouldn't either - I
firmly believe that, as this is unpaid, volunteer work, everybody should
have the right to move on when they decide they no longer have the time
or will to contribute.

The exact impliciation of this in regards to her status as maintainer
is open to interpretation. I would argue that this means, should she in
her work stumble upon an issue in any of her listed backends, she would be
willing to submit PRs addressing those issues. However, it also means
that she is not able to put in the time to be active as a maintainer on
those platforms, or review PRs and issues for those platforms.

On March 28th I responded to her email as follows:

> Hey, thanks for responding.

> <removed, personal details>

> In the meanwhile, there are still a few loose ends from your time as
> maintainer that I'd like to get cleaned up. It's okay if you aren't
> going to be spending much time on Winit, but there's still a broad
> assumption that you're able to review PRs for them and that doesn't seem
> to be the case. Would you be able to do a couple things to ease the
> transition to whoever next takes over the macOS, X11, and Android
> backends?

> 1) Submit a PR downgrading yourself from maintainer for macOS, X11, and
>    Android, so somebody else can more actively take them over.
> 2) Post your WIP macOS backend for EL2.0 as well as the issues it
>    currently has, so whoever next maintains macOS can finish it.

> Going forward, I'd like to reach out to the broader Rust community and
> find more active maintainers so Winit can get to 1.0 and I can mostly
> move on from it.

I recieved no response to that email. On April 4th, I followed up on
that email:

> If you aren't able to act as a maintainer for Winit, and aren't able to
> submit a PR updating your official status as maintainer to reflect
> reality, would you mind if I submitted a PR removing you as maintainer?
> That's not something I want to do since it's a bad image for me, a bad
> image for Winit, and sets an extremely uncomfortable precedent, but I'd
> like to start more aggressive outreach to ensure each backend is less
> dependent on one specific person and I don't want to see new
> contributors pinging you for help when you're unable to provide it.

> If you don't reply by the 11th that's the path I'm going to take, but I
> consider it the nuclear option and I want to avoid invoking it if at all
> possible.

Up to this date (April 13th), I have recieved no response. Given the
amount of time I've given her to respond, as well as her lack of
response, I believe we have the justification to remove her from the
table. Should she show back up again, any clarifications on her status
would be welcome, and she is welcome to submit a PR re-listing herself
on the table with a more accurate description of her current contributor
status. However, once we begin the contributor marketing push discussed
in #830, I don't want new contributors to attempt to ask her questions
on the macOS, X11, or Android backends when she isn't able to give a
response.

-----------------------------------------------------------------------

This PR also introduces HALL_OF_CHAMPIONS.md, which commends the efforts
of former maintainers that have contributed greatly to the Winit project.
This wasn't discussed previously, but I think it's important to recognize
the people that brought us to where we are today. It currently lists
@tomaka and @francesca64, as they are the two individuals I'm aware of
that both deserve such recognition and no longer actively contribute to
Winit, but if there's anybody I missed feel free to suggest them and a
blurb describing their work.
2019-04-24 12:44:21 +02:00
Osspial
7203ec45b5 Fix TODO in CONTRIBUTING.md 2019-04-14 12:05:37 -04:00
Osspial
fc835f383b Fix link in PULL_REQUEST_TEMPLATE.md 2019-04-13 20:42:22 -04:00
Osspial
2f9607694f Winit Features and Scope (#695)
* Add initial draft of SCOPE document

* Rephrase/rename feature tiers

* Rename to FEATURES and add a few annotations

* Fix API Reworks table

* Add more annotations

* Some phrasing

* Split compat matrix into seperate section, to be moved into wiki

* Mention compatibility in CONTRIBUTING

* Remove some discuss annotations

* Apply review changes and rename child windows feature to popup windows

* Update based on discussion

* Add issue for Android HiDPI

* Update FEATURES.md

* Update FEATURES.md

* Update PULL_REQUEST_TEMPLATE.md

* Update PULL_REQUEST_TEMPLATE.md

* Reformat FEATURES.MD

* Remove comments

* Improve formatting and add guide for extending #Features
2019-04-13 18:57:08 -04:00
Osspial
cd5caf6a22 Update for 0.19.1 (#823) 2019-04-08 01:08:31 -04:00
Hal Gentz
8522071c2c Add ability to get wayland display from events loop. (#829)
Signed-off-by: Hal Gentz <zegentzy@protonmail.com>
2019-04-08 01:07:47 -04:00
Osspial
dfa972eab1 Fix window icon (#831)
* Fix window icon

* Add CHANGELOG entry
2019-04-08 01:07:12 -04:00
mitchmindtree
69585fe2f2 [Rebased] [x11-backend] Retrieve DPI from Xft.dpi XResource (#824)
* [x11-backend] Retrieve DPI from Xft.dpi XResource

* Update CHANGELOG.md

* Update window.rs

* Update CHANGELOG.md
2019-04-07 12:48:21 -04:00
Christian Duerr
c0b2cad3f9 Add additional numpad key mappings (#805)
* Add additional numpad key mappings

Since some platforms have already used the existing `Add`, `Subtract`
and `Divide` codes to map numpad keys, the X11 and Wayland platform has
been updated to achieve parity between platforms. On macOS only the
`Subtract` numpad key had to be added.

Since the numpad key is different from the normal keys, an alternative
option would be to add new `NumpadAdd`, `NumpadSubtract` and
`NumpadDivide` actions, however I think in this case it should be fine
to map them to the same virtual key code.

* Add Numpad PageUp/Down, Home and End on Wayland
2019-04-07 01:25:37 -04:00
TakWolf
57680d2d17 fix command key event left and right reverse on macOS (#810)
* fix command key event left and right reverse on macOS

https://github.com/tomaka/winit/issues/808

* update changelog
2019-03-25 14:05:07 -04:00
Tobias Kortkamp
0019ff210c Fix build on FreeBSD (#815)
* Fix build on FreeBSD

error[E0432]: unresolved import `libc::__errno_location`
  --> src/platform/linux/x11/mod.rs:22:85
   |
22 | use libc::{select, fd_set, FD_SET, FD_ZERO, FD_ISSET, EINTR, EINVAL, ENOMEM, EBADF, __errno_location};
   |                                                                                     ^^^^^^^^^^^^^^^^ no `__errno_location` in the root

__errno_location is called __error on FreeBSD and __errno on Open- and NetBSD.

Signed-off-by: Tobias Kortkamp <t@tobik.me>

* Import __error / __errno on *BSD as __errno_location

Signed-off-by: Tobias Kortkamp <t@tobik.me>

* Add changelog entry

Signed-off-by: Tobias Kortkamp <t@tobik.me>
2019-03-22 10:44:00 -04:00
Hal Gentz
4a103387e5 Add contact info. (#818)
Signed-off-by: Hal Gentz <zegentzy@protonmail.com>
2019-03-19 22:20:03 -04:00
Osspial
b6ca584ecf On Windows, fix CursorMoved(0, 0) getting sent on focus (#819)
* On Windows, fix CursorMoved(0, 0) getting sent on focus

* Add changelog entry
2019-03-19 22:19:41 -04:00
Osspial
e0340d52b0 Update winit to 0.19.0 (#798)
* Update winit to 0.19.0

* Update date for 0.19
2019-03-06 21:50:13 -05:00
Hal Gentz
f928a4b917 Use XRRGetScreenResourcesCurrent when avail. (#801)
* Use `XRRGetScreenResourcesCurrent` when avail.

Signed-off-by: Hal Gentz <zegentzy@protonmail.com>

* Changelog

Signed-off-by: Hal Gentz <zegentzy@protonmail.com>
2019-03-05 20:58:14 -05:00
Osspial
c5d22fda2b Ignore the AltGr key when populating ModifersState (#763)
* When building ModifiersState, ignore AltGr on Windows

* Add CHANGELOG entry

* Also filter out Control when pressing AltGr
2019-03-05 17:55:01 -05:00
Riku Salminen
9ea2810b46 x11: thread safe replacement for XNextEvent (#782)
XNextEvent will block for input while holding the global Xlib mutex.

This will cause a deadlock in even the most trivial multi-threaded
application because OpenGL functions will need to hold the Xlib mutex
too.

Add EventsLoop::poll_one_event and EventsLoop::wait_for_input to provide
thread-safe functions to poll and wait events from the X11 event queue
using unix select(2) and XCheckIfEvent.

This is a somewhat ugly workaround to an ugly problem.

Fixes #779
2019-02-24 18:02:55 -05:00
Michael Palmos
9a23ec3c37 Fix incorrect keycodes when using a non-US keyboard layout. (#755)
* Fix incorrect keycodes when using a non-US keyboard layout.

This commit fixes the issue described in #752, and uses the advised
method to fix it.

* Style fixes

Co-Authored-By: Toqozz <toqoz@hotmail.com>

* Refactoring of macOS `virtualkeycode` fix (#752)

* Applies requested changes as per pull request discussion (#755).
2019-02-23 15:41:55 -05:00
Torkel Danielsson
84c812e568 Handle horizontal wheel input (Windows) (#792)
* add handler for horizontal wheel input

* add changlelog message re now handling horiz scroll on windows
2019-02-22 09:31:16 -05:00
trimental
f0ce5b0c8d On wayland, fix with_title() not setting the windows title (#770) 2019-02-22 09:30:59 -05:00
325 changed files with 22156 additions and 61835 deletions

View File

@@ -1,2 +0,0 @@
[alias]
run-wasm = ["run", "--release", "--package", "run-wasm", "--"]

56
.circleci/config.yml Normal file
View File

@@ -0,0 +1,56 @@
version: 2
jobs:
android-test:
working_directory: ~/winit
docker:
- image: tomaka/cargo-apk
steps:
- run: apt-get -qq update && apt-get install -y git
- checkout
- restore_cache:
key: android-test-cache-{{ checksum "Cargo.toml" }}
- run: cargo apk build --example window
- save_cache:
key: android-test-cache-{{ checksum "Cargo.toml" }}
paths:
- target
asmjs-test:
working_directory: ~/winit
docker:
- image: tomaka/rustc-emscripten
steps:
- run: apt-get -qq update && apt-get install -y git
- checkout
- restore_cache:
key: asmjs-test-cache-{{ checksum "Cargo.toml" }}
- run: cargo build --example window --target asmjs-unknown-emscripten
- save_cache:
key: asmjs-test-cache-{{ checksum "Cargo.toml" }}
paths:
- target
wasm-test:
working_directory: ~/winit
docker:
- image: tomaka/rustc-emscripten
steps:
- run: apt-get -qq update && apt-get install -y git
- checkout
- restore_cache:
key: wasm-test-cache-{{ checksum "Cargo.toml" }}
- run: cargo build --example window --target wasm32-unknown-emscripten
- save_cache:
key: wasm-test-cache-{{ checksum "Cargo.toml" }}
paths:
- target
workflows:
version: 2
build-test-and-deploy:
jobs:
- android-test
- asmjs-test
- wasm-test

34
.github/CODEOWNERS vendored
View File

@@ -1,34 +0,0 @@
# Android
/src/platform/android.rs @msiglreith @MarijnS95
/src/platform_impl/android @msiglreith @MarijnS95
# iOS
/src/platform/ios.rs @madsmtm
/src/platform_impl/ios @madsmtm
# Unix
/src/platform_impl/linux/mod.rs @kchibisov
# Wayland
/src/platform/wayland.rs @kchibisov
/src/platform_impl/linux/wayland @kchibisov
# X11
/src/platform/x11.rs @kchibisov @notgull
/src/platform_impl/linux/x11 @kchibisov @notgull
# macOS
/src/platform/macos.rs @madsmtm
/src/platform_impl/macos @madsmtm
# Web
/src/platform/web.rs @daxpedda
/src/platform_impl/web @daxpedda
# Windows
/src/platform/windows.rs @msiglreith
/src/platform_impl/windows @msiglreith
# Orbital (Redox OS)
/src/platform/orbital.rs @jackpot51
/src/platform_impl/orbital @jackpot51

View File

@@ -1,5 +1,5 @@
- [ ] Tested on all platforms changed
- [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
- [ ] Created or updated an example program if it would help users understand this functionality
- [ ] Created an example program if it would help users understand this functionality
- [ ] Updated [feature matrix](https://github.com/rust-windowing/winit/blob/master/FEATURES.md), if new features were added or implemented

View File

@@ -1,203 +0,0 @@
name: CI
on:
pull_request:
push:
branches: [master]
jobs:
fmt:
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: Check Formatting
run: cargo fmt -- --check
tests:
name: Test ${{ matrix.toolchain }} ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
strategy:
fail-fast: false
matrix:
toolchain: [stable, nightly, '1.70.0']
platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
- { name: 'Windows 32bit MSVC', target: i686-pc-windows-msvc, os: windows-latest, }
- { name: 'Windows 64bit GNU', target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
- { name: 'Windows 32bit GNU', target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
- { name: 'Linux 32bit', target: i686-unknown-linux-gnu, os: ubuntu-latest, }
- { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' }
- { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' }
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--features=android-native-activity', cmd: 'apk --' }
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
- { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, }
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, }
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, }
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
exclude:
# Android is tested on stable-3
- toolchain: '1.70.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
include:
- toolchain: '1.70.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
- toolchain: 'nightly'
platform: {
name: 'web Atomic',
target: wasm32-unknown-unknown,
os: ubuntu-latest,
options: '-Zbuild-std=panic_abort,std',
rustflags: '-Ctarget-feature=+atomics,+bulk-memory',
components: rust-src,
}
env:
# Set more verbose terminal output
CARGO_TERM_VERBOSE: true
RUST_BACKTRACE: 1
# Faster compilation and error on warnings
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings ${{ matrix.platform.rustflags }}'
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
CMD: ${{ matrix.platform.cmd }}
steps:
- uses: actions/checkout@v3
- name: Restore cache of cargo folder
# We use `restore` and later `save`, so that we can create the key after
# the cache has been downloaded.
#
# This could be avoided if we added Cargo.lock to the repository.
uses: actions/cache/restore@v3
with:
# https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-never-intended-to-be-found
restore-keys: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}
- name: Generate lockfile
# Also updates the crates.io index
run: cargo generate-lockfile
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
run: sudo apt-get update && sudo apt-get install gcc-multilib
- name: Cache cargo-apk
if: contains(matrix.platform.target, 'android')
id: cargo-apk-cache
uses: actions/cache@v3
with:
path: ~/.cargo/bin/cargo-apk
# Change this key if we update the required cargo-apk version
key: cargo-apk-v0-9-7
- uses: dtolnay/rust-toolchain@master
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
with:
toolchain: stable
- name: Install cargo-apk
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
run: cargo install cargo-apk --version=^0.9.7 --locked
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }}
targets: ${{ matrix.platform.target }}
components: clippy, ${{ matrix.platform.components }}
- name: Check documentation
run: cargo doc --no-deps $OPTIONS --document-private-items
env:
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }}'
- name: Build crate
run: cargo $CMD build -p winit $OPTIONS
- name: Build tests
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0'
run: cargo $CMD test -p winit --no-run $OPTIONS
- name: Run tests
if: >
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0'
run: cargo $CMD test $OPTIONS
- name: Lint with clippy
if: (matrix.toolchain == 'stable') && !contains(matrix.platform.options, '--no-default-features')
run: cargo clippy --all-targets $OPTIONS -- -Dwarnings
- name: Build tests with serde enabled
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0'
run: cargo $CMD test --no-run $OPTIONS -p winit --features serde
- name: Run tests with serde enabled
if: >
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0'
run: cargo $CMD test $OPTIONS --features serde
- name: Check docs.rs documentation
if: matrix.toolchain == 'nightly'
run: cargo doc --no-deps $OPTIONS --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
env:
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }} --cfg=docsrs'
# See restore step above
- name: Save cache of cargo folder
uses: actions/cache/save@v3
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }}
cargo-deny:
name: Run cargo-deny on ${{ matrix.platform.name }}
runs-on: ubuntu-latest
# TODO: remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved
strategy:
fail-fast: false
matrix:
platform:
- { name: 'Android', target: aarch64-linux-android }
- { name: 'iOS', target: aarch64-apple-ios }
- { name: 'Linux', target: x86_64-unknown-linux-gnu }
- { name: 'macOS', target: x86_64-apple-darwin }
- { name: 'Redox OS', target: x86_64-unknown-redox }
- { name: 'web', target: wasm32-unknown-unknown }
- { name: 'Windows', target: x86_64-pc-windows-gnu }
steps:
- uses: actions/checkout@v3
- uses: EmbarkStudios/cargo-deny-action@v1
with:
command: check
log-level: error
arguments: --all-features --target ${{ matrix.platform.target }}

View File

@@ -1,50 +0,0 @@
name: Docs
on:
push:
branches: [master]
jobs:
docs:
name: Documentation
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}winit
runs-on: ubuntu-latest
permissions:
contents: read
pages: write
id-token: write
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
- name: Run Rustdoc
env:
RUSTDOCFLAGS: --crate-version master --cfg=docsrs
run: |
cargo doc --no-deps -Z rustdoc-map -Z rustdoc-scrape-examples --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Fix permissions
run: |
chmod -c -R +rX "target/doc" | while read line; do
echo "::warning title=Invalid file permissions automatically fixed::$line"
done
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: target/doc
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

4
.gitignore vendored
View File

@@ -3,8 +3,4 @@ target/
rls/
.vscode/
*~
*.wasm
*.ts
*.js
#*#
.DS_Store

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "deps/apk-builder"]
path = deps/apk-builder
url = https://github.com/rust-windowing/android-rs-glue

64
.travis.yml Normal file
View File

@@ -0,0 +1,64 @@
language: rust
cache: cargo
matrix:
include:
# Linux 32bit
- env: TARGET=i686-unknown-linux-gnu
os: linux
rust: nightly
addons:
apt:
# Cross compiler and cross compiled C libraries
packages: &i686_packages
- gcc-multilib
- env: TARGET=i686-unknown-linux-gnu
os: linux
rust: stable
addons:
apt:
packages: *i686_packages
# Linux 64bit
- env: TARGET=x86_64-unknown-linux-gnu
os: linux
rust: nightly
- env: TARGET=x86_64-unknown-linux-gnu
os: linux
rust: stable
# macOS
- env: TARGET=x86_64-apple-darwin
os: osx
rust: nightly
- env: TARGET=x86_64-apple-darwin
os: osx
rust: stable
# iOS
- env: TARGET=x86_64-apple-ios
os: osx
rust: nightly
- env: TARGET=x86_64-apple-ios
os: osx
rust: stable
install:
- rustup self update
- rustup target add $TARGET; true
script:
- cargo build --target $TARGET --verbose
- cargo build --target $TARGET --features serde --verbose
- cargo build --target $TARGET --features icon_loading --verbose
# Running iOS apps on OSX requires the simulator so we skip that for now
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --verbose; fi
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --features serde --verbose; fi
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --features icon_loading --verbose; fi
after_success:
- |
[ $TRAVIS_BRANCH = master ] &&
[ $TRAVIS_PULL_REQUEST = false ] &&
cargo publish --token ${CRATESIO_TOKEN}

View File

@@ -1,926 +1,8 @@
# Changelog
All notable changes to this project will be documented in this file.
Please keep one empty line before and after all headers. (This is required for
`git` to produce a conflict when a release is made while a PR is open and the
PR's changelog entry would go into the wrong section).
And please only add new entries to the top of this list, right below the `#
Unreleased` header.
# Unreleased
- On X11, fix swapped instance and general class names.
- **Breaking:** Removed unnecessary generic parameter `T` from `EventLoopWindowTarget`.
- On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example.
- **Breaking:** Remove `Window::set_cursor_icon`
- Add `WindowBuilder::with_cursor` and `Window::set_cursor` which takes a `CursorIcon` or `CustomCursor`
- Add `CustomCursor`
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
- Add `CustomCursorExtWebSys::from_animation` to allow creating animated cursors from other `CustomCursor`s.
- On macOS, add services menu.
- **Breaking:** On Web, remove queuing fullscreen request in absence of transient activation.
- On Web, fix setting cursor icon overriding cursor visibility.
- **Breaking:** On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`.
- **Breaking:** On Web, macOS and iOS, return `HandleError::Unavailable` when a window handle is not available.
- **Breaking:** Bump MSRV from `1.65` to `1.70`.
- On Web, add the ability to toggle calling `Event.preventDefault()` on `Window`.
- **Breaking:** Remove `WindowAttributes::fullscreen()` and expose as field directly.
- **Breaking:** Rename `VideoMode` to `VideoModeHandle` to represent that it doesn't hold static data.
- **Breaking:** No longer export `platform::x11::XNotSupported`.
- **Breaking:** Renamed `platform::x11::XWindowType` to `platform::x11::WindowType`.
- Add the `OwnedDisplayHandle` type for allowing safe display handle usage outside of trivial cases.
- **Breaking:** Rename `TouchpadMagnify` to `PinchGesture`, `SmartMagnify` to `DoubleTapGesture` and `TouchpadRotate` to `RotationGesture` to represent the action rather than the intent.
- on iOS, add detection support for `PinchGesture`, `DoubleTapGesture` and `RotationGesture`.
- on Windows: add `with_system_backdrop`, `with_border_color`, `with_title_background_color`, `with_title_text_color` and `with_corner_preference`
- On Windows, Remove `WS_CAPTION`, `WS_BORDER` and `WS_EX_WINDOWEDGE` styles for child windows without decorations.
- On Windows, fixed a race condition when sending an event through the loop proxy.
- **Breaking:** Removed `EventLoopError::AlreadyRunning`, which can't happen as it is already prevented by the type system.
- On Wayland, disable `Occluded` event handling.
- Added `EventLoop::builder`, which is intended to replace the (now deprecated) `EventLoopBuilder::new`.
- **Breaking:** Changed the signature of `EventLoop::with_user_event` to return a builder.
- **Breaking:** Removed `EventLoopBuilder::with_user_event`, the functionality is now available in `EventLoop::with_user_event`.
- Add `Window::builder`, which is intended to replace the (now deprecated) `WindowBuilder::new`.
- On X11, reload dpi on `_XSETTINGS_SETTINGS` update.
- On X11, fix deadlock when adjusting DPI and resizing at the same time.
- On Wayland, fix `Focused(false)` being send when other seats still have window focused.
- On Wayland, fix `Window::set_{min,max}_inner_size` not always applied.
- On Windows, fix inconsistent resizing behavior with multi-monitor setups when repositioning outside the event loop.
- On Wayland, fix `WAYLAND_SOCKET` not used when detecting platform.
- **Breaking:** Move some types to the `winit-core` crate. Most types are
re-exported verbatim; however:
- `Event`, `WindowEvent` and `KeyEvent` now take a generic that `winit`
implements.
- `ActivationToken` and `InnerSizeWriter` expose new methods to make them
useful.
- `InnerSizeWriter::request_inner_size` returns an `InnerSizeIgnored` error
instead of an `ExternalError`.
# Version 0.19.2 (2019-07-29)
# 0.29.10
- On Web, account for canvas being focused already before event loop starts.
- On Web, increase cursor position accuracy.
# 0.29.9
- On X11, fix `NotSupported` error not propagated when creating event loop.
- On Wayland, fix resize not issued when scale changes
- On X11 and Wayland, fix arrow up on keypad reported as `ArrowLeft`.
- On macOS, report correct logical key when Ctrl or Cmd is pressed.
# 0.29.8
- On X11, fix IME input lagging behind.
- On X11, fix `ModifiersChanged` not sent from xdotool-like input
- On X11, fix keymap not updated from xmodmap.
- On X11, reduce the amount of time spent fetching screen resources.
- On Wayland, fix `Window::request_inner_size` being overwritten by resize.
- On Wayland, fix `Window::inner_size` not using the correct rounding.
# 0.29.7
- On X11, fix `Xft.dpi` reload during runtime.
- On X11, fix window minimize.
# 0.29.6
- On Web, fix context menu not being disabled by `with_prevent_default(true)`.
- On Wayland, fix `WindowEvent::Destroyed` not being delivered after destroying window.
- Fix `EventLoopExtRunOnDemand::run_on_demand` not working for consequent invocation
# 0.29.5
- On macOS, remove spurious error logging when handling `Fn`.
- On X11, fix an issue where floating point data from the server is
misinterpreted during a drag and drop operation.
- On X11, fix a bug where focusing the window would panic.
- On macOS, fix `refresh_rate_millihertz`.
- On Wayland, disable Client Side Decorations when `wl_subcompositor` is not supported.
- On X11, fix `Xft.dpi` detection from Xresources.
- On Windows, fix consecutive calls to `window.set_fullscreen(Some(Fullscreen::Borderless(None)))` resulting in losing previous window state when eventually exiting fullscreen using `window.set_fullscreen(None)`.
- On Wayland, fix resize being sent on focus change.
- On Windows, fix `set_ime_cursor_area`.
# 0.29.4
- Fix crash when running iOS app on macOS.
- On X11, check common alternative cursor names when loading cursor.
- On X11, reload the DPI after a property change event.
- On Windows, fix so `drag_window` and `drag_resize_window` can be called from another thread.
- On Windows, fix `set_control_flow` in `AboutToWait` not being taken in account.
- On macOS, send a `Resized` event after each `ScaleFactorChanged` event.
- On Wayland, fix `wl_surface` being destroyed before associated objects.
- On macOS, fix assertion when pressing `Fn` key.
- On Windows, add `WindowBuilderExtWindows::with_clip_children` to control `WS_CLIPCHILDREN` style.
# 0.29.3
- On Wayland, apply correct scale to `PhysicalSize` passed in `WindowBuilder::with_inner_size` when possible.
- On Wayland, fix `RedrawRequsted` being always sent without decorations and `sctk-adwaita` feature.
- On Wayland, ignore resize requests when the window is fully tiled.
- On Wayland, use `configure_bounds` to constrain `with_inner_size` when compositor wants users to pick size.
- On Windows, fix deadlock when accessing the state during `Cursor{Enter,Leave}`.
- On Windows, add support for `Window::set_transparent`.
- On macOS, fix deadlock when entering a nested event loop from an event handler.
- On macOS, add support for `Window::set_blur`.
# 0.29.2
- **Breaking:** Bump MSRV from `1.60` to `1.65`.
- **Breaking:** Add `Event::MemoryWarning`; implemented on iOS/Android.
- **Breaking:** Bump `ndk` version to `0.8.0`, ndk-sys to `0.5.0`, `android-activity` to `0.5.0`.
- **Breaking:** Change default `ControlFlow` from `Poll` to `Wait`.
- **Breaking:** Move `Event::RedrawRequested` to `WindowEvent::RedrawRequested`.
- **Breaking:** Moved `ControlFlow::Exit` to `EventLoopWindowTarget::exit()` and `EventLoopWindowTarget::exiting()` and removed `ControlFlow::ExitWithCode(_)` entirely.
- **Breaking:** Moved `ControlFlow` to `EventLoopWindowTarget::set_control_flow()` and `EventLoopWindowTarget::control_flow()`.
- **Breaking:** `EventLoop::new` and `EventLoopBuilder::build` now return `Result<Self, EventLoopError>`
- **Breaking:** `WINIT_UNIX_BACKEND` was removed in favor of standard `WAYLAND_DISPLAY` and `DISPLAY` variables.
- **Breaking:** on Wayland, dispatching user created Wayland queue won't wake up the loop unless winit has event to send back.
- **Breaking:** remove `DeviceEvent::Text`.
- **Breaking:** Remove lifetime parameter from `Event` and `WindowEvent`.
- **Breaking:** Rename `Window::set_inner_size` to `Window::request_inner_size` and indicate if the size was applied immediately.
- **Breaking:** `ActivationTokenDone` event which could be requested with the new `startup_notify` module, see its docs for more.
- **Breaking:** `ScaleFactorChanged` now contains a writer instead of a reference to update inner size.
- **Breaking** `run() -> !` has been replaced by `run() -> Result<(), EventLoopError>` for returning errors without calling `std::process::exit()` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
- **Breaking** Removed `EventLoopExtRunReturn` / `run_return` in favor of `EventLoopExtPumpEvents` / `pump_events` and `EventLoopExtRunOnDemand` / `run_on_demand` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
- `RedrawRequested` is no longer guaranteed to be emitted after `MainEventsCleared`, it is now platform-specific when the event is emitted after being requested via `redraw_request()`.
- On Windows, `RedrawRequested` is now driven by `WM_PAINT` messages which are requested via `redraw_request()`
- **Breaking** `LoopDestroyed` renamed to `LoopExiting` ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `RedrawEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `MainEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking:** Remove all deprecated `modifiers` fields.
- **Breaking:** Rename `DeviceEventFilter` to `DeviceEvents` reversing the behavior of variants.
- **Breaking** Add `AboutToWait` event which is emitted when the event loop is about to block and wait for new events ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking:** Rename `EventLoopWindowTarget::set_device_event_filter` to `listen_device_events`.
- **Breaking:** Rename `Window::set_ime_position` to `Window::set_ime_cursor_area` adding a way to set exclusive zone.
- **Breaking:** `with_x11_visual` now takes the visual ID instead of the bare pointer.
- **Breaking** `MouseButton` now supports `Back` and `Forward` variants, emitted from mouse events on Wayland, X11, Windows, macOS and Web.
- **Breaking:** On Web, `instant` is now replaced by `web_time`.
- **Breaking:** On Web, dropped support for Safari versions below 13.1.
- **Breaking:** On Web, the canvas output bitmap size is no longer adjusted.
- **Breaking:** On Web, the canvas size is not controlled by Winit anymore and external changes to the canvas size will be reported through `WindowEvent::Resized`.
- **Breaking:** Updated `bitflags` crate version to `2`, which changes the API on exposed types.
- **Breaking:** `CursorIcon::Arrow` was removed.
- **Breaking:** `CursorIcon::Hand` is now named `CursorIcon::Pointer`.
- **Breaking:** `CursorIcon` is now used from the `cursor-icon` crate.
- **Breaking:** `WindowExtWebSys::canvas()` now returns an `Option`.
- **Breaking:** Overhaul keyboard input handling.
- Replace `KeyboardInput` with `KeyEvent` and `RawKeyEvent`.
- Change `WindowEvent::KeyboardInput` to contain a `KeyEvent`.
- Change `Event::Key` to contain a `RawKeyEvent`.
- Remove `Event::ReceivedCharacter`. In its place, you should use
`KeyEvent.text` in combination with `WindowEvent::Ime`.
- Replace `VirtualKeyCode` with the `Key` enum.
- Replace `ScanCode` with the `KeyCode` enum.
- Rename `ModifiersState::LOGO` to `SUPER` and `ModifiersState::CTRL` to `CONTROL`.
- Add `PhysicalKey` wrapping `KeyCode` and `NativeKeyCode`.
- Add `KeyCode` to refer to keys (roughly) by their physical location.
- Add `NativeKeyCode` to represent raw `KeyCode`s which Winit doesn't
understand.
- Add `Key` to represent the keys after they've been interpreted by the
active (software) keyboard layout.
- Add `NamedKey` to represent the categorized keys.
- Add `NativeKey` to represent raw `Key`s which Winit doesn't understand.
- Add `KeyLocation` to tell apart `Key`s which usually "mean" the same thing,
but can appear simultaneously in different spots on the same keyboard
layout.
- Add `Window::reset_dead_keys` to enable application-controlled cancellation
of dead key sequences.
- Add `KeyEventExtModifierSupplement` to expose additional (and less
portable) interpretations of a given key-press.
- Add `PhysicalKeyExtScancode`, which lets you convert between scancodes and
`PhysicalKey`.
- `ModifiersChanged` now uses dedicated `Modifiers` struct.
- Removed platform-specific extensions that should be retrieved through `raw-window-handle` trait implementations instead:
- `platform::windows::HINSTANCE`.
- `WindowExtWindows::hinstance`.
- `WindowExtWindows::hwnd`.
- `WindowExtIOS::ui_window`.
- `WindowExtIOS::ui_view_controller`.
- `WindowExtIOS::ui_view`.
- `WindowExtMacOS::ns_window`.
- `WindowExtMacOS::ns_view`.
- `EventLoopWindowTargetExtWayland::wayland_display`.
- `WindowExtWayland::wayland_surface`.
- `WindowExtWayland::wayland_display`.
- `WindowExtX11::xlib_window`.
- `WindowExtX11::xlib_display`.
- `WindowExtX11::xlib_screen_id`.
- `WindowExtX11::xcb_connection`.
- Reexport `raw-window-handle` in `window` module.
- Add `ElementState::is_pressed`.
- Add `Window::pre_present_notify` to notify winit before presenting to the windowing system.
- Add `Window::set_blur` to request a blur behind the window; implemented on Wayland for now.
- Add `Window::show_window_menu` to request a titlebar/system menu; implemented on Wayland/Windows for now.
- Implement `AsFd`/`AsRawFd` for `EventLoop<T>` on X11 and Wayland.
- Implement `PartialOrd` and `Ord` for `MouseButton`.
- Implement `PartialOrd` and `Ord` on types in the `dpi` module.
- Make `WindowBuilder` `Send + Sync`.
- Make iOS `MonitorHandle` and `VideoMode` usable from other threads.
- Make iOS windows usable from other threads.
- On Android, add force data to touch events.
- On Android, added `EventLoopBuilderExtAndroid::handle_volume_keys` to indicate that the application will handle the volume keys manually.
- On Android, fix `DeviceId` to contain device id's.
- On Orbital, fix `ModifiersChanged` not being sent.
- On Wayland, `Window::outer_size` now accounts for **client side** decorations.
- On Wayland, add `Window::drag_resize_window` method.
- On Wayland, remove `WINIT_WAYLAND_CSD_THEME` variable.
- On Wayland, fix `TouchPhase::Canceled` being sent for moved events.
- On Wayland, fix forward compatibility issues.
- On Wayland, fix initial window size not restored for maximized/fullscreened on startup window.
- On Wayland, fix maximized startup not taking full size on GNOME.
- On Wayland, fix maximized window creation and window geometry handling.
- On Wayland, fix window not checking that it actually got initial configure event.
- On Wayland, make double clicking and moving the CSD frame more reliable.
- On Wayland, support `Occluded` event with xdg-shell v6
- On Wayland, use frame callbacks to throttle `RedrawRequested` events so redraws will align with compositor.
- On Web, `ControlFlow::WaitUntil` now uses the Prioritized Task Scheduling API. `setTimeout()`, with a trick to circumvent throttling to 4ms, is used as a fallback.
- On Web, `EventLoopProxy` now implements `Send`.
- On Web, `Window` now implements `Send` and `Sync`.
- On Web, account for CSS `padding`, `border`, and `margin` when getting or setting the canvas position.
- On Web, add Fullscreen API compatibility for Safari.
- On Web, add `DeviceEvent::Motion`, `DeviceEvent::MouseWheel`, `DeviceEvent::Button` and `DeviceEvent::Key` support.
- On Web, add `EventLoopWindowTargetExtWebSys` and `PollStrategy`, which allows to set different strategies for `ControlFlow::Poll`. By default the Prioritized Task Scheduling API is used, but an option to use `Window.requestIdleCallback` is available as well. Both use `setTimeout()`, with a trick to circumvent throttling to 4ms, as a fallback.
- On Web, add `WindowBuilderExtWebSys::with_append()` to append the canvas element to the web page on creation.
- On Web, allow event loops to be recreated with `spawn`.
- On Web, enable event propagation.
- On Web, fix `ControlFlow::WaitUntil` to never wake up **before** the given time.
- On Web, fix `DeviceEvent::MouseMotion` only being emitted for each canvas instead of the whole window.
- On Web, fix `Window:::set_fullscreen` doing nothing when called outside the event loop but during transient activation.
- On Web, fix pen treated as mouse input.
- On Web, fix pointer button events not being processed when a buttons is already pressed.
- On Web, fix scale factor resize suggestion always overwriting the canvas size.
- On Web, fix some `WindowBuilder` methods doing nothing.
- On Web, fix some `Window` methods using incorrect HTML attributes instead of CSS properties.
- On Web, fix the bfcache by not using the `beforeunload` event and map bfcache loading/unloading to `Suspended`/`Resumed` events.
- On Web, fix touch input not gaining or loosing focus.
- On Web, fix touch location to be as accurate as mouse position.
- On Web, handle coalesced pointer events, which increases the resolution of pointer inputs.
- On Web, implement `Window::focus_window()`.
- On Web, implement `Window::set_(min|max)_inner_size()`.
- On Web, implement `WindowEvent::Occluded`.
- On Web, never return a `MonitorHandle`.
- On Web, prevent clicks on the canvas to select text.
- On Web, remove any fullscreen requests from the queue when an external fullscreen activation was detected.
- On Web, remove unnecessary `Window::is_dark_mode()`, which was replaced with `Window::theme()`.
- On Web, respect `EventLoopWindowTarget::listen_device_events()` settings.
- On Web, scale factor and dark mode detection are now more robust.
- On Web, send mouse position on button release as well.
- On Web, take all transient activations on the canvas and window into account to queue a fullscreen request.
- On Web, use `Window.requestAnimationFrame()` to throttle `RedrawRequested` events.
- On Web, use the correct canvas size when calculating the new size during scale factor change, instead of using the output bitmap size.
- On Web: fix `Window::request_redraw` not waking the event loop when called from outside the loop.
- On Web: fix position of touch events to be relative to the canvas.
- On Windows, add `drag_resize_window` method support.
- On Windows, add horizontal MouseWheel `DeviceEvent`.
- On Windows, added `WindowBuilderExtWindows::with_class_name` to customize the internal class name.
- On Windows, fix IME APIs not working when from non event loop thread.
- On Windows, fix `CursorEnter/Left` not being sent when grabbing the mouse.
- On Windows, fix `RedrawRequested` not being delivered when calling `Window::request_redraw` from `RedrawRequested`.
- On Windows, port to `windows-sys` version 0.48.0.
- On X11, add a `with_embedded_parent_window` function to the window builder to allow embedding a window into another window.
- On X11, fix event loop not waking up on `ControlFlow::Poll` and `ControlFlow::WaitUntil`.
- On X11, fix false positive flagging of key repeats when pressing different keys with no release between presses.
- On X11, set `visual_id` in returned `raw-window-handle`.
- On iOS, add ability to change the status bar style.
- On iOS, add force data to touch events when using the Apple Pencil.
- On iOS, always wake the event loop when transitioning from `ControlFlow::Poll` to `ControlFlow::Poll`.
- On iOS, send events `WindowEvent::Occluded(false)`, `WindowEvent::Occluded(true)` when application enters/leaves foreground.
- On macOS, add tabbing APIs on `WindowExtMacOS` and `EventLoopWindowTargetExtMacOS`.
- On macOS, fix assertion when pressing `Globe` key.
- On macOS, fix crash in `window.set_minimized(false)`.
- On macOS, fix crash when dropping `Window`.
# 0.28.7
- Fix window size sometimes being invalid when resizing on macOS 14 Sonoma.
# 0.28.6
- On macOS, fixed memory leak when getting monitor handle.
- On macOS, fix `Backspace` being emitted when clearing preedit with it.
# 0.28.5
- On macOS, fix `key_up` being ignored when `Ime` is disabled.
# 0.28.4
- On macOS, fix empty marked text blocking regular input.
- On macOS, fix potential panic when getting refresh rate.
- On macOS, fix crash when calling `Window::set_ime_position` from another thread.
# 0.28.3
- Fix macOS memory leaks.
# 0.28.2
- Implement `HasRawDisplayHandle` for `EventLoop`.
- On macOS, set resize increments only for live resizes.
- On Wayland, fix rare crash on DPI change
- Web: Added support for `Window::theme`.
- On Wayland, fix rounding issues when doing resize.
- On macOS, fix wrong focused state on startup.
- On Windows, fix crash on setting taskbar when using Visual Studio debugger.
- On macOS, resize simple fullscreen windows on windowDidChangeScreen events.
# 0.28.1
- On Wayland, fix crash when dropping a window in multi-window setup.
# 0.28.0
- On macOS, fixed `Ime::Commit` persisting for all input after interacting with `Ime`.
- On macOS, added `WindowExtMacOS::option_as_alt` and `WindowExtMacOS::set_option_as_alt`.
- On Windows, fix window size for maximized, undecorated windows.
- On Windows and macOS, add `WindowBuilder::with_active`.
- Add `Window::is_minimized`.
- On X11, fix errors handled during `register_xlib_error_hook` invocation bleeding into winit.
- Add `Window::has_focus`.
- On Windows, fix `Window::set_minimized(false)` not working for windows minimized by `Win + D` hotkey.
- **Breaking:** On Web, touch input no longer fires `WindowEvent::Cursor*`, `WindowEvent::MouseInput`, or `DeviceEvent::MouseMotion` like other platforms, but instead it fires `WindowEvent::Touch`.
- **Breaking:** Removed platform specific `WindowBuilder::with_parent` API in favor of `WindowBuilder::with_parent_window`.
- On Windows, retain `WS_MAXIMIZE` window style when un-minimizing a maximized window.
- On Windows, fix left mouse button release event not being sent after `Window::drag_window`.
- On macOS, run most actions on the main thread, which is strictly more correct, but might make multithreaded applications block slightly more.
- On macOS, fix panic when getting current monitor without any monitor attached.
- On Windows and MacOS, add API to enable/disable window buttons (close, minimize, ...etc).
- On Windows, macOS, X11 and Wayland, add `Window::set_theme`.
- **Breaking:** Remove `WindowExtWayland::wayland_set_csd_theme` and `WindowBuilderExtX11::with_gtk_theme_variant`.
- On Windows, revert window background to an empty brush to avoid white flashes when changing scaling.
- **Breaking:** Removed `Window::set_always_on_top` and related APIs in favor of `Window::set_window_level`.
- On Windows, MacOS and X11, add always on bottom APIs.
- On Windows, fix the value in `MouseButton::Other`.
- On macOS, add `WindowExtMacOS::is_document_edited` and `WindowExtMacOS::set_document_edited` APIs.
- **Breaking:** Removed `WindowBuilderExtIOS::with_root_view_class`; instead, you should use `[[view layer] addSublayer: ...]` to add an instance of the desired layer class (e.g. `CAEAGLLayer` or `CAMetalLayer`). See `vulkano-win` or `wgpu` for examples of this.
- On MacOS and Windows, add `Window::set_content_protected`.
- On MacOS, add `EventLoopBuilderExtMacOS::with_activate_ignoring_other_apps`.
- On Windows, fix icons specified on `WindowBuilder` not taking effect for windows created after the first one.
- On Windows and macOS, add `Window::title` to query the current window title.
- On Windows, fix focusing menubar when pressing `Alt`.
- On MacOS, made `accepts_first_mouse` configurable.
- Migrated `WindowBuilderExtUnix::with_resize_increments` to `WindowBuilder`.
- Added `Window::resize_increments`/`Window::set_resize_increments` to update resize increments at runtime for X11/macOS.
- macOS/iOS: Use `objc2` instead of `objc` internally.
- **Breaking:** Bump MSRV from `1.57` to `1.60`.
- **Breaking:** Split the `platform::unix` module into `platform::x11` and `platform::wayland`. The extension types are similarly renamed.
- **Breaking:**: Removed deprecated method `platform::unix::WindowExtUnix::is_ready`.
- Removed `parking_lot` dependency.
- **Breaking:** On macOS, add support for two-finger touchpad magnification and rotation gestures with new events `WindowEvent::TouchpadMagnify` and `WindowEvent::TouchpadRotate`. Also add support for touchpad smart-magnification gesture with a new event `WindowEvent::SmartMagnify`.
- **Breaking:** On web, the `WindowBuilderExtWebSys::with_prevent_default` setting (enabled by default), now additionally prevents scrolling of the webpage in mobile browsers, previously it only disabled scrolling on desktop.
- On Wayland, `wayland-csd-adwaita` now uses `ab_glyph` instead of `crossfont` to render the title for decorations.
- On Wayland, a new `wayland-csd-adwaita-crossfont` feature was added to use `crossfont` instead of `ab_glyph` for decorations.
- On Wayland, if not otherwise specified use upstream automatic CSD theme selection.
- On X11, added `WindowExtX11::with_parent` to create child windows.
- Added support for `WindowBuilder::with_theme` and `Window::theme` to support per-window dark/light/system theme configuration on macos, windows and wayland.
- On macOS, added support for `WindowEvent::ThemeChanged`.
- **Breaking:** Removed `WindowBuilderExtWindows::with_theme` and `WindowBuilderExtWayland::with_wayland_csd_theme` in favour of `WindowBuilder::with_theme`.
- **Breaking:** Removed `WindowExtWindows::theme` in favour of `Window::theme`.
- Enabled `doc_auto_cfg` when generating docs on docs.rs for feature labels.
- **Breaking:** On Android, switched to using [`android-activity`](https://github.com/rib/android-activity) crate as a glue layer instead of [`ndk-glue`](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue). See [README.md#Android](https://github.com/rust-windowing/winit#Android) for more details. ([#2444](https://github.com/rust-windowing/winit/pull/2444))
- **Breaking:** Removed support for `raw-window-handle` version `0.4`
- On Wayland, `RedrawRequested` not emitted during resize.
- Add a `set_wait_timeout` function to `ControlFlow` to allow waiting for a `Duration`.
- **Breaking:** Remove the unstable `xlib_xconnection()` function from the private interface.
- Added Orbital support for Redox OS
- On X11, added `drag_resize_window` method.
- Added `Window::set_transparent` to provide a hint about transparency of the window on Wayland and macOS.
- On macOS, fix the mouse buttons other than left/right/middle being reported as middle.
- On Wayland, support fractional scaling via the wp-fractional-scale protocol.
- On web, fix removal of mouse event listeners from the global object upon window distruction.
- Add WindowAttributes getter to WindowBuilder to allow introspection of default values.
- Added `Window::set_ime_purpose` for setting the IME purpose, currently implemented on Wayland only.
# 0.27.5
- On Wayland, fix byte offset in `Ime::Preedit` pointing to invalid bytes.
# 0.27.4
- On Windows, emit `ReceivedCharacter` events on system keybindings.
- On Windows, fixed focus event emission on minimize.
- On X11, fixed IME crashing during reload.
# 0.27.3
- On Windows, added `WindowExtWindows::set_undecorated_shadow` and `WindowBuilderExtWindows::with_undecorated_shadow` to draw the drop shadow behind a borderless window.
- On Windows, fixed default window features (ie snap, animations, shake, etc.) when decorations are disabled.
- On Windows, fixed ALT+Space shortcut to open window menu.
- On Wayland, fixed `Ime::Preedit` not being sent on IME reset.
- Fixed unbound version specified for `raw-window-handle` leading to compilation failures.
- Empty `Ime::Preedit` event will be sent before `Ime::Commit` to help clearing preedit.
- On X11, fixed IME context picking by querying for supported styles beforehand.
# 0.27.2 (2022-8-12)
- On macOS, fixed touch phase reporting when scrolling.
- On X11, fix min, max and resize increment hints not persisting for resizable windows (e.g. on DPI change).
- On Windows, respect min/max inner sizes when creating the window.
- For backwards compatibility, `Window` now (additionally) implements the old version (`0.4`) of the `HasRawWindowHandle` trait
- On Windows, added support for `EventLoopWindowTarget::set_device_event_filter`.
- On Wayland, fix user requested `WindowEvent::RedrawRequested` being delayed by a frame.
# 0.27.1 (2022-07-30)
- The minimum supported Rust version was lowered to `1.57.0` and now explicitly tested.
- On X11, fix crash on start due to inability to create an IME context without any preedit.
# 0.27.0 (2022-07-26)
- On Windows, fix hiding a maximized window.
- On Android, `ndk-glue`'s `NativeWindow` lock is now held between `Event::Resumed` and `Event::Suspended`.
- On Web, added `EventLoopExtWebSys` with a `spawn` method to start the event loop without throwing an exception.
- Added `WindowEvent::Occluded(bool)`, currently implemented on macOS and X11.
- On X11, fix events for caps lock key not being sent
- Build docs on `docs.rs` for iOS and Android as well.
- **Breaking:** Removed the `WindowAttributes` struct, since all its functionality is accessible from `WindowBuilder`.
- Added `WindowBuilder::transparent` getter to check if the user set `transparent` attribute.
- On macOS, Fix emitting `Event::LoopDestroyed` on CMD+Q.
- On macOS, fixed an issue where having multiple windows would prevent run_return from ever returning.
- On Wayland, fix bug where the cursor wouldn't hide in GNOME.
- On macOS, Windows, and Wayland, add `set_cursor_hittest` to let the window ignore mouse events.
- On Windows, added `WindowExtWindows::set_skip_taskbar` and `WindowBuilderExtWindows::with_skip_taskbar`.
- On Windows, added `EventLoopBuilderExtWindows::with_msg_hook`.
- On Windows, remove internally unique DC per window.
- On macOS, remove the need to call `set_ime_position` after moving the window.
- Added `Window::is_visible`.
- Added `Window::is_resizable`.
- Added `Window::is_decorated`.
- On X11, fix for repeated event loop iteration when `ControlFlow` was `Wait`
- On X11, fix scale factor calculation when the only monitor is reconnected
- On Wayland, report unaccelerated mouse deltas in `DeviceEvent::MouseMotion`.
- On Web, a focused event is manually generated when a click occurs to emulate behaviour of other backends.
- **Breaking:** Bump `ndk` version to 0.6, ndk-sys to `v0.3`, `ndk-glue` to `0.6`.
- Remove no longer needed `WINIT_LINK_COLORSYNC` environment variable.
- **Breaking:** Rename the `Exit` variant of `ControlFlow` to `ExitWithCode`, which holds a value to control the exit code after running. Add an `Exit` constant which aliases to `ExitWithCode(0)` instead to avoid major breakage. This shouldn't affect most existing programs.
- Add `EventLoopBuilder`, which allows you to create and tweak the settings of an event loop before creating it.
- Deprecated `EventLoop::with_user_event`; use `EventLoopBuilder::with_user_event` instead.
- **Breaking:** Replaced `EventLoopExtMacOS` with `EventLoopBuilderExtMacOS` (which also has renamed methods).
- **Breaking:** Replaced `EventLoopExtWindows` with `EventLoopBuilderExtWindows` (which also has renamed methods).
- **Breaking:** Replaced `EventLoopExtUnix` with `EventLoopBuilderExtUnix` (which also has renamed methods).
- **Breaking:** The platform specific extensions for Windows `winit::platform::windows` have changed. All `HANDLE`-like types e.g. `HWND` and `HMENU` were converted from winapi types or `*mut c_void` to `isize`. This was done to be consistent with the type definitions in windows-sys and to not expose internal dependencies.
- The internal bindings to the [Windows API](https://docs.microsoft.com/en-us/windows/) were changed from the unofficial [winapi](https://github.com/retep998/winapi-rs) bindings to the official Microsoft [windows-sys](https://github.com/microsoft/windows-rs) bindings.
- On Wayland, fix polling during consecutive `EventLoop::run_return` invocations.
- On Windows, fix race issue creating fullscreen windows with `WindowBuilder::with_fullscreen`
- On Android, `virtual_keycode` for `KeyboardInput` events is now filled in where a suitable match is found.
- Added helper methods on `ControlFlow` to set its value.
- On Wayland, fix `TouchPhase::Ended` always reporting the location of the first touch down, unless the compositor
sent a cancel or frame event.
- On iOS, send `RedrawEventsCleared` even if there are no redraw events, consistent with other platforms.
- **Breaking:** Replaced `Window::with_app_id` and `Window::with_class` with `Window::with_name` on `WindowBuilderExtUnix`.
- On Wayland, fallback CSD was replaced with proper one:
- `WindowBuilderExtUnix::with_wayland_csd_theme` to set color theme in builder.
- `WindowExtUnix::wayland_set_csd_theme` to set color theme when creating a window.
- `WINIT_WAYLAND_CSD_THEME` env variable was added, it can be used to set "dark"/"light" theme in apps that don't expose theme setting.
- `wayland-csd-adwaita` feature that enables proper CSD with title rendering using FreeType system library.
- `wayland-csd-adwaita-notitle` feature that enables CSD but without title rendering.
- On Wayland and X11, fix window not resizing with `Window::set_inner_size` after calling `Window:set_resizable(false)`.
- On Windows, fix wrong fullscreen monitors being recognized when handling WM_WINDOWPOSCHANGING messages
- **Breaking:** Added new `WindowEvent::Ime` supported on desktop platforms.
- Added `Window::set_ime_allowed` supported on desktop platforms.
- **Breaking:** IME input on desktop platforms won't be received unless it's explicitly allowed via `Window::set_ime_allowed` and new `WindowEvent::Ime` events are handled.
- On macOS, `WindowEvent::Resized` is now emitted in `frameDidChange` instead of `windowDidResize`.
- **Breaking:** On X11, device events are now ignored for unfocused windows by default, use `EventLoopWindowTarget::set_device_event_filter` to set the filter level.
- Implemented `Default` on `EventLoop<()>`.
- Implemented `Eq` for `Fullscreen`, `Theme`, and `UserAttentionType`.
- **Breaking:** `Window::set_cursor_grab` now accepts `CursorGrabMode` to control grabbing behavior.
- On Wayland, add support for `Window::set_cursor_position`.
- Fix on macOS `WindowBuilder::with_disallow_hidpi`, setting true or false by the user no matter the SO default value.
- `EventLoopBuilder::build` will now panic when the `EventLoop` is being created more than once.
- Added `From<u64>` for `WindowId` and `From<WindowId>` for `u64`.
- Added `MonitorHandle::refresh_rate_millihertz` to get monitor's refresh rate.
- **Breaking**, Replaced `VideoMode::refresh_rate` with `VideoMode::refresh_rate_millihertz` providing better precision.
- On Web, add `with_prevent_default` and `with_focusable` to `WindowBuilderExtWebSys` to control whether events should be propagated.
- On Windows, fix focus events being sent to inactive windows.
- **Breaking**, update `raw-window-handle` to `v0.5` and implement `HasRawDisplayHandle` for `Window` and `EventLoopWindowTarget`.
- On X11, add function `register_xlib_error_hook` into `winit::platform::unix` to subscribe for errors comming from Xlib.
- On Android, upgrade `ndk` and `ndk-glue` dependencies to the recently released `0.7.0`.
- All platforms can now be relied on to emit a `Resumed` event. Applications are recommended to lazily initialize graphics state and windows on first resume for portability.
- **Breaking:**: Reverse horizontal scrolling sign in `MouseScrollDelta` to match the direction of vertical scrolling. A positive X value now means moving the content to the right. The meaning of vertical scrolling stays the same: a positive Y value means moving the content down.
- On MacOS, fix deadlock when calling `set_maximized` from event loop.
# 0.26.1 (2022-01-05)
- Fix linking to the `ColorSync` framework on macOS 10.7, and in newer Rust versions.
- On Web, implement cursor grabbing through the pointer lock API.
- On X11, add mappings for numpad comma, numpad enter, numlock and pause.
- On macOS, fix Pinyin IME input by reverting a change that intended to improve IME.
- On Windows, fix a crash with transparent windows on Windows 11.
# 0.26.0 (2021-12-01)
- Update `raw-window-handle` to `v0.4`. This is _not_ a breaking change, we still implement `HasRawWindowHandle` from `v0.3`, see [rust-windowing/raw-window-handle#74](https://github.com/rust-windowing/raw-window-handle/pull/74). Note that you might have to run `cargo update -p raw-window-handle` after upgrading.
- On X11, bump `mio` to 0.8.
- On Android, fixed `WindowExtAndroid::config` initially returning an empty `Configuration`.
- On Android, fixed `Window::scale_factor` and `MonitorHandle::scale_factor` initially always returning 1.0.
- On X11, select an appropriate visual for transparency if is requested
- On Wayland and X11, fix diagonal window resize cursor orientation.
- On macOS, drop the event callback before exiting.
- On Android, implement `Window::request_redraw`
- **Breaking:** On Web, remove the `stdweb` backend.
- Added `Window::focus_window`to bring the window to the front and set input focus.
- On Wayland and X11, implement `is_maximized` method on `Window`.
- On Windows, prevent ghost window from showing up in the taskbar after either several hours of use or restarting `explorer.exe`.
- On macOS, fix issue where `ReceivedCharacter` was not being emitted during some key repeat events.
- On Wayland, load cursor icons `hand2` and `hand1` for `CursorIcon::Hand`.
- **Breaking:** On Wayland, Theme trait and its support types are dropped.
- On Wayland, bump `smithay-client-toolkit` to 0.15.1.
- On Wayland, implement `request_user_attention` with `xdg_activation_v1`.
- On X11, emit missing `WindowEvent::ScaleFactorChanged` when the only monitor gets reconnected.
- On X11, if RANDR based scale factor is higher than 20 reset it to 1
- On Wayland, add an enabled-by-default feature called `wayland-dlopen` so users can opt out of using `dlopen` to load system libraries.
- **Breaking:** On Android, bump `ndk` and `ndk-glue` to 0.5.
- On Windows, increase wait timer resolution for more accurate timing when using `WaitUntil`.
- On macOS, fix native file dialogs hanging the event loop.
- On Wayland, implement a workaround for wrong configure size when using `xdg_decoration` in `kwin_wayland`
- On macOS, fix an issue that prevented the menu bar from showing in borderless fullscreen mode.
- On X11, EINTR while polling for events no longer causes a panic. Instead it will be treated as a spurious wakeup.
# 0.25.0 (2021-05-15)
- **Breaking:** On macOS, replace `WindowBuilderExtMacOS::with_activation_policy` with `EventLoopExtMacOS::set_activation_policy`
- On macOS, wait with activating the application until the application has initialized.
- On macOS, fix creating new windows when the application has a main menu.
- On Windows, fix fractional deltas for mouse wheel device events.
- On macOS, fix segmentation fault after dropping the main window.
- On Android, `InputEvent::KeyEvent` is partially implemented providing the key scancode.
- Added `is_maximized` method to `Window`.
- On Windows, fix bug where clicking the decoration bar would make the cursor blink.
- On Windows, fix bug causing newly created windows to erroneously display the "wait" (spinning) cursor.
- On macOS, wake up the event loop immediately when a redraw is requested.
- On Windows, change the default window size (1024x768) to match the default on other desktop platforms (800x600).
- On Windows, fix bug causing mouse capture to not be released.
- On Windows, fix fullscreen not preserving minimized/maximized state.
- On Android, unimplemented events are marked as unhandled on the native event loop.
- On Windows, added `WindowBuilderExtWindows::with_menu` to set a custom menu at window creation time.
- On Android, bump `ndk` and `ndk-glue` to 0.3: use predefined constants for event `ident`.
- On macOS, fix objects captured by the event loop closure not being dropped on panic.
- On Windows, fixed `WindowEvent::ThemeChanged` not properly firing and fixed `Window::theme` returning the wrong theme.
- On Web, added support for `DeviceEvent::MouseMotion` to listen for relative mouse movements.
- Added `WindowBuilder::with_position` to allow setting the position of a `Window` on creation. Supported on Windows, macOS and X11.
- Added `Window::drag_window`. Implemented on Windows, macOS, X11 and Wayland.
- On X11, bump `mio` to 0.7.
- On Windows, added `WindowBuilderExtWindows::with_owner_window` to allow creating popup windows.
- On Windows, added `WindowExtWindows::set_enable` to allow creating modal popup windows.
- On macOS, emit `RedrawRequested` events immediately while the window is being resized.
- Implement `Default`, `Hash`, and `Eq` for `LogicalPosition`, `PhysicalPosition`, `LogicalSize`, and `PhysicalSize`.
- On macOS, initialize the Menu Bar with minimal defaults. (Can be prevented using `enable_default_menu_creation`)
- On macOS, change the default behavior for first click when the window was unfocused. Now the window becomes focused and then emits a `MouseInput` event on a "first mouse click".
- Implement mint (math interoperability standard types) conversions (under feature flag `mint`).
# 0.24.0 (2020-12-09)
- On Windows, fix applications not exiting gracefully due to thread_event_target_callback accessing corrupted memory.
- On Windows, implement `Window::set_ime_position`.
- **Breaking:** On Windows, Renamed `WindowBuilderExtWindows`'s `is_dark_mode` to `theme`.
- **Breaking:** On Windows, renamed `WindowBuilderExtWindows::is_dark_mode` to `theme`.
- On Windows, add `WindowBuilderExtWindows::with_theme` to set a preferred theme.
- On Windows, fix bug causing message boxes to appear delayed.
- On Android, calling `WindowEvent::Focused` now works properly instead of always returning false.
- On Windows, fix Alt-Tab behaviour by removing borderless fullscreen "always on top" flag.
- On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions.
- **Breaking:** On Windows, include prefix byte in scancodes.
- On Wayland, fix window not being resizeable when using `WindowBuilder::with_min_inner_size`.
- On Unix, fix cross-compiling to wasm32 without enabling X11 or Wayland.
- On Windows, fix use-after-free crash during window destruction.
- On Web, fix `WindowEvent::ReceivedCharacter` never being sent on key input.
- On macOS, fix compilation when targeting aarch64.
- On X11, fix `Window::request_redraw` not waking the event loop.
- On Wayland, the keypad arrow keys are now recognized.
- **Breaking** Rename `desktop::EventLoopExtDesktop` to `run_return::EventLoopExtRunReturn`.
- Added `request_user_attention` method to `Window`.
- **Breaking:** On macOS, removed `WindowExt::request_user_attention`, use `Window::request_user_attention`.
- **Breaking:** On X11, removed `WindowExt::set_urgent`, use `Window::request_user_attention`.
- On Wayland, default font size in CSD increased from 11 to 17.
- On Windows, fix bug causing message boxes to appear delayed.
- On Android, support multi-touch.
- On Wayland, extra mouse buttons are not dropped anymore.
- **Breaking**: `MouseButton::Other` now uses `u16`.
# 0.23.0 (2020-10-02)
- On iOS, fixed support for the "Debug View Hierarchy" feature in Xcode.
- On all platforms, `available_monitors` and `primary_monitor` are now on `EventLoopWindowTarget` rather than `EventLoop` to list monitors event in the event loop.
- On Unix, X11 and Wayland are now optional features (enabled by default)
- On X11, fix deadlock when calling `set_fullscreen_inner`.
- On Web, prevent the webpage from scrolling when the user is focused on a winit canvas
- On Web, calling `window.set_cursor_icon` no longer breaks HiDPI scaling
- On Windows, drag and drop is now optional (enabled by default) and can be disabled with `WindowBuilderExtWindows::with_drag_and_drop(false)`.
- On Wayland, fix deadlock when calling to `set_inner_size` from a callback.
- On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`.
- On android added support for `run_return`.
- On MacOS, Fixed fullscreen and dialog support for `run_return`.
- On Windows, fix bug where we'd try to emit `MainEventsCleared` events during nested win32 event loops.
- On Web, use mouse events if pointer events aren't supported. This affects Safari.
- On Windows, `set_ime_position` is now a no-op instead of a runtime crash.
- On Android, `set_fullscreen` is now a no-op instead of a runtime crash.
- On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash.
- On Android, fix `ControlFlow::Poll` not polling the Android event queue.
- On macOS, add `NSWindow.hasShadow` support.
- On Web, fix vertical mouse wheel scrolling being inverted.
- On Web, implement mouse capturing for click-dragging out of the canvas.
- On Web, fix `ControlFlow::Exit` not properly handled.
- On Web (web-sys only), send `WindowEvent::ScaleFactorChanged` event when `window.devicePixelRatio` is changed.
- **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error.
- **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`.
- On NetBSD, fixed crash due to incorrect detection of the main thread.
- **Breaking:** On X11, `-` key is mapped to the `Minus` virtual key code, instead of `Subtract`.
- On macOS, fix inverted horizontal scroll.
- **Breaking:** `current_monitor` now returns `Option<MonitorHandle>`.
- **Breaking:** `primary_monitor` now returns `Option<MonitorHandle>`.
- On macOS, updated core-* dependencies and cocoa.
- Bump `parking_lot` to 0.11
- On Android, bump `ndk`, `ndk-sys` and `ndk-glue` to 0.2. Checkout the new ndk-glue main proc attribute.
- On iOS, fixed starting the app in landscape where the view still had portrait dimensions.
- Deprecate the stdweb backend, to be removed in a future release
- **Breaking:** Prefixed virtual key codes `Add`, `Multiply`, `Divide`, `Decimal`, and `Subtract` with `Numpad`.
- Added `Asterisk` and `Plus` virtual key codes.
- On Web (web-sys only), the `Event::LoopDestroyed` event is correctly emitted when leaving the page.
- On Web, the `WindowEvent::Destroyed` event now gets emitted when a `Window` is dropped.
- On Web (web-sys only), the event listeners are now removed when a `Window` is dropped or when the event loop is destroyed.
- On Web, the event handler closure passed to `EventLoop::run` now gets dropped after the event loop is destroyed.
- **Breaking:** On Web, the canvas element associated to a `Window` is no longer removed from the DOM when the `Window` is dropped.
- On Web, `WindowEvent::Resized` is now emitted when `Window::set_inner_size` is called.
- **Breaking:** `Fullscreen` enum now uses `Borderless(Option<MonitorHandle>)` instead of `Borderless(MonitorHandle)` to allow picking the current monitor.
- On MacOS, fix `WindowEvent::Moved` ignoring the scale factor.
- On Wayland, add missing virtual keycodes.
- On Wayland, implement proper `set_cursor_grab`.
- On Wayland, the cursor will use similar icons if the requested one isn't available.
- On Wayland, right clicking on client side decorations will request application menu.
- On Wayland, fix tracking of window size after state changes.
- On Wayland, fix client side decorations not being hidden properly in fullscreen.
- On Wayland, fix incorrect size event when entering fullscreen with client side decorations.
- On Wayland, fix `resizable` attribute not being applied properly on startup.
- On Wayland, fix disabled repeat rate not being handled.
- On Wayland, fix decoration buttons not working after tty switch.
- On Wayland, fix scaling not being applied on output re-enable.
- On Wayland, fix crash when `XCURSOR_SIZE` is `0`.
- On Wayland, fix pointer getting created in some cases without pointer capability.
- On Wayland, on kwin, fix space between window and decorations on startup.
- **Breaking:** On Wayland, `Theme` trait was reworked.
- On Wayland, disable maximize button for non-resizable window.
- On Wayland, added support for `set_ime_position`.
- On Wayland, fix crash on startup since GNOME 3.37.90.
- On X11, fix incorrect modifiers state on startup.
# 0.22.2 (2020-05-16)
- Added Clone implementation for 'static events.
- On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`.
- On Windows, fix `WindowBuilder::with_maximized` being ignored.
- On Android, minimal platform support.
- On iOS, touch positions are now properly converted to physical pixels.
- On macOS, updated core-* dependencies and cocoa
# 0.22.1 (2020-04-16)
- On X11, fix `ResumeTimeReached` being fired too early.
- On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame`
- On Web, fix a possible panic during event handling
- On macOS, fix `EventLoopProxy` leaking memory for every instance.
# 0.22.0 (2020-03-09)
- On Windows, fix minor timing issue in wait_until_time_or_msg
- On Windows, rework handling of request_redraw() to address panics.
- On macOS, fix `set_simple_screen` to remember frame excluding title bar.
- On Wayland, fix coordinates in touch events when scale factor isn't 1.
- On Wayland, fix color from `close_button_icon_color` not applying.
- Ignore locale if unsupported by X11 backend
- On Wayland, Add HiDPI cursor support
- On Web, add the ability to query "Light" or "Dark" system theme send `ThemeChanged` on change.
- Fix `Event::to_static` returning `None` for user events.
- On Wayland, Hide CSD for fullscreen windows.
- On Windows, ignore spurious mouse move messages.
- **Breaking:** Move `ModifiersChanged` variant from `DeviceEvent` to `WindowEvent`.
- On Windows, add `IconExtWindows` trait which exposes creating an `Icon` from an external file or embedded resource
- Add `BadIcon::OsError` variant for when OS icon functionality fails
- On Windows, fix crash at startup on systems that do not properly support Windows' Dark Mode
- Revert On macOS, fix not sending ReceivedCharacter event for specific keys combinations.
- on macOS, fix incorrect ReceivedCharacter events for some key combinations.
- **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`.
- On macOS, a mouse motion event is now generated before every mouse click.
# 0.21.0 (2020-02-04)
- On Windows, fixed "error: linking with `link.exe` failed: exit code: 1120" error on older versions of windows.
- On macOS, fix set_minimized(true) works only with decorations.
- On macOS, add `hide_application` to `EventLoopWindowTarget` via a new `EventLoopWindowTargetExtMacOS` trait. `hide_application` will hide the entire application by calling `-[NSApplication hide: nil]`.
- On macOS, fix not sending ReceivedCharacter event for specific keys combinations.
- On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates.
- On macOS, fix issue where unbundled applications would sometimes open without being focused.
- On macOS, fix `run_return` does not return unless it receives a message.
- On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop.
- On X11, fix deadlock on window state when handling certain window events.
- `WindowBuilder` now implements `Default`.
- **Breaking:** `WindowEvent::CursorMoved` changed to `f64` units, preserving high-precision data supplied by most backends
- On Wayland, fix coordinates in mouse events when scale factor isn't 1
- On Web, add the ability to provide a custom canvas
- **Breaking:** On Wayland, the `WaylandTheme` struct has been replaced with a `Theme` trait, allowing for extra configuration
# 0.20.0 (2020-01-05)
- On X11, fix `ModifiersChanged` emitting incorrect modifier change events
- **Breaking**: Overhaul how Winit handles DPI:
- Window functions and events now return `PhysicalSize` instead of `LogicalSize`.
- Functions that take `Size` or `Position` types can now take either `Logical` or `Physical` types.
- `hidpi_factor` has been renamed to `scale_factor`.
- `HiDpiFactorChanged` has been renamed to `ScaleFactorChanged`, and lets you control how the OS
resizes the window in response to the change.
- On X11, deprecate `WINIT_HIDPI_FACTOR` environment variable in favor of `WINIT_X11_SCALE_FACTOR`.
- `Size` and `Position` types are now generic over their exact pixel type.
# 0.20.0 Alpha 6 (2020-01-03)
- On macOS, fix `set_cursor_visible` hides cursor outside of window.
- On macOS, fix `CursorEntered` and `CursorLeft` events fired at old window size.
- On macOS, fix error when `set_fullscreen` is called during fullscreen transition.
- On all platforms except mobile and WASM, implement `Window::set_minimized`.
- On X11, fix `CursorEntered` event being generated for non-winit windows.
- On macOS, fix crash when starting maximized without decorations.
- On macOS, fix application not terminating on `run_return`.
- On Wayland, fix cursor icon updates on window borders when using CSD.
- On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode.
- On Windows, theme the title bar according to whether the system theme is "Light" or "Dark".
- Added `WindowEvent::ThemeChanged` variant to handle changes to the system theme. Currently only implemented on Windows.
- **Breaking**: Changes to the `RedrawRequested` event (#1041):
- `RedrawRequested` has been moved from `WindowEvent` to `Event`.
- `EventsCleared` has been renamed to `MainEventsCleared`.
- `RedrawRequested` is now issued only after `MainEventsCleared`.
- `RedrawEventsCleared` is issued after each set of `RedrawRequested` events.
- Implement synthetic window focus key events on Windows.
- **Breaking**: Change `ModifiersState` to a `bitflags` struct.
- On Windows, implement `VirtualKeyCode` translation for `LWin` and `RWin`.
- On Windows, fix closing the last opened window causing `DeviceEvent`s to stop getting emitted.
- On Windows, fix `Window::set_visible` not setting internal flags correctly. This resulted in some weird behavior.
- Add `DeviceEvent::ModifiersChanged`.
- Deprecate `modifiers` fields in other events in favor of `ModifiersChanged`.
- On X11, `WINIT_HIDPI_FACTOR` now dominates `Xft.dpi` when picking DPI factor for output.
- On X11, add special value `randr` for `WINIT_HIDPI_FACTOR` to make winit use self computed DPI factor instead of the one from `Xft.dpi`.
# 0.20.0 Alpha 5 (2019-12-09)
- On macOS, fix application termination on `ControlFlow::Exit`
- On Windows, fix missing `ReceivedCharacter` events when Alt is held.
- On macOS, stop emitting private corporate characters in `ReceivedCharacter` events.
- On X11, fix misreporting DPI factor at startup.
- On X11, fix events not being reported when using `run_return`.
- On X11, fix key modifiers being incorrectly reported.
- On X11, fix window creation hanging when another window is fullscreen.
- On Windows, fix focusing unfocused windows when switching from fullscreen to windowed.
- On X11, fix reporting incorrect DPI factor when waking from suspend.
- Change `EventLoopClosed` to contain the original event.
- **Breaking**: Add `is_synthetic` field to `WindowEvent` variant `KeyboardInput`,
indicating that the event is generated by winit.
- On X11, generate synthetic key events for keys held when a window gains or loses focus.
- On X11, issue a `CursorMoved` event when a `Touch` event occurs,
as X11 implicitly moves the cursor for such events.
# 0.20.0 Alpha 4 (2019-10-18)
- Add web support via the 'stdweb' or 'web-sys' features
- On Windows, implemented function to get HINSTANCE
- On macOS, implement `run_return`.
- On iOS, fix inverted parameter in `set_prefers_home_indicator_hidden`.
- On X11, performance is improved when rapidly calling `Window::set_cursor_icon`.
- On iOS, fix improper `msg_send` usage that was UB and/or would break if `!` is stabilized.
- On Windows, unset `maximized` when manually changing the window's position or size.
- On Windows, add touch pressure information for touch events.
- On macOS, differentiate between `CursorIcon::Grab` and `CursorIcon::Grabbing`.
- On Wayland, fix event processing sometimes stalling when using OpenGL with vsync.
- Officially remove the Emscripten backend.
- On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`.
- On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode.
- On iOS, fix panic upon closing the app.
- On X11, allow setting mulitple `XWindowType`s.
- On iOS, fix null window on initial `HiDpiFactorChanged` event.
- On Windows, fix fullscreen window shrinking upon getting restored to a normal window.
- On macOS, fix events not being emitted during modal loops, such as when windows are being resized
by the user.
- On Windows, fix hovering the mouse over the active window creating an endless stream of CursorMoved events.
- Always dispatch a `RedrawRequested` event after creating a new window.
- On X11, return dummy monitor data to avoid panicking when no monitors exist.
- On X11, prevent stealing input focus when creating a new window.
Only steal input focus when entering fullscreen mode.
- On Wayland, fixed DeviceEvents for relative mouse movement is not always produced
- On Wayland, add support for set_cursor_visible and set_cursor_grab.
- On Wayland, fixed DeviceEvents for relative mouse movement is not always produced.
- Removed `derivative` crate dependency.
- On Wayland, add support for set_cursor_icon.
- Use `impl Iterator<Item = MonitorHandle>` instead of `AvailableMonitorsIter` consistently.
- On macOS, fix fullscreen state being updated after entering fullscreen instead of before,
resulting in `Window::fullscreen` returning the old state in `Resized` events instead of
reflecting the new fullscreen state
- On X11, fix use-after-free during window creation
- On Windows, disable monitor change keyboard shortcut while in exclusive fullscreen.
- On Windows, ensure that changing a borderless fullscreen window's monitor via keyboard shortcuts keeps the window fullscreen on the new monitor.
- Prevent `EventLoop::new` and `EventLoop::with_user_event` from getting called outside the main thread.
- This is because some platforms cannot run the event loop outside the main thread. Preventing this
reduces the potential for cross-platform compatibility gotchyas.
- On Windows and Linux X11/Wayland, add platform-specific functions for creating an `EventLoop` outside the main thread.
- On Wayland, drop resize events identical to the current window size.
- On Windows, fix window rectangle not getting set correctly on high-DPI systems.
# 0.20.0 Alpha 3 (2019-08-14)
- On macOS, drop the run closure on exit.
- On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates.
- On X11, fix delayed events after window redraw.
- On macOS, add `WindowBuilderExt::with_disallow_hidpi` to have the option to turn off best resolution openGL surface.
- On Windows, screen saver won't start if the window is in fullscreen mode.
- Change all occurrences of the `new_user_event` method to `with_user_event`.
- On macOS, the dock and the menu bar are now hidden in fullscreen mode.
- `Window::set_fullscreen` now takes `Option<Fullscreen>` where `Fullscreen`
consists of `Fullscreen::Exclusive(VideoMode)` and
`Fullscreen::Borderless(MonitorHandle)` variants.
- Adds support for exclusive fullscreen mode.
- On iOS, add support for hiding the home indicator.
- On iOS, add support for deferring system gestures.
- On iOS, fix a crash that occurred while acquiring a monitor's name.
- On iOS, fix armv7-apple-ios compile target.
- Removed the `T: Clone` requirement from the `Clone` impl of `EventLoopProxy<T>`.
- On iOS, disable overscan compensation for external displays (removes black
bars surrounding the image).
- On Linux, the functions `is_wayland`, `is_x11`, `xlib_xconnection` and `wayland_display` have been moved to a new `EventLoopWindowTargetExtUnix` trait.
- On iOS, add `set_prefers_status_bar_hidden` extension function instead of
hijacking `set_decorations` for this purpose.
- On macOS and iOS, corrected the auto trait impls of `EventLoopProxy`.
- On iOS, add touch pressure information for touch events.
- Implement `raw_window_handle::HasRawWindowHandle` for `Window` type on all supported platforms.
- On macOS, fix the signature of `-[NSView drawRect:]`.
- On iOS, fix the behavior of `ControlFlow::Poll`. It wasn't polling if that was the only mode ever used by the application.
- On iOS, fix DPI sent out by views on creation was `0.0` - now it gives a reasonable number.
- On iOS, RedrawRequested now works for gl/metal backed views.
- On iOS, RedrawRequested is generally ordered after EventsCleared.
# 0.20.0 Alpha 2 (2019-07-09)
- On X11, non-resizable windows now have maximize explicitly disabled.
- On Windows, support paths longer than MAX_PATH (260 characters) in `WindowEvent::DroppedFile`
and `WindowEvent::HoveredFile`.
- On Mac, implement `DeviceEvent::Button`.
- Change `Event::Suspended(true / false)` to `Event::Suspended` and `Event::Resumed`.
- On X11, fix sanity check which checks that a monitor's reported width and height (in millimeters) are non-zero when calculating the DPI factor.
- Revert the use of invisible surfaces in Wayland, which introduced graphical glitches with OpenGL (#835)
- On X11, implement `_NET_WM_PING` to allow desktop environment to kill unresponsive programs.
- On Windows, when a window is initially invisible, it won't take focus from the existing visible windows.
- On Windows, fix multiple calls to `request_redraw` during `EventsCleared` sending multiple `RedrawRequested events.`
- On Windows, fix edge case where `RedrawRequested` could be dispatched before input events in event loop iteration.
- On Windows, fix timing issue that could cause events to be improperly dispatched after `RedrawRequested` but before `EventsCleared`.
- On macOS, drop unused Metal dependency.
- On Windows, fix the trail effect happening on transparent decorated windows. Borderless (or un-decorated) windows were not affected.
- On Windows, fix `with_maximized` not properly setting window size to entire window.
- On macOS, change `WindowExtMacOS::request_user_attention()` to take an `enum` instead of a `bool`.
# 0.20.0 Alpha 1 (2019-06-21)
- Changes below are considered **breaking**.
- Change all occurrences of `EventsLoop` to `EventLoop`.
- Previously flat API is now exposed through `event`, `event_loop`, `monitor`, and `window` modules.
- `os` module changes:
- Renamed to `platform`.
- All traits now have platform-specific suffixes.
- Exposes new `desktop` module on Windows, Mac, and Linux.
- Changes to event loop types:
- `EventLoopProxy::wakeup` has been removed in favor of `send_event`.
- **Major:** New `run` method drives winit event loop.
- Returns `!` to ensure API behaves identically across all supported platforms.
- This allows `emscripten` implementation to work without lying about the API.
- `ControlFlow`'s variants have been replaced with `Wait`, `WaitUntil(Instant)`, `Poll`, and `Exit`.
- Is read after `EventsCleared` is processed.
- `Wait` waits until new events are available.
- `WaitUntil` waits until either new events are available or the provided time has been reached.
- `Poll` instantly resumes the event loop.
- `Exit` aborts the event loop.
- Takes a closure that implements `'static + FnMut(Event<T>, &EventLoop<T>, &mut ControlFlow)`.
- `&EventLoop<T>` is provided to allow new `Window`s to be created.
- **Major:** `platform::desktop` module exposes `EventLoopExtDesktop` trait with `run_return` method.
- Behaves identically to `run`, but returns control flow to the calling context and can take non-`'static` closures.
- `EventLoop`'s `poll_events` and `run_forever` methods have been removed in favor of `run` and `run_return`.
- Changes to events:
- Remove `Event::Awakened` in favor of `Event::UserEvent(T)`.
- Can be sent with `EventLoopProxy::send_event`.
- Rename `WindowEvent::Refresh` to `WindowEvent::RedrawRequested`.
- `RedrawRequested` can be sent by the user with the `Window::request_redraw` method.
- `EventLoop`, `EventLoopProxy`, and `Event` are now generic over `T`, for use in `UserEvent`.
- **Major:** Add `NewEvents(StartCause)`, `EventsCleared`, and `LoopDestroyed` variants to `Event`.
- `NewEvents` is emitted when new events are ready to be processed by event loop.
- `StartCause` describes why new events are available, with `ResumeTimeReached`, `Poll`, `WaitCancelled`, and `Init` (sent once at start of loop).
- `EventsCleared` is emitted when all available events have been processed.
- Can be used to perform logic that depends on all events being processed (e.g. an iteration of a game loop).
- `LoopDestroyed` is emitted when the `run` or `run_return` method is about to exit.
- Rename `MonitorId` to `MonitorHandle`.
- Removed `serde` implementations from `ControlFlow`.
- Rename several functions to improve both internal consistency and compliance with Rust API guidelines.
- Remove `WindowBuilder::multitouch` field, since it was only implemented on a few platforms. Multitouch is always enabled now.
- **Breaking:** On macOS, change `ns` identifiers to use snake_case for consistency with iOS's `ui` identifiers.
- Add `MonitorHandle::video_modes` method for retrieving supported video modes for the given monitor.
- On Wayland, the window now exists even if nothing has been drawn.
- On Windows, fix initial dimensions of a fullscreen window.
- On Windows, Fix transparent borderless windows rendering wrong.
# Version 0.19.1 (2019-04-08)
@@ -928,9 +10,6 @@ and `WindowEvent::HoveredFile`.
- On Windows, fix `CursorMoved(0, 0)` getting dispatched on window focus.
- On macOS, fix command key event left and right reverse.
- On FreeBSD, NetBSD, and OpenBSD, fix build of X11 backend.
- On Linux, the numpad's add, subtract and divide keys are now mapped to the `Add`, `Subtract` and `Divide` virtual key codes
- On macOS, the numpad's subtract key has been added to the `Subtract` mapping
- On Wayland, the numpad's home, end, page up and page down keys are now mapped to the `Home`, `End`, `PageUp` and `PageDown` virtual key codes
- On Windows, fix icon not showing up in corner of window.
- On X11, change DPI scaling factor behavior. First, winit tries to read it from "Xft.dpi" XResource, and uses DPI calculation from xrandr dimensions as fallback behavior.
@@ -941,7 +20,7 @@ and `WindowEvent::HoveredFile`.
- On Wayland, fix `with_title()` not setting the windows title
- On Wayland, add `set_wayland_theme()` to control client decoration color theme
- Added serde serialization to `os::unix::XWindowType`.
- **Breaking:** Remove the `icon_loading` feature and the associated `image` dependency.
- **Breaking:** `image` crate upgraded to 0.21. This is exposed as part of the `icon_loading` API.
- On X11, make event loop thread safe by replacing XNextEvent with select(2) and XCheckIfEvent
- On Windows, fix malformed function pointer typecast that could invoke undefined behavior.
- Refactored Windows state/flag-setting code.
@@ -949,6 +28,9 @@ and `WindowEvent::HoveredFile`.
- On Windows, cursor grabs used to get perpetually canceled when the grabbing window lost focus. Now, cursor grabs automatically get re-initialized when the window regains focus and the mouse moves over the client area.
- On Windows, only vertical mouse wheel events were handled. Now, horizontal mouse wheel events are also handled.
- On Windows, ignore the AltGr key when populating the `ModifersState` type.
- On Linux, the numpad's add, subtract and divide keys are now mapped to the `Add`, `Subtract` and `Divide` virtual key codes
- On macOS, the numpad's subtract key has been added to the `Subtract` mapping
- On Wayland, the numpad's home, end, page up and page down keys are now mapped to the `Home`, `End`, `PageUp` and `PageDown` virtual key codes
# Version 0.18.1 (2018-12-30)
@@ -1035,7 +117,7 @@ and `WindowEvent::HoveredFile`.
# Version 0.16.1 (2018-07-02)
- Added logging through `log`. Logging will become more extensive over time.
- On X11 and Windows, the window's DPI factor is guessed before creating the window. This _greatly_ cuts back on unsightly auto-resizing that would occur immediately after window creation.
- On X11 and Windows, the window's DPI factor is guessed before creating the window. This *greatly* cuts back on unsightly auto-resizing that would occur immediately after window creation.
- Fixed X11 backend compilation for environments where `c_char` is unsigned.
# Version 0.16.0 (2018-06-25)
@@ -1185,7 +267,7 @@ and `WindowEvent::HoveredFile`.
# Version 0.10.1 (2018-02-05)
_Yanked_
*Yanked*
# Version 0.10.0 (2017-12-27)

View File

@@ -1,3 +1,7 @@
# PLEASE MAKE PRs AGAINST THE `eventloop-2.0` BRANCH.
All development work for our next version is being done against that branch. Refer to [#459](https://github.com/rust-windowing/winit/issues/459) and [the associated milestone](https://github.com/rust-windowing/winit/milestone/2) for details on what that branch changes.
# Winit Contributing Guidelines
## Scope
@@ -11,7 +15,7 @@ may be worth creating a separate crate that extends Winit's API to add that func
When reporting an issue, in order to help the maintainers understand what the problem is, please make
your description of the issue as detailed as possible:
- if it is a bug, please provide a clear explanation of what happens, what should happen, and how to
- if it is a bug, please provide clear explanation of what happens, what should happen, and how to
reproduce the issue, ideally by providing a minimal program exhibiting the problem
- if it is a feature request, please provide a clear argumentation about why you believe this feature
should be supported by winit
@@ -20,52 +24,35 @@ your description of the issue as detailed as possible:
When making a code contribution to winit, before opening your pull request, please make sure that:
- your patch builds with Winit's minimal supported rust version - Rust 1.70.
- you tested your modifications on all the platforms impacted, or if not possible, detail which platforms
- you tested your modifications on all the platforms impacted, or if not possible detail which platforms
were not tested, and what should be tested, so that a maintainer or another contributor can test them
- you updated any relevant documentation in winit
- you left comments in your code explaining any part that is not straightforward, so that the
maintainers and future contributors don't have to try to guess what your code is supposed to do
- your PR adds an entry to the changelog file if the introduced change is relevant to winit users.
You needn't worry about the added entry causing conflicts, the maintainer that merges the PR will
handle those for you when merging (see below).
- your PR adds an entry to the changelog file if the introduced change is relevant to winit users
- if your PR affects the platform compatibility of one or more features or adds another feature, the
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
should be updated.
Once your PR is open, you can ask for a review by a maintainer of your platform. Winit's merging policy
Once your PR is open, you can ask for review by a maintainer of your platform. Winit's merging policy
is that a PR must be approved by at least two maintainers of winit before being merged, including
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
Once your PR is deemed ready, the merging maintainer will take care of resolving conflicts in
`CHANGELOG.md` (but you must resolve other conflicts yourself). Doing this requires that you check the
"give contributors write access to the branch" checkbox when creating the PR.
## Maintainers & Testers
The current maintainers are listed in the [CODEOWNERS](.github/CODEOWNERS) file.
Winit is managed by several people, each with their specialities, and each maintaining a subset of the
backends of winit. As such, depending on your platform of interest, your contacts will be different.
If you are interested in being pinged when testing is needed for a specific platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
This table summarizes who can be contacted in which case, with the following legend:
## Release process
- `M` - Maintainer: is a main maintainer for this platform
- `C` - Collaborator: can review code and address issues on this platform
- `T` - Tester: has the ability of testing the platform
- ` `: knows nothing of this platform
Given that winit is a widely used library, we should be able to make a patch
releases at any time we want without blocking the development of new features.
To achieve these goals, a new branch is created for every new release. Releases and later patch releases are committed and tagged in this branch.
The exact steps for an exemplary `0.2.0` release might look like this:
1. Initially, the version on the latest master is `0.1.0`
2. A new `v0.2.x` branch is created for the release
3. In the branch, the version is bumped to `v0.2.0`
4. The new commit in the branch is tagged `v0.2.0`
5. The version is pushed to crates.io
6. A GitHub release is created for the `v0.2.0` tag
7. On master, the version is bumped to `0.2.0`, and the CHANGELOG is updated
When doing a patch release, the process is similar:
1. Initially, the version of the latest release is `0.2.0`
2. Checkout the `v0.2.x` branch
3. Cherry-pick the required non-breaking changes into the `v0.2.x`
4. Follow steps 3-7 of the regular release example
| Platform | Windows | macOS | X11 | Wayland | Android | iOS | Emscripten |
| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| @mitchmindtree | T | | T | T | | | |
| @Osspial | M | | T | T | T | | T |
| @vberger | | | T | M | | | |
| @mtak- | | T | | | T | M | |

View File

@@ -1,16 +1,70 @@
[workspace]
members = [
"run-wasm",
"winit",
"winit-core"
]
resolver = "2"
[package]
name = "winit"
version = "0.19.2"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
keywords = ["windowing"]
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/rust-windowing/winit"
documentation = "https://docs.rs/winit"
categories = ["gui"]
[workspace.dependencies]
bitflags = "2"
cfg_aliases = "0.2.0"
cursor-icon = "1.1.0"
serde = { version = "1", features = ["serde_derive"] }
smol_str = "0.2.0"
web-time = "1"
winit-core = { path = "./winit-core", default-features = false, features = ["std"] }
[package.metadata.docs.rs]
features = ["icon_loading", "serde"]
[features]
icon_loading = ["image"]
[dependencies]
lazy_static = "1"
libc = "0.2"
log = "0.4"
image = { version = "0.21", optional = true }
serde = { version = "1", optional = true, features = ["serde_derive"] }
[target.'cfg(target_os = "android")'.dependencies.android_glue]
version = "0.2"
[target.'cfg(target_os = "ios")'.dependencies]
objc = "0.2.3"
[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2.3"
cocoa = "0.18.4"
core-foundation = "0.6"
core-graphics = "0.17.3"
[target.'cfg(target_os = "windows")'.dependencies]
backtrace = "0.3"
bitflags = "1"
[target.'cfg(target_os = "windows")'.dependencies.winapi]
version = "0.3.6"
features = [
"combaseapi",
"dwmapi",
"errhandlingapi",
"hidusage",
"libloaderapi",
"objbase",
"ole2",
"processthreadsapi",
"shellapi",
"shellscalingapi",
"shobjidl_core",
"unknwnbase",
"winbase",
"windowsx",
"winerror",
"wingdi",
"winnt",
"winuser",
]
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
wayland-client = { version = "0.21", features = [ "dlopen", "egl", "cursor"] }
smithay-client-toolkit = "0.4.3"
x11-dl = "2.18.3"
parking_lot = "0.8"
percent-encoding = "2.0"

View File

@@ -1,21 +1,19 @@
# Winit Scope
Winit aims to expose an interface that abstracts over window creation and input handling and can
be used to create both games and applications. It supports the following main graphical platforms:
Winit aims to expose an interface that abstracts over window creation and input handling, and can
be used to create both games and applications. It supports the main graphical platforms:
- Desktop
- Windows 7+ (10+ is tested regularly)
- macOS 10.7+ (10.14+ is tested regularly)
- Windows
- macOS
- Unix
- via X11
- via Wayland
- Redox OS, via Orbital
- Mobile
- iOS
- Android
- Web
- Chrome
- Firefox
- Safari 13.1+
- via Emscripten
- via WASM
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
aim to support every single feature of every platform, but rather to abstract over the common features
@@ -45,10 +43,10 @@ be released and the library will enter maintenance mode. For the most part, new
be added past this point. New platform features may be accepted and exposed through point releases.
### Tier upgrades
Some platform features could, in theory, be exposed across multiple platforms, but have not gone
Some platform features could in theory be exposed across multiple platforms, but have not gone
through the implementation work necessary to function on all platforms. When one of these features
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
If that gets accepted, the platform-specific functions get deprecated and become permanently
If that gets accepted, the platform-specific functions gets deprecated and become permanently
exposed through the core, cross-platform API.
# Features
@@ -83,13 +81,9 @@ If your PR makes notable changes to Winit's features, please update this section
- **Window maximization**: The windows created by winit can be maximized upon creation.
- **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after
creation.
- **Window minimization**: The windows created by winit can be minimized after creation.
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation.
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
for fullscreen windows and, if applicable, captures the monitor for exclusive
use by this application.
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
@@ -98,39 +92,26 @@ If your PR makes notable changes to Winit's features, please update this section
### System Information
- **Monitor list**: Retrieve the list of monitors and their metadata, including which one is primary.
- **Video mode query**: Monitors can be queried for their supported fullscreen video modes (consisting of resolution, refresh rate, and bit depth).
### Input Handling
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
- **Mouse set location**: Forcibly changing the location of the pointer.
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
- **Cursor icon**: Changing the cursor icon or hiding the cursor.
- **Cursor image**: Changing the cursor to your own image.
- **Cursor hittest**: Handle or ignore mouse events for a window.
- **Cursor grab**: Locking the cursor so it cannot exit the client area of a window.
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
- **Touch events**: Single-touch events.
- **Touch pressure**: Touch events contain information about the amount of force being applied.
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
- **Gamepad/Joystick events**: Capturing input from gamepads and joysticks.
- **Device movement events**: Capturing input from the device gyroscope and accelerometer.
- **Gamepad/Joystick events**: Capturing input from gampads and joysticks.
- **Device movement events:**: Capturing input from the device gyroscope and accelerometer.
## Platform
### Windows
* Setting the name of the internal window class
* Setting the taskbar icon
* Setting the parent window
* Setting a menu bar
* `WS_EX_NOREDIRECTIONBITMAP` support
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
* Changing a system-drawn backdrop
* Setting the window border color
* Setting the title bar background color
* Setting the title color
* Setting the corner rounding preference
### macOS
* Window activation policy
@@ -139,8 +120,6 @@ If your PR makes notable changes to Winit's features, please update this section
* Hidden titlebar
* Hidden titlebar buttons
* Full-size content view
* Accepts first mouse
* Set a preferred theme and get current theme.
### Unix
* Window urgency
@@ -148,21 +127,6 @@ If your PR makes notable changes to Winit's features, please update this section
* X11 Override Redirect Flag
* GTK Theme Variant
* Base window size
* Setting the X11 parent window
### iOS
* `winit` has a minimum OS requirement of iOS 8
* Get the `UIScreen` object pointer
* Setting the `UIView` hidpi factor
* Valid orientations
* Home indicator visibility
* Status bar visibility and style
* Deferring system gestures
* Getting the device idiom
* Getting the preferred video mode
### Web
* Get if the systems preferred color scheme is "dark"
## Usability
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
@@ -172,71 +136,61 @@ If your PR makes notable changes to Winit's features, please update this section
Legend:
- ✔️: Works as intended
- ▢: Mostly works, but some bugs are known
- ▢: Mostly works but some bugs are known
- ❌: Missing feature or large bugs making it unusable
- **N/A**: Not applicable for this platform
- ❓: Unknown status
### Windowing
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Web |Redox OS|
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A** |
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ |
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window resizing |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Window resize increments |❌ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ |
|Window blur |❌ |❌ |❌ |✔️ |**N/A**|**N/A**|N/A |❌ |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|**N/A** |
|HiDPI support |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** |
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Emscripten|
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] | |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ | |
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A** |
|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**N/A** |
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**| |
|Window resize increments |❌ | | |❌ |❌ |❌ |❌ |
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**| |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**| |
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|Popup windows | | | | |❌ |❌ | |
### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ |
|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**| |
|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
### Input handling
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** |
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor image |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |▢[#720] |**N/A**|**N/A**|❓ |**N/A** |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |**N/A** |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|Resize with cursor |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Mouse set location |✔️ |✔️ |✔️ | |**N/A**|**N/A**|**N/A** |
|Cursor grab |✔️ |▢[#165] |▢[#242] |❌[#306] |**N/A**|**N/A**|✔️ |
|Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**|❌ |
|Touch events |✔️ | |✔️ |✔️ |✔️ |✔️ |✔️ |
|Multitouch | | |✔️ |✔️ |❓ |❌ |❌ |
|Keyboard events |✔️ |✔️ |✔️ |✔️ | |❌ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ | | |
|Gamepad/Joystick events |❌[#804] |❌ | | | | |❌ |
|Device movement events |❓ | | | | |❌ | |
### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |✔️ |
|Keyboard Input 2.0 ([#753]) |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|Event Loop 2.0 ([#459]) |✔️ |❌ |❌ |✔️ |❌ |❌ |❌ |
|Keyboard Input ([#812]) | |❌ |❌ | |❌ |❌ |❌ |
### Completed API Reworks
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
[#165]: https://github.com/rust-windowing/winit/issues/165
[#219]: https://github.com/rust-windowing/winit/issues/219
@@ -251,5 +205,5 @@ Changes in the API that have been agreed upon but aren't implemented across all
[#720]: https://github.com/rust-windowing/winit/issues/720
[#721]: https://github.com/rust-windowing/winit/issues/721
[#750]: https://github.com/rust-windowing/winit/issues/750
[#753]: https://github.com/rust-windowing/winit/issues/753
[#804]: https://github.com/rust-windowing/winit/issues/804
[#812]: https://github.com/rust-windowing/winit/issues/812

View File

@@ -1,25 +1,11 @@
# Hall of Champions
The winit maintainers would like to recognize the following former winit
contributors, without whom winit would not exist in its current form. We thank
them deeply for their time and efforts and wish them the best of luck in their
The Winit maintainers would like to recognize the following former Winit
contributors, without whom Winit would not exist in its current form. We thank
them deeply for their time and efforts, and wish them best of luck in their
future endeavors:
* [@tomaka]: For creating the winit project and guiding it through its early
* @tomaka: For creating the Winit project and guiding it through its early
years of existence.
* [@vberger]: For diligently creating the Wayland backend and being its
extremely helpful and benevolent maintainer for years.
* [@francesca64]: For taking over the responsibility of maintaining almost every
winit backend and standardizing HiDPI support across all of them.
* [@Osspial]: For heroically landing EventLoop 2.0 and valiantly ushering in a
vastly more sustainable era of winit.
* [@goddessfreya]: For selflessly taking over maintainership of glutin and her
stellar dedication to improving both winit and glutin.
* [@ArturKovacs]: For consistently maintaining the macOS backend and for his immense involvement in designing and implementing the new keyboard API.
[@tomaka]: https://github.com/tomaka
[@vberger]: https://github.com/vberger
[@francesca64]: https://github.com/francesca64
[@Osspial]: https://github.com/Osspial
[@goddessfreya]: https://github.com/goddessfreya
[@ArturKovacs]: https://github.com/ArturKovacs
* @francesca64: For taking over the responsibility of maintaining almost every
Winit backend, and standardizing HiDPI support across all of them

196
README.md
View File

@@ -1,188 +1,80 @@
# This branch has been archived
Please direct all work against `master`.
--------
# winit - Cross-platform window creation and management in Rust
[![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit)
[![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit)
[![Master Docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=master%20docs
)](https://rust-windowing.github.io/winit/winit/index.html)
[![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions)
[![Build Status](https://travis-ci.org/rust-windowing/winit.svg?branch=master)](https://travis-ci.org/rust-windowing/winit)
[![Build status](https://ci.appveyor.com/api/projects/status/hr89but4x1n3dphq/branch/master?svg=true)](https://ci.appveyor.com/project/Osspial/winit/branch/master)
```toml
[dependencies]
winit = "0.29.10"
winit = "0.19.2"
```
## [Documentation](https://docs.rs/winit)
For features _within_ the scope of winit, see [FEATURES.md](FEATURES.md).
For features _outside_ the scope of winit, see [Are we GUI Yet?](https://areweguiyet.com/) and [Are we game yet?](https://arewegameyet.rs/), depending on what kind of project you're looking to do.
For features _outside_ the scope of winit, see [Missing features provided by other crates](https://github.com/rust-windowing/winit/wiki/Missing-features-provided-by-other-crates) in the wiki.
## Contact Us
Join us in our [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) room. If you don't get an answer there, try [![Libera.Chat](https://img.shields.io/badge/libera.chat-%23winit-red.svg)](https://web.libera.chat/#winit).
Join us in any of these:
The maintainers have a meeting every friday at UTC 14. The meeting notes can be found [here](https://hackmd.io/@winit-meetings).
[![Freenode](https://img.shields.io/badge/freenode.net-%23glutin-red.svg)](http://webchat.freenode.net?channels=%23glutin&uio=MTY9dHJ1ZSYyPXRydWUmND10cnVlJjExPTE4NSYxMj10cnVlJjE1PXRydWU7a)
[![Matrix](https://img.shields.io/badge/Matrix-%23Glutin%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#Glutin:matrix.org)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tomaka/glutin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Usage
Winit is a window creation and management library. It can create windows and lets you handle
events (for example: the window being resized, a key being pressed, a mouse movement, etc.)
produced by the window.
produced by window.
Winit is designed to be a low-level brick in a hierarchy of libraries. Consequently, in order to
show something on the window you need to use the platform-specific getters provided by winit, or
another library.
```rust
extern crate winit;
fn main() {
let mut events_loop = winit::EventsLoop::new();
let window = winit::Window::new(&events_loop).unwrap();
events_loop.run_forever(|event| {
match event {
winit::Event::WindowEvent {
event: winit::WindowEvent::CloseRequested,
..
} => winit::ControlFlow::Break,
_ => winit::ControlFlow::Continue,
}
});
}
```
Winit is only officially supported on the latest stable version of the Rust compiler.
### Cargo Features
Winit provides the following features, which can be enabled in your `Cargo.toml` file:
* `icon_loading`: Enables loading window icons directly from files. Depends on the [`image` crate](https://crates.io/crates/image).
* `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
* `x11` (enabled by default): On Unix platform, compiles with the X11 backend
* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend
* `mint`: Enables mint (math interoperability standard types) conversions.
## MSRV Policy
This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to
the MSRV will be accompanied by a minor version bump.
As a **tentative** policy, the upper bound of the MSRV is given by the following
formula:
```
min(sid, stable - 3)
```
Where `sid` is the current version of `rustc` provided by [Debian Sid], and
`stable` is the latest stable version of Rust. This bound may be broken in case of a major ecosystem shift or a security vulnerability.
[Debian Sid]: https://packages.debian.org/sid/rustc
The exception is for the Android platform, where a higher Rust version
must be used for certain Android features. In this case, the MSRV will be
capped at the latest stable version of Rust minus three. This inconsistency is
not reflected in Cargo metadata, as it is not powerful enough to expose this
restriction.
All crates in the [`rust-windowing`] organizations have the
same MSRV policy.
[`rust-windowing`]: https://github.com/rust-windowing
### Platform-specific usage
#### Wayland
#### Emscripten and WebAssembly
Note that windows don't appear on Wayland until you draw/present to them.
Building a binary will yield a `.js` file. In order to use it in an HTML file, you need to:
#### Web
To run the web example: `cargo run-wasm --example web`
Winit supports compiling to the `wasm32-unknown-unknown` target with `web-sys`.
On the web platform, a Winit window is backed by a `<canvas>` element. You can
either [provide Winit with a `<canvas>` element][web with_canvas], or [let Winit
create a `<canvas>` element which you can then retrieve][web canvas getter] and
insert it into the DOM yourself.
For the example code using Winit on Web, check out the [web example]. For
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
book].
[web with_canvas]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowBuilderExtWebSys.html#tymethod.with_canvas
[web canvas getter]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowExtWebSys.html#tymethod.canvas
[web example]: ./examples/web.rs
[Rust and WebAssembly book]: https://rustwasm.github.io/book/
#### Android
The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/latest/ndk/) crate.
Native Android applications need some form of "glue" crate that is responsible
for defining the main entry point for your Rust application as well as tracking
various life-cycle events and synchronizing with the main JVM thread.
Winit uses the [android-activity](https://github.com/rib/android-activity) as a
glue crate (prior to `0.28` it used
[ndk-glue](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue)).
The version of the glue crate that your application depends on _must_ match the
version that Winit depends on because the glue crate is responsible for your
application's main entry point. If Cargo resolves multiple versions, they will
clash.
`winit` glue compatibility table:
| winit | ndk-glue |
| :---: | :--------------------------: |
| 0.29 | `android-activity = "0.5"` |
| 0.28 | `android-activity = "0.4"` |
| 0.27 | `ndk-glue = "0.7"` |
| 0.26 | `ndk-glue = "0.5"` |
| 0.25 | `ndk-glue = "0.3"` |
| 0.24 | `ndk-glue = "0.2"` |
The recommended way to avoid a conflict with the glue version is to avoid explicitly
depending on the `android-activity` crate, and instead consume the API that
is re-exported by Winit under `winit::platform::android::activity::*`
Running on an Android device needs a dynamic system library. Add this to Cargo.toml:
```toml
[lib]
name = "main"
crate-type = ["cdylib"]
```
All Android applications are based on an `Activity` subclass, and the
`android-activity` crate is designed to support different choices for this base
class. Your application _must_ specify the base class it needs via a feature flag:
| Base Class | Feature Flag | Notes |
| :--------------: | :---------------: | :-----: |
| `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.|
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`], a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
[`GameActivity`]: https://developer.android.com/games/agdk/game-activity
[`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
[`AndroidAppCompat`]: https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity
[agdk_jetpack]: https://developer.android.com/jetpack/androidx/releases/games
[agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
[Gradle]: https://developer.android.com/studio/build
For more details, refer to these `android-activity` [example applications](https://github.com/rust-mobile/android-activity/tree/main/examples).
##### Converting from `ndk-glue` to `android-activity`
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be:
1. Remove `ndk-glue` from your `Cargo.toml`
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.10", features = [ "android-native-activity" ] }`
3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
#### MacOS
A lot of functionality expects the application to be ready before you start
doing anything; this includes creating windows, fetching monitors, drawing,
and so on, see issues [#2238], [#2051] and [#2087].
If you encounter problems, you should try doing your initialization inside
`Event::Resumed`.
#### iOS
Similar to macOS, iOS's main `UIApplicationMain` does some init work that's required
by all UI-related code (see issue [#1705]). It would be best to consider creating your windows
inside `Event::Resumed`.
[#2238]: https://github.com/rust-windowing/winit/issues/2238
[#2051]: https://github.com/rust-windowing/winit/issues/2051
[#2087]: https://github.com/rust-windowing/winit/issues/2087
[#1705]: https://github.com/rust-windowing/winit/issues/1705
#### Redox OS
Redox OS has some functionality not yet present that will be implemented when
its orbital display server provides it.
- Put a `<canvas id="my_id"></canvas>` element somewhere. A canvas corresponds to a winit "window".
- Write a Javascript code that creates a global variable named `Module`. Set `Module.canvas` to
the element of the `<canvas>` element (in the example you would retrieve it via `document.getElementById("my_id")`).
More information [here](https://kripken.github.io/emscripten-site/docs/api_reference/module.html).
- Make sure that you insert the `.js` file generated by Rust after the `Module` variable is created.

24
appveyor.yml Normal file
View File

@@ -0,0 +1,24 @@
environment:
matrix:
- TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly
- TARGET: x86_64-pc-windows-msvc
CHANNEL: stable
- TARGET: i686-pc-windows-msvc
CHANNEL: nightly
- TARGET: i686-pc-windows-gnu
CHANNEL: nightly
install:
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init -yv --default-toolchain %CHANNEL% --default-host %TARGET%
- SET PATH=%PATH%;%USERPROFILE%\.cargo\bin
- SET PATH=%PATH%;C:\MinGW\bin
- rustc -V
- cargo -V
build: false
test_script:
- cargo test --verbose
- cargo test --features serde --verbose
- cargo test --features icon_loading --verbose

View File

@@ -1,15 +0,0 @@
disallowed-methods = [
{ path = "web_sys::window", reason = "is not available in every context" },
{ path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
{ path = "icrate::AppKit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
{ path = "icrate::AppKit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
]

View File

@@ -1,65 +0,0 @@
# https://embarkstudios.github.io/cargo-deny/
# cargo install cargo-deny
# cargo update && cargo deny --all-features --log-level error --target aarch64-apple-ios check
# Note: running just `cargo deny check` without a `--target` will result in
# false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
targets = [
{ triple = "aarch64-apple-ios" },
{ triple = "aarch64-linux-android" },
{ triple = "i686-pc-windows-gnu" },
{ triple = "i686-pc-windows-msvc" },
{ triple = "i686-unknown-linux-gnu" },
{ triple = "wasm32-unknown-unknown" },
{ triple = "x86_64-apple-darwin" },
{ triple = "x86_64-apple-ios" },
{ triple = "x86_64-pc-windows-gnu" },
{ triple = "x86_64-pc-windows-msvc" },
{ triple = "x86_64-unknown-linux-gnu" },
{ triple = "x86_64-unknown-redox" },
]
[advisories]
vulnerability = "deny"
unmaintained = "warn"
yanked = "deny"
ignore = []
[bans]
multiple-versions = "deny"
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
deny = []
skip = [
{ name = "raw-window-handle" }, # we intentionally have multiple versions of this
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
{ name = "libloading" }, # x11rb uses a different version until the next update
]
skip-tree = [
{ name = "run-wasm", version = "0.1.0" }
]
[licenses]
private = { ignore = true }
unlicensed = "deny"
allow-osi-fsf-free = "neither"
confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text
copyleft = "deny"
allow = [
"Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
"CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/
"ISC", # https://tldrlegal.com/license/-isc-license
"LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321
"MIT-0", # https://choosealicense.com/licenses/mit-0/
"MIT", # https://tldrlegal.com/license/mit-license
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux.
"OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html
"OpenSSL", # https://www.openssl.org/source/license.html - used on Linux
"Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
]

View File

@@ -1,11 +0,0 @@
# Image Attribution
These images are used in the documentation of `winit`.
## keyboard_*.svg
These files are a modified version of "[ANSI US QWERTY (Windows)](https://commons.wikimedia.org/wiki/File:ANSI_US_QWERTY_(Windows).svg)"
by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It was
originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en)
License. Minor modifications have been made by [John Nunley](https://github.com/notgull),
which have been released under the same license as a derivative work.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

32
examples/cursor.rs Normal file
View File

@@ -0,0 +1,32 @@
extern crate winit;
use winit::{Event, ElementState, MouseCursor, WindowEvent, KeyboardInput, ControlFlow};
fn main() {
let mut events_loop = winit::EventsLoop::new();
let window = winit::WindowBuilder::new().build(&events_loop).unwrap();
window.set_title("A fantastic window!");
let cursors = [MouseCursor::Default, MouseCursor::Crosshair, MouseCursor::Hand, MouseCursor::Arrow, MouseCursor::Move, MouseCursor::Text, MouseCursor::Wait, MouseCursor::Help, MouseCursor::Progress, MouseCursor::NotAllowed, MouseCursor::ContextMenu, MouseCursor::Cell, MouseCursor::VerticalText, MouseCursor::Alias, MouseCursor::Copy, MouseCursor::NoDrop, MouseCursor::Grab, MouseCursor::Grabbing, MouseCursor::AllScroll, MouseCursor::ZoomIn, MouseCursor::ZoomOut, MouseCursor::EResize, MouseCursor::NResize, MouseCursor::NeResize, MouseCursor::NwResize, MouseCursor::SResize, MouseCursor::SeResize, MouseCursor::SwResize, MouseCursor::WResize, MouseCursor::EwResize, MouseCursor::NsResize, MouseCursor::NeswResize, MouseCursor::NwseResize, MouseCursor::ColResize, MouseCursor::RowResize];
let mut cursor_idx = 0;
events_loop.run_forever(|event| {
match event {
Event::WindowEvent { event: WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, .. }, .. }, .. } => {
println!("Setting cursor to \"{:?}\"", cursors[cursor_idx]);
window.set_cursor(cursors[cursor_idx]);
if cursor_idx < cursors.len() - 1 {
cursor_idx += 1;
} else {
cursor_idx = 0;
}
},
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
return ControlFlow::Break;
},
_ => ()
}
ControlFlow::Continue
});
}

38
examples/cursor_grab.rs Normal file
View File

@@ -0,0 +1,38 @@
extern crate winit;
fn main() {
let mut events_loop = winit::EventsLoop::new();
let window = winit::WindowBuilder::new()
.with_title("Super Cursor Grab'n'Hide Simulator 9000")
.build(&events_loop)
.unwrap();
events_loop.run_forever(|event| {
if let winit::Event::WindowEvent { event, .. } = event {
use winit::WindowEvent::*;
match event {
CloseRequested => return winit::ControlFlow::Break,
KeyboardInput {
input: winit::KeyboardInput {
state: winit::ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
},
..
} => {
use winit::VirtualKeyCode::*;
match key {
Escape => return winit::ControlFlow::Break,
G => window.grab_cursor(!modifiers.shift).unwrap(),
H => window.hide_cursor(!modifiers.shift),
_ => (),
}
}
_ => (),
}
}
winit::ControlFlow::Continue
});
}

130
examples/fullscreen.rs Normal file
View File

@@ -0,0 +1,130 @@
extern crate winit;
use std::io::{self, Write};
use winit::{ControlFlow, Event, WindowEvent};
fn main() {
let mut events_loop = winit::EventsLoop::new();
#[cfg(target_os = "macos")]
let mut macos_use_simple_fullscreen = false;
let monitor = {
// On macOS there are two fullscreen modes "native" and "simple"
#[cfg(target_os = "macos")]
{
print!("Please choose the fullscreen mode: (1) native, (2) simple: ");
io::stdout().flush().unwrap();
let mut num = String::new();
io::stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
match num {
2 => macos_use_simple_fullscreen = true,
_ => {}
}
// Prompt for monitor when using native fullscreen
if !macos_use_simple_fullscreen {
Some(prompt_for_monitor(&events_loop))
} else {
None
}
}
#[cfg(not(target_os = "macos"))]
Some(prompt_for_monitor(&events_loop))
};
let mut is_fullscreen = monitor.is_some();
let mut is_maximized = false;
let mut decorations = true;
let window = winit::WindowBuilder::new()
.with_title("Hello world!")
.with_fullscreen(monitor)
.build(&events_loop)
.unwrap();
events_loop.run_forever(|event| {
println!("{:?}", event);
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => return ControlFlow::Break,
WindowEvent::KeyboardInput {
input:
winit::KeyboardInput {
virtual_keycode: Some(virtual_code),
state,
..
},
..
} => match (virtual_code, state) {
(winit::VirtualKeyCode::Escape, _) => return ControlFlow::Break,
(winit::VirtualKeyCode::F, winit::ElementState::Pressed) => {
#[cfg(target_os = "macos")]
{
if macos_use_simple_fullscreen {
use winit::os::macos::WindowExt;
if WindowExt::set_simple_fullscreen(&window, !is_fullscreen) {
is_fullscreen = !is_fullscreen;
}
return ControlFlow::Continue;
}
}
is_fullscreen = !is_fullscreen;
if !is_fullscreen {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(window.get_current_monitor()));
}
}
(winit::VirtualKeyCode::S, winit::ElementState::Pressed) => {
println!("window.get_fullscreen {:?}", window.get_fullscreen());
#[cfg(target_os = "macos")]
{
use winit::os::macos::WindowExt;
println!("window.get_simple_fullscreen {:?}", WindowExt::get_simple_fullscreen(&window));
}
}
(winit::VirtualKeyCode::M, winit::ElementState::Pressed) => {
is_maximized = !is_maximized;
window.set_maximized(is_maximized);
}
(winit::VirtualKeyCode::D, winit::ElementState::Pressed) => {
decorations = !decorations;
window.set_decorations(decorations);
}
_ => (),
},
_ => (),
},
_ => {}
}
ControlFlow::Continue
});
}
// Enumerate monitors and prompt user to choose one
fn prompt_for_monitor(events_loop: &winit::EventsLoop) -> winit::MonitorId {
for (num, monitor) in events_loop.get_available_monitors().enumerate() {
println!("Monitor #{}: {:?}", num, monitor.get_name());
}
print!("Please write the number of the monitor to use: ");
io::stdout().flush().unwrap();
let mut num = String::new();
io::stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let monitor = events_loop.get_available_monitors().nth(num).expect("Please enter a valid ID");
println!("Using {:?}", monitor.get_name());
monitor
}

View File

@@ -0,0 +1,74 @@
extern crate winit;
fn main() {
let mut events_loop = winit::EventsLoop::new();
let _window = winit::WindowBuilder::new()
.with_title("Your faithful window")
.build(&events_loop)
.unwrap();
let mut close_requested = false;
events_loop.run_forever(|event| {
use winit::WindowEvent::*;
use winit::ElementState::Released;
use winit::VirtualKeyCode::{N, Y};
match event {
winit::Event::WindowEvent { event, .. } => match event {
CloseRequested => {
// `CloseRequested` is sent when the close button on the window is pressed (or
// through whatever other mechanisms the window manager provides for closing a
// window). If you don't handle this event, the close button won't actually do
// anything.
// A common thing to do here is prompt the user if they have unsaved work.
// Creating a proper dialog box for that is far beyond the scope of this
// example, so here we'll just respond to the Y and N keys.
println!("Are you ready to bid your window farewell? [Y/N]");
close_requested = true;
// In applications where you can safely close the window without further
// action from the user, this is generally where you'd handle cleanup before
// closing the window. How to close the window is detailed in the handler for
// the Y key.
}
KeyboardInput {
input:
winit::KeyboardInput {
virtual_keycode: Some(virtual_code),
state: Released,
..
},
..
} => match virtual_code {
Y => {
if close_requested {
// This is where you'll want to do any cleanup you need.
println!("Buh-bye!");
// For a single-window application like this, you'd normally just
// break out of the event loop here. If you wanted to keep running the
// event loop (i.e. if it's a multi-window application), you need to
// drop the window. That closes it, and results in `Destroyed` being
// sent.
return winit::ControlFlow::Break;
}
}
N => {
if close_requested {
println!("Your window will continue to stay by your side.");
close_requested = false;
}
}
_ => (),
},
_ => (),
},
_ => (),
}
winit::ControlFlow::Continue
});
}

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

23
examples/min_max_size.rs Normal file
View File

@@ -0,0 +1,23 @@
extern crate winit;
use winit::dpi::LogicalSize;
fn main() {
let mut events_loop = winit::EventsLoop::new();
let window = winit::WindowBuilder::new()
.build(&events_loop)
.unwrap();
window.set_min_dimensions(Some(LogicalSize::new(400.0, 200.0)));
window.set_max_dimensions(Some(LogicalSize::new(800.0, 400.0)));
events_loop.run_forever(|event| {
println!("{:?}", event);
match event {
winit::Event::WindowEvent { event: winit::WindowEvent::CloseRequested, .. } => winit::ControlFlow::Break,
_ => winit::ControlFlow::Continue,
}
});
}

7
examples/monitor_list.rs Normal file
View File

@@ -0,0 +1,7 @@
extern crate winit;
fn main() {
let event_loop = winit::EventsLoop::new();
let window = winit::WindowBuilder::new().build(&event_loop).unwrap();
println!("{:#?}\nPrimary: {:#?}", window.get_available_monitors(), window.get_primary_monitor());
}

33
examples/multiwindow.rs Normal file
View File

@@ -0,0 +1,33 @@
extern crate winit;
use std::collections::HashMap;
fn main() {
let mut events_loop = winit::EventsLoop::new();
let mut windows = HashMap::new();
for _ in 0..3 {
let window = winit::Window::new(&events_loop).unwrap();
windows.insert(window.id(), window);
}
events_loop.run_forever(|event| {
match event {
winit::Event::WindowEvent {
event: winit::WindowEvent::CloseRequested,
window_id,
} => {
println!("Window {:?} has received the signal to close", window_id);
// This drops the window, causing it to close.
windows.remove(&window_id);
if windows.is_empty() {
return winit::ControlFlow::Break;
}
}
_ => (),
}
winit::ControlFlow::Continue
})
}

29
examples/proxy.rs Normal file
View File

@@ -0,0 +1,29 @@
extern crate winit;
fn main() {
let mut events_loop = winit::EventsLoop::new();
let _window = winit::WindowBuilder::new()
.with_title("A fantastic window!")
.build(&events_loop)
.unwrap();
let proxy = events_loop.create_proxy();
std::thread::spawn(move || {
// Wake up the `events_loop` once every second.
loop {
std::thread::sleep(std::time::Duration::from_secs(1));
proxy.wakeup().unwrap();
}
});
events_loop.run_forever(|event| {
println!("{:?}", event);
match event {
winit::Event::WindowEvent { event: winit::WindowEvent::CloseRequested, .. } =>
winit::ControlFlow::Break,
_ => winit::ControlFlow::Continue,
}
});
}

38
examples/resizable.rs Normal file
View File

@@ -0,0 +1,38 @@
extern crate winit;
fn main() {
let mut events_loop = winit::EventsLoop::new();
let mut resizable = false;
let window = winit::WindowBuilder::new()
.with_title("Hit space to toggle resizability.")
.with_dimensions((400, 200).into())
.with_resizable(resizable)
.build(&events_loop)
.unwrap();
events_loop.run_forever(|event| {
match event {
winit::Event::WindowEvent { event, .. } => match event {
winit::WindowEvent::CloseRequested => return winit::ControlFlow::Break,
winit::WindowEvent::KeyboardInput {
input:
winit::KeyboardInput {
virtual_keycode: Some(winit::VirtualKeyCode::Space),
state: winit::ElementState::Released,
..
},
..
} => {
resizable = !resizable;
println!("Resizable: {}", resizable);
window.set_resizable(resizable);
}
_ => (),
},
_ => (),
};
winit::ControlFlow::Continue
});
}

20
examples/transparent.rs Normal file
View File

@@ -0,0 +1,20 @@
extern crate winit;
fn main() {
let mut events_loop = winit::EventsLoop::new();
let window = winit::WindowBuilder::new().with_decorations(false)
.with_transparency(true)
.build(&events_loop).unwrap();
window.set_title("A fantastic window!");
events_loop.run_forever(|event| {
println!("{:?}", event);
match event {
winit::Event::WindowEvent { event: winit::WindowEvent::CloseRequested, .. } => winit::ControlFlow::Break,
_ => winit::ControlFlow::Continue,
}
});
}

22
examples/window.rs Normal file
View File

@@ -0,0 +1,22 @@
extern crate winit;
fn main() {
let mut events_loop = winit::EventsLoop::new();
let _window = winit::WindowBuilder::new()
.with_title("A fantastic window!")
.build(&events_loop)
.unwrap();
events_loop.run_forever(|event| {
println!("{:?}", event);
match event {
winit::Event::WindowEvent {
event: winit::WindowEvent::CloseRequested,
..
} => winit::ControlFlow::Break,
_ => winit::ControlFlow::Continue,
}
});
}

94
examples/window_icon.rs Normal file
View File

@@ -0,0 +1,94 @@
// Heads up: you need to compile this example with `--features icon_loading`.
// `Icon::from_path` won't be available otherwise, though for your own applications, you could use
// `Icon::from_rgba` if you don't want to depend on the `image` crate.
extern crate winit;
#[cfg(feature = "icon_loading")]
extern crate image;
#[cfg(feature = "icon_loading")]
fn main() {
use winit::Icon;
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
// by WM, and on Windows, you still have to account for screen scaling. Here we use 32px,
// since it seems to work well enough in most cases. Be careful about going too high, or
// you'll be bitten by the low-quality downscaling built into the WM.
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");
// While `Icon::from_path` is the most straightforward, you have a few other options. If you
// want to use the `include_bytes` macro, then pass the result to `Icon::from_bytes`. See the
// docs for the full list of options (you'll have to generate the docs with the `icon_loading`
// feature enabled).
let icon = Icon::from_path(path).expect("Failed to open icon");
let mut events_loop = winit::EventsLoop::new();
let window = winit::WindowBuilder::new()
.with_title("An iconic window!")
// At present, this only does anything on Windows and X11, so if you want to save load
// time, you can put icon loading behind a function that returns `None` on other platforms.
.with_window_icon(Some(icon))
.build(&events_loop)
.unwrap();
events_loop.run_forever(|event| {
if let winit::Event::WindowEvent { event, .. } = event {
use winit::WindowEvent::*;
match event {
CloseRequested => return winit::ControlFlow::Break,
DroppedFile(path) => {
use image::GenericImageView;
let icon_image = image::open(path).expect("Failed to open window icon");
let (width, height) = icon_image.dimensions();
const DESIRED_SIZE: u32 = 32;
let (new_width, new_height) = if width == height {
(DESIRED_SIZE, DESIRED_SIZE)
} else {
// Note that this will never divide by zero, due to the previous condition.
let aspect_adjustment = DESIRED_SIZE as f64
/ std::cmp::max(width, height) as f64;
(
(width as f64 * aspect_adjustment) as u32,
(height as f64 * aspect_adjustment) as u32,
)
};
// By scaling the icon ourselves, we get higher-quality filtering and save
// some memory.
let icon = image::imageops::resize(
&icon_image,
new_width,
new_height,
image::FilterType::Lanczos3,
);
let (offset_x, offset_y) = (
(DESIRED_SIZE - new_width) / 2,
(DESIRED_SIZE - new_height) / 2,
);
let mut canvas = image::ImageBuffer::new(DESIRED_SIZE, DESIRED_SIZE);
image::imageops::replace(
&mut canvas,
&icon,
offset_x,
offset_y,
);
window.set_window_icon(Some(canvas.into()));
},
_ => (),
}
}
winit::ControlFlow::Continue
});
}
#[cfg(not(feature = "icon_loading"))]
fn main() {
print!(
r#"This example requires the `icon_loading` feature:
cargo run --example window_icon --features icon_loading
"#);
}

View File

@@ -1,8 +0,0 @@
[package]
name = "run-wasm"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
cargo-run-wasm = "0.2.0"

View File

@@ -1,3 +0,0 @@
fn main() {
cargo_run_wasm::run_wasm_with_css("body { margin: 0px; }");
}

View File

@@ -1,3 +0,0 @@
force_explicit_abi=true
use_field_init_shorthand=true
# merge_imports=true

331
src/dpi.rs Normal file
View File

@@ -0,0 +1,331 @@
//! DPI is important, so read the docs for this module if you don't want to be confused.
//!
//! Originally, `winit` dealt entirely in physical pixels (excluding unintentional inconsistencies), but now all
//! window-related functions both produce and consume logical pixels. Monitor-related functions still use physical
//! pixels, as do any context-related functions in `glutin`.
//!
//! If you've never heard of these terms before, then you're not alone, and this documentation will explain the
//! concepts.
//!
//! Modern screens have a defined physical resolution, most commonly 1920x1080. Indepedent of that is the amount of
//! space the screen occupies, which is to say, the height and width in millimeters. The relationship between these two
//! measurements is the *pixel density*. Mobile screens require a high pixel density, as they're held close to the
//! eyes. Larger displays also require a higher pixel density, hence the growing presence of 1440p and 4K displays.
//!
//! So, this presents a problem. Let's say we want to render a square 100px button. It will occupy 100x100 of the
//! screen's pixels, which in many cases, seems perfectly fine. However, because this size doesn't account for the
//! screen's dimensions or pixel density, the button's size can vary quite a bit. On a 4K display, it would be unusably
//! small.
//!
//! That's a description of what happens when the button is 100x100 *physical* pixels. Instead, let's try using 100x100
//! *logical* pixels. To map logical pixels to physical pixels, we simply multiply by the DPI (dots per inch) factor.
//! On a "typical" desktop display, the DPI factor will be 1.0, so 100x100 logical pixels equates to 100x100 physical
//! pixels. However, a 1440p display may have a DPI factor of 1.25, so the button is rendered as 125x125 physical pixels.
//! Ideally, the button now has approximately the same perceived size across varying displays.
//!
//! Failure to account for the DPI factor can create a badly degraded user experience. Most notably, it can make users
//! feel like they have bad eyesight, which will potentially cause them to think about growing elderly, resulting in
//! them entering an existential panic. Once users enter that state, they will no longer be focused on your application.
//!
//! There are two ways to get the DPI factor:
//! - You can track the [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) event of your
//! windows. This event is sent any time the DPI factor changes, either because the window moved to another monitor,
//! or because the user changed the configuration of their screen.
//! - You can also retrieve the DPI factor of a monitor by calling
//! [`MonitorId::get_hidpi_factor`](../struct.MonitorId.html#method.get_hidpi_factor), or the
//! current DPI factor applied to a window by calling
//! [`Window::get_hidpi_factor`](../struct.Window.html#method.get_hidpi_factor), which is roughly equivalent
//! to `window.get_current_monitor().get_hidpi_factor()`.
//!
//! Depending on the platform, the window's actual DPI factor may only be known after
//! the event loop has started and your window has been drawn once. To properly handle these cases,
//! the most robust way is to monitor the [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged)
//! event and dynamically adapt your drawing logic to follow the DPI factor.
//!
//! Here's an overview of what sort of DPI factors you can expect, and where they come from:
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the display settings.
//! While users are free to select any option they want, they're only given a selection of "nice" DPI factors, i.e.
//! 1.0, 1.25, 1.5... on Windows 7, the DPI factor is global and changing it requires logging out.
//! - **macOS:** The buzzword is "retina displays", which have a DPI factor of 2.0. Otherwise, the DPI factor is 1.0.
//! Intermediate DPI factors are never used, thus 1440p displays/etc. aren't properly supported. It's possible for any
//! display to use that 2.0 DPI factor, given the use of the command line.
//! - **X11:** On X11, we calcuate the DPI factor based on the millimeter dimensions provided by XRandR. This can
//! result in a wide range of possible values, including some interesting ones like 1.0833333333333333. This can be
//! overridden using the `WINIT_HIDPI_FACTOR` environment variable, though that's not recommended.
//! - **Wayland:** On Wayland, DPI factors are set per-screen by the server, and are always integers (most often 1 or 2).
//! - **iOS:** DPI factors are both constant and device-specific on iOS.
//! - **Android:** This feature isn't yet implemented on Android, so the DPI factor will always be returned as 1.0.
//!
//! The window's logical size is conserved across DPI changes, resulting in the physical size changing instead. This
//! may be surprising on X11, but is quite standard elsewhere. Physical size changes always produce a
//! [`Resized`](../enum.WindowEvent.html#variant.Resized) event, even on platforms where no resize actually occurs,
//! such as macOS and Wayland. As a result, it's not necessary to separately handle
//! [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) if you're only listening for size.
//!
//! Your GPU has no awareness of the concept of logical pixels, and unless you like wasting pixel density, your
//! framebuffer's size should be in physical pixels.
//!
//! `winit` will send [`Resized`](../enum.WindowEvent.html#variant.Resized) events whenever a window's logical size
//! changes, and [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) events
//! whenever the DPI factor changes. Receiving either of these events means that the physical size of your window has
//! changed, and you should recompute it using the latest values you received for each. If the logical size and the
//! DPI factor change simultaneously, `winit` will send both events together; thus, it's recommended to buffer
//! these events and process them at the end of the queue.
//!
//! If you never received any [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) events,
//! then your window's DPI factor is 1.
/// Checks that the DPI factor is a normal positive `f64`.
///
/// All functions that take a DPI factor assert that this will return `true`. If you're sourcing DPI factors from
/// anywhere other than winit, it's recommended to validate them using this function before passing them to winit;
/// otherwise, you risk panics.
#[inline]
pub fn validate_hidpi_factor(dpi_factor: f64) -> bool {
dpi_factor.is_sign_positive() && dpi_factor.is_normal()
}
/// A position represented in logical pixels.
///
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
/// which can cause noticable issues. To help with that, an `Into<(i32, i32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalPosition {
pub x: f64,
pub y: f64,
}
impl LogicalPosition {
#[inline]
pub fn new(x: f64, y: f64) -> Self {
LogicalPosition { x, y }
}
#[inline]
pub fn from_physical<T: Into<PhysicalPosition>>(physical: T, dpi_factor: f64) -> Self {
physical.into().to_logical(dpi_factor)
}
#[inline]
pub fn to_physical(&self, dpi_factor: f64) -> PhysicalPosition {
assert!(validate_hidpi_factor(dpi_factor));
let x = self.x * dpi_factor;
let y = self.y * dpi_factor;
PhysicalPosition::new(x, y)
}
}
impl From<(f64, f64)> for LogicalPosition {
#[inline]
fn from((x, y): (f64, f64)) -> Self {
Self::new(x, y)
}
}
impl From<(i32, i32)> for LogicalPosition {
#[inline]
fn from((x, y): (i32, i32)) -> Self {
Self::new(x as f64, y as f64)
}
}
impl Into<(f64, f64)> for LogicalPosition {
#[inline]
fn into(self) -> (f64, f64) {
(self.x, self.y)
}
}
impl Into<(i32, i32)> for LogicalPosition {
/// Note that this rounds instead of truncating.
#[inline]
fn into(self) -> (i32, i32) {
(self.x.round() as _, self.y.round() as _)
}
}
/// A position represented in physical pixels.
///
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
/// which can cause noticable issues. To help with that, an `Into<(i32, i32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalPosition {
pub x: f64,
pub y: f64,
}
impl PhysicalPosition {
#[inline]
pub fn new(x: f64, y: f64) -> Self {
PhysicalPosition { x, y }
}
#[inline]
pub fn from_logical<T: Into<LogicalPosition>>(logical: T, dpi_factor: f64) -> Self {
logical.into().to_physical(dpi_factor)
}
#[inline]
pub fn to_logical(&self, dpi_factor: f64) -> LogicalPosition {
assert!(validate_hidpi_factor(dpi_factor));
let x = self.x / dpi_factor;
let y = self.y / dpi_factor;
LogicalPosition::new(x, y)
}
}
impl From<(f64, f64)> for PhysicalPosition {
#[inline]
fn from((x, y): (f64, f64)) -> Self {
Self::new(x, y)
}
}
impl From<(i32, i32)> for PhysicalPosition {
#[inline]
fn from((x, y): (i32, i32)) -> Self {
Self::new(x as f64, y as f64)
}
}
impl Into<(f64, f64)> for PhysicalPosition {
#[inline]
fn into(self) -> (f64, f64) {
(self.x, self.y)
}
}
impl Into<(i32, i32)> for PhysicalPosition {
/// Note that this rounds instead of truncating.
#[inline]
fn into(self) -> (i32, i32) {
(self.x.round() as _, self.y.round() as _)
}
}
/// A size represented in logical pixels.
///
/// The size is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
/// which can cause noticable issues. To help with that, an `Into<(u32, u32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalSize {
pub width: f64,
pub height: f64,
}
impl LogicalSize {
#[inline]
pub fn new(width: f64, height: f64) -> Self {
LogicalSize { width, height }
}
#[inline]
pub fn from_physical<T: Into<PhysicalSize>>(physical: T, dpi_factor: f64) -> Self {
physical.into().to_logical(dpi_factor)
}
#[inline]
pub fn to_physical(&self, dpi_factor: f64) -> PhysicalSize {
assert!(validate_hidpi_factor(dpi_factor));
let width = self.width * dpi_factor;
let height = self.height * dpi_factor;
PhysicalSize::new(width, height)
}
}
impl From<(f64, f64)> for LogicalSize {
#[inline]
fn from((width, height): (f64, f64)) -> Self {
Self::new(width, height)
}
}
impl From<(u32, u32)> for LogicalSize {
#[inline]
fn from((width, height): (u32, u32)) -> Self {
Self::new(width as f64, height as f64)
}
}
impl Into<(f64, f64)> for LogicalSize {
#[inline]
fn into(self) -> (f64, f64) {
(self.width, self.height)
}
}
impl Into<(u32, u32)> for LogicalSize {
/// Note that this rounds instead of truncating.
#[inline]
fn into(self) -> (u32, u32) {
(self.width.round() as _, self.height.round() as _)
}
}
/// A size represented in physical pixels.
///
/// The size is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
/// which can cause noticable issues. To help with that, an `Into<(u32, u32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalSize {
pub width: f64,
pub height: f64,
}
impl PhysicalSize {
#[inline]
pub fn new(width: f64, height: f64) -> Self {
PhysicalSize { width, height }
}
#[inline]
pub fn from_logical<T: Into<LogicalSize>>(logical: T, dpi_factor: f64) -> Self {
logical.into().to_physical(dpi_factor)
}
#[inline]
pub fn to_logical(&self, dpi_factor: f64) -> LogicalSize {
assert!(validate_hidpi_factor(dpi_factor));
let width = self.width / dpi_factor;
let height = self.height / dpi_factor;
LogicalSize::new(width, height)
}
}
impl From<(f64, f64)> for PhysicalSize {
#[inline]
fn from((width, height): (f64, f64)) -> Self {
Self::new(width, height)
}
}
impl From<(u32, u32)> for PhysicalSize {
#[inline]
fn from((width, height): (u32, u32)) -> Self {
Self::new(width as f64, height as f64)
}
}
impl Into<(f64, f64)> for PhysicalSize {
#[inline]
fn into(self) -> (f64, f64) {
(self.width, self.height)
}
}
impl Into<(u32, u32)> for PhysicalSize {
/// Note that this rounds instead of truncating.
#[inline]
fn into(self) -> (u32, u32) {
(self.width.round() as _, self.height.round() as _)
}
}

480
src/events.rs Normal file
View File

@@ -0,0 +1,480 @@
use std::path::PathBuf;
use {DeviceId, LogicalPosition, LogicalSize, WindowId};
/// Describes a generic event.
#[derive(Clone, Debug, PartialEq)]
pub enum Event {
WindowEvent {
window_id: WindowId,
event: WindowEvent,
},
DeviceEvent {
device_id: DeviceId,
event: DeviceEvent,
},
Awakened,
/// The application has been suspended or resumed.
///
/// The parameter is true if app was suspended, and false if it has been resumed.
Suspended(bool),
}
/// Describes an event from a `Window`.
#[derive(Clone, Debug, PartialEq)]
pub enum WindowEvent {
/// The size of the window has changed. Contains the client area's new dimensions.
Resized(LogicalSize),
/// The position of the window has changed. Contains the window's new position.
Moved(LogicalPosition),
/// The window has been requested to close.
CloseRequested,
/// The window has been destroyed.
Destroyed,
/// A file has been dropped into the window.
///
/// When the user drops multiple files at once, this event will be emitted for each file
/// separately.
DroppedFile(PathBuf),
/// A file is being hovered over the window.
///
/// When the user hovers multiple files at once, this event will be emitted for each file
/// separately.
HoveredFile(PathBuf),
/// A file was hovered, but has exited the window.
///
/// There will be a single `HoveredFileCancelled` event triggered even if multiple files were
/// hovered.
HoveredFileCancelled,
/// The window received a unicode character.
ReceivedCharacter(char),
/// The window gained or lost focus.
///
/// The parameter is true if the window has gained focus, and false if it has lost focus.
Focused(bool),
/// An event from the keyboard has been received.
KeyboardInput { device_id: DeviceId, input: KeyboardInput },
/// The cursor has moved on the window.
CursorMoved {
device_id: DeviceId,
/// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is
/// limited by the display area and it may have been transformed by the OS to implement effects such as cursor
/// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control.
position: LogicalPosition,
modifiers: ModifiersState
},
/// The cursor has entered the window.
CursorEntered { device_id: DeviceId },
/// The cursor has left the window.
CursorLeft { device_id: DeviceId },
/// A mouse wheel movement or touchpad scroll occurred.
MouseWheel { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase, modifiers: ModifiersState },
/// An mouse button press has been received.
MouseInput { device_id: DeviceId, state: ElementState, button: MouseButton, modifiers: ModifiersState },
/// Touchpad pressure event.
///
/// At the moment, only supported on Apple forcetouch-capable macbooks.
/// The parameters are: pressure level (value between 0 and 1 representing how hard the touchpad
/// is being pressed) and stage (integer representing the click level).
TouchpadPressure { device_id: DeviceId, pressure: f32, stage: i64 },
/// Motion on some analog axis. May report data redundant to other, more specific events.
AxisMotion { device_id: DeviceId, axis: AxisId, value: f64 },
/// The window needs to be redrawn.
Refresh,
/// Touch event has been received
Touch(Touch),
/// The DPI factor of the window has changed.
///
/// The following user actions can cause DPI changes:
///
/// * Changing the display's resolution.
/// * Changing the display's DPI factor (e.g. in Control Panel on Windows).
/// * Moving the window to a display with a different DPI factor.
///
/// For more information about DPI in general, see the [`dpi`](dpi/index.html) module.
HiDpiFactorChanged(f64),
}
/// Represents raw hardware events that are not associated with any particular window.
///
/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person
/// game controls. Many physical actions, such as mouse movement, can produce both device and window events. Because
/// window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs
/// may not match.
///
/// Note that these events are delivered regardless of input focus.
#[derive(Clone, Debug, PartialEq)]
pub enum DeviceEvent {
Added,
Removed,
/// Change in physical position of a pointing device.
///
/// This represents raw, unfiltered physical motion. Not to be confused with `WindowEvent::CursorMoved`.
MouseMotion {
/// (x, y) change in position in unspecified units.
///
/// Different devices may use different units.
delta: (f64, f64),
},
/// Physical scroll event
MouseWheel {
delta: MouseScrollDelta,
},
/// Motion on some analog axis. This event will be reported for all arbitrary input devices
/// that winit supports on this platform, including mouse devices. If the device is a mouse
/// device then this will be reported alongside the MouseMotion event.
Motion { axis: AxisId, value: f64 },
Button { button: ButtonId, state: ElementState },
Key(KeyboardInput),
Text { codepoint: char },
}
/// Describes a keyboard input event.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KeyboardInput {
/// Identifies the physical key pressed
///
/// This should not change if the user adjusts the host's keyboard map. Use when the physical location of the
/// key is more important than the key's host GUI semantics, such as for movement controls in a first-person
/// game.
pub scancode: ScanCode,
pub state: ElementState,
/// Identifies the semantic meaning of the key
///
/// Use when the semantics of the key are more important than the physical location of the key, such as when
/// implementing appropriate behavior for "page up."
pub virtual_keycode: Option<VirtualKeyCode>,
/// Modifier keys active at the time of this input.
///
/// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from
/// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere.
pub modifiers: ModifiersState
}
/// Describes touch-screen input state.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TouchPhase {
Started,
Moved,
Ended,
Cancelled
}
/// Represents touch event
///
/// Every time user touches screen new Start event with some finger id is generated.
/// When the finger is removed from the screen End event with same id is generated.
///
/// For every id there will be at least 2 events with phases Start and End (or Cancelled).
/// There may be 0 or more Move events.
///
///
/// Depending on platform implementation id may or may not be reused by system after End event.
///
/// Gesture regonizer using this event should assume that Start event received with same id
/// as previously received End event is a new finger and has nothing to do with an old one.
///
/// Touch may be cancelled if for example window lost focus.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Touch {
pub device_id: DeviceId,
pub phase: TouchPhase,
pub location: LogicalPosition,
/// unique identifier of a finger.
pub id: u64
}
/// Hardware-dependent keyboard scan code.
pub type ScanCode = u32;
/// Identifier for a specific analog axis on some device.
pub type AxisId = u32;
/// Identifier for a specific button on some device.
pub type ButtonId = u32;
/// Describes the input state of a key.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ElementState {
Pressed,
Released,
}
/// Describes a button of a mouse controller.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseButton {
Left,
Right,
Middle,
Other(u8),
}
/// Describes a difference in the mouse scroll wheel state.
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseScrollDelta {
/// Amount in lines or rows to scroll in the horizontal
/// and vertical directions.
///
/// Positive values indicate movement forward
/// (away from the user) or rightwards.
LineDelta(f32, f32),
/// Amount in pixels to scroll in the horizontal and
/// vertical direction.
///
/// Scroll events are expressed as a PixelDelta if
/// supported by the device (eg. a touchpad) and
/// platform.
PixelDelta(LogicalPosition),
}
/// Symbolic name for a keyboard key.
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum VirtualKeyCode {
/// The '1' key over the letters.
Key1,
/// The '2' key over the letters.
Key2,
/// The '3' key over the letters.
Key3,
/// The '4' key over the letters.
Key4,
/// The '5' key over the letters.
Key5,
/// The '6' key over the letters.
Key6,
/// The '7' key over the letters.
Key7,
/// The '8' key over the letters.
Key8,
/// The '9' key over the letters.
Key9,
/// The '0' key over the 'O' and 'P' keys.
Key0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
/// The Escape key, next to F1.
Escape,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
/// Print Screen/SysRq.
Snapshot,
/// Scroll Lock.
Scroll,
/// Pause/Break key, next to Scroll lock.
Pause,
/// `Insert`, next to Backspace.
Insert,
Home,
Delete,
End,
PageDown,
PageUp,
Left,
Up,
Right,
Down,
/// The Backspace key, right over Enter.
// TODO: rename
Back,
/// The Enter key.
Return,
/// The space bar.
Space,
/// The "Compose" key on Linux.
Compose,
Caret,
Numlock,
Numpad0,
Numpad1,
Numpad2,
Numpad3,
Numpad4,
Numpad5,
Numpad6,
Numpad7,
Numpad8,
Numpad9,
AbntC1,
AbntC2,
Add,
Apostrophe,
Apps,
At,
Ax,
Backslash,
Calculator,
Capital,
Colon,
Comma,
Convert,
Decimal,
Divide,
Equals,
Grave,
Kana,
Kanji,
LAlt,
LBracket,
LControl,
LShift,
LWin,
Mail,
MediaSelect,
MediaStop,
Minus,
Multiply,
Mute,
MyComputer,
NavigateForward, // also called "Prior"
NavigateBackward, // also called "Next"
NextTrack,
NoConvert,
NumpadComma,
NumpadEnter,
NumpadEquals,
OEM102,
Period,
PlayPause,
Power,
PrevTrack,
RAlt,
RBracket,
RControl,
RShift,
RWin,
Semicolon,
Slash,
Sleep,
Stop,
Subtract,
Sysrq,
Tab,
Underline,
Unlabeled,
VolumeDown,
VolumeUp,
Wake,
WebBack,
WebFavorites,
WebForward,
WebHome,
WebRefresh,
WebSearch,
WebStop,
Yen,
Copy,
Paste,
Cut,
}
/// Represents the current state of the keyboard modifiers
///
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
#[derive(Default, Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct ModifiersState {
/// The "shift" key
pub shift: bool,
/// The "control" key
pub ctrl: bool,
/// The "alt" key
pub alt: bool,
/// The "logo" key
///
/// This is the "windows" key on PC and "command" key on Mac.
pub logo: bool
}

170
src/icon.rs Normal file
View File

@@ -0,0 +1,170 @@
use std::{fmt, mem};
use std::error::Error;
#[cfg(feature = "icon_loading")]
use std::io::{BufRead, Seek};
#[cfg(feature = "icon_loading")]
use std::path::Path;
#[cfg(feature = "icon_loading")]
use image;
#[repr(C)]
#[derive(Debug)]
pub(crate) struct Pixel {
pub(crate) r: u8,
pub(crate) g: u8,
pub(crate) b: u8,
pub(crate) a: u8,
}
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// An error produced when using `Icon::from_rgba` with invalid arguments.
pub enum BadIcon {
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
/// safely interpreted as 32bpp RGBA pixels.
ByteCountNotDivisibleBy4 {
byte_count: usize,
},
/// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
/// At least one of your arguments is incorrect.
DimensionsVsPixelCount {
width: u32,
height: u32,
width_x_height: usize,
pixel_count: usize,
},
}
impl fmt::Display for BadIcon {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
let msg = match self {
&BadIcon::ByteCountNotDivisibleBy4 { byte_count } => format!(
"The length of the `rgba` argument ({:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
byte_count,
),
&BadIcon::DimensionsVsPixelCount {
width,
height,
width_x_height,
pixel_count,
} => format!(
"The specified dimensions ({:?}x{:?}) don't match the number of pixels supplied by the `rgba` argument ({:?}). For those dimensions, the expected pixel count is {:?}.",
width, height, pixel_count, width_x_height,
),
};
write!(formatter, "{}", msg)
}
}
impl Error for BadIcon {
fn description(&self) -> &str {
"A valid icon cannot be created from these arguments"
}
fn cause(&self) -> Option<&Error> {
Some(self)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
/// An icon used for the window titlebar, taskbar, etc.
///
/// Enabling the `icon_loading` feature provides you with several convenience methods for creating
/// an `Icon` from any format supported by the [image](https://github.com/PistonDevelopers/image)
/// crate.
pub struct Icon {
pub(crate) rgba: Vec<u8>,
pub(crate) width: u32,
pub(crate) height: u32,
}
impl Icon {
/// Creates an `Icon` from 32bpp RGBA data.
///
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
if rgba.len() % PIXEL_SIZE != 0 {
return Err(BadIcon::ByteCountNotDivisibleBy4 { byte_count: rgba.len() });
}
let pixel_count = rgba.len() / PIXEL_SIZE;
if pixel_count != (width * height) as usize {
Err(BadIcon::DimensionsVsPixelCount {
width,
height,
width_x_height: (width * height) as usize,
pixel_count,
})
} else {
Ok(Icon { rgba, width, height })
}
}
#[cfg(feature = "icon_loading")]
/// Loads an `Icon` from the path of an image on the filesystem.
///
/// Requires the `icon_loading` feature.
pub fn from_path<P: AsRef<Path>>(path: P) -> image::ImageResult<Self> {
image::open(path).map(Into::into)
}
#[cfg(feature = "icon_loading")]
/// Loads an `Icon` from anything implementing `BufRead` and `Seek`.
///
/// Requires the `icon_loading` feature.
pub fn from_reader<R: BufRead + Seek>(
reader: R,
format: image::ImageFormat,
) -> image::ImageResult<Self> {
image::load(reader, format).map(Into::into)
}
#[cfg(feature = "icon_loading")]
/// Loads an `Icon` from the unprocessed bytes of an image file.
/// Uses heuristics to determine format.
///
/// Requires the `icon_loading` feature.
pub fn from_bytes(bytes: &[u8]) -> image::ImageResult<Self> {
image::load_from_memory(bytes).map(Into::into)
}
#[cfg(feature = "icon_loading")]
/// Loads an `Icon` from the unprocessed bytes of an image.
///
/// Requires the `icon_loading` feature.
pub fn from_bytes_with_format(
bytes: &[u8],
format: image::ImageFormat,
) -> image::ImageResult<Self> {
image::load_from_memory_with_format(bytes, format).map(Into::into)
}
}
#[cfg(feature = "icon_loading")]
/// Requires the `icon_loading` feature.
impl From<image::DynamicImage> for Icon {
fn from(image: image::DynamicImage) -> Self {
use image::{GenericImageView, Pixel};
let (width, height) = image.dimensions();
let mut rgba = Vec::with_capacity((width * height) as usize * PIXEL_SIZE);
for (_, _, pixel) in image.pixels() {
rgba.extend_from_slice(&pixel.to_rgba().data);
}
Icon { rgba, width, height }
}
}
#[cfg(feature = "icon_loading")]
/// Requires the `icon_loading` feature.
impl From<image::RgbaImage> for Icon {
fn from(buf: image::RgbaImage) -> Self {
let (width, height) = buf.dimensions();
let mut rgba = Vec::with_capacity((width * height) as usize * PIXEL_SIZE);
for (_, _, pixel) in buf.enumerate_pixels() {
rgba.extend_from_slice(&pixel.data);
}
Icon { rgba, width, height }
}
}

542
src/lib.rs Normal file
View File

@@ -0,0 +1,542 @@
//! Winit allows you to build a window on as many platforms as possible.
//!
//! # Building a window
//!
//! Before you can build a window, you first need to build an `EventsLoop`. This is done with the
//! `EventsLoop::new()` function. Example:
//!
//! ```no_run
//! use winit::EventsLoop;
//! let events_loop = EventsLoop::new();
//! ```
//!
//! Once this is done there are two ways to create a window:
//!
//! - Calling `Window::new(&events_loop)`.
//! - Calling `let builder = WindowBuilder::new()` then `builder.build(&events_loop)`.
//!
//! The first way is the simplest way and will give you default values for everything.
//!
//! The second way allows you to customize the way your window will look and behave by modifying
//! the fields of the `WindowBuilder` object before you create the window.
//!
//! # Events handling
//!
//! Once a window has been created, it will *generate events*. For example whenever the user moves
//! the window, resizes the window, moves the mouse, etc. an event is generated.
//!
//! The events generated by a window can be retrieved from the `EventsLoop` the window was created
//! with.
//!
//! There are two ways to do so. The first is to call `events_loop.poll_events(...)`, which will
//! retrieve all the events pending on the windows and immediately return after no new event is
//! available. You usually want to use this method in application that render continuously on the
//! screen, such as video games.
//!
//! ```no_run
//! use winit::{Event, WindowEvent};
//! use winit::dpi::LogicalSize;
//! # use winit::EventsLoop;
//! # let mut events_loop = EventsLoop::new();
//!
//! loop {
//! events_loop.poll_events(|event| {
//! match event {
//! Event::WindowEvent {
//! event: WindowEvent::Resized(LogicalSize { width, height }),
//! ..
//! } => {
//! println!("The window was resized to {}x{}", width, height);
//! },
//! _ => ()
//! }
//! });
//! }
//! ```
//!
//! The second way is to call `events_loop.run_forever(...)`. As its name tells, it will run
//! forever unless it is stopped by returning `ControlFlow::Break`.
//!
//! ```no_run
//! use winit::{ControlFlow, Event, WindowEvent};
//! # use winit::EventsLoop;
//! # let mut events_loop = EventsLoop::new();
//!
//! events_loop.run_forever(|event| {
//! match event {
//! Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
//! println!("The close button was pressed; stopping");
//! ControlFlow::Break
//! },
//! _ => ControlFlow::Continue,
//! }
//! });
//! ```
//!
//! If you use multiple windows, the `WindowEvent` event has a member named `window_id`. You can
//! compare it with the value returned by the `id()` method of `Window` in order to know which
//! window has received the event.
//!
//! # Drawing on the window
//!
//! Winit doesn't provide any function that allows drawing on a window. However it allows you to
//! retrieve the raw handle of the window (see the `os` module for that), which in turn allows you
//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that will draw on the window.
//!
#[allow(unused_imports)]
#[macro_use]
extern crate lazy_static;
extern crate libc;
#[macro_use]
extern crate log;
#[cfg(feature = "icon_loading")]
extern crate image;
#[cfg(feature = "serde")]
#[macro_use]
extern crate serde;
#[cfg(target_os = "windows")]
extern crate winapi;
#[cfg(target_os = "windows")]
extern crate backtrace;
#[macro_use]
#[cfg(target_os = "windows")]
extern crate bitflags;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[macro_use]
extern crate objc;
#[cfg(target_os = "macos")]
extern crate cocoa;
#[cfg(target_os = "macos")]
extern crate core_foundation;
#[cfg(target_os = "macos")]
extern crate core_graphics;
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
extern crate x11_dl;
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
extern crate parking_lot;
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
extern crate percent_encoding;
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
extern crate smithay_client_toolkit as sctk;
pub(crate) use dpi::*; // TODO: Actually change the imports throughout the codebase.
pub use events::*;
pub use window::{AvailableMonitorsIter, MonitorId};
pub use icon::*;
pub mod dpi;
mod events;
mod icon;
mod platform;
mod window;
pub mod os;
/// Represents a window.
///
/// # Example
///
/// ```no_run
/// use winit::{Event, EventsLoop, Window, WindowEvent, ControlFlow};
///
/// let mut events_loop = EventsLoop::new();
/// let window = Window::new(&events_loop).unwrap();
///
/// events_loop.run_forever(|event| {
/// match event {
/// Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
/// ControlFlow::Break
/// },
/// _ => ControlFlow::Continue,
/// }
/// });
/// ```
pub struct Window {
window: platform::Window,
}
impl std::fmt::Debug for Window {
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
fmtr.pad("Window { .. }")
}
}
/// Identifier of a window. Unique for each window.
///
/// Can be obtained with `window.id()`.
///
/// Whenever you receive an event specific to a window, this event contains a `WindowId` which you
/// can then compare to the ids of your windows.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(platform::WindowId);
impl WindowId {
/// Returns a dummy `WindowId`, useful for unit testing. The only guarantee made about the return
/// value of this function is that it will always be equal to itself and to future values returned
/// by this function. No other guarantees are made. This may be equal to a real `WindowId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub unsafe fn dummy() -> Self {
WindowId(platform::WindowId::dummy())
}
}
/// Identifier of an input device.
///
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or
/// physical. Virtual devices typically aggregate inputs from multiple physical devices.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(platform::DeviceId);
impl DeviceId {
/// Returns a dummy `DeviceId`, useful for unit testing. The only guarantee made about the return
/// value of this function is that it will always be equal to itself and to future values returned
/// by this function. No other guarantees are made. This may be equal to a real `DeviceId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub unsafe fn dummy() -> Self {
DeviceId(platform::DeviceId::dummy())
}
}
/// Provides a way to retrieve events from the system and from the windows that were registered to
/// the events loop.
///
/// An `EventsLoop` can be seen more or less as a "context". Calling `EventsLoop::new()`
/// initializes everything that will be required to create windows. For example on Linux creating
/// an events loop opens a connection to the X or Wayland server.
///
/// To wake up an `EventsLoop` from a another thread, see the `EventsLoopProxy` docs.
///
/// Note that the `EventsLoop` cannot be shared accross threads (due to platform-dependant logic
/// forbiding it), as such it is neither `Send` nor `Sync`. If you need cross-thread access, the
/// `Window` created from this `EventsLoop` _can_ be sent to an other thread, and the
/// `EventsLoopProxy` allows you to wakeup an `EventsLoop` from an other thread.
pub struct EventsLoop {
events_loop: platform::EventsLoop,
_marker: ::std::marker::PhantomData<*mut ()> // Not Send nor Sync
}
impl std::fmt::Debug for EventsLoop {
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
fmtr.pad("EventsLoop { .. }")
}
}
/// Returned by the user callback given to the `EventsLoop::run_forever` method.
///
/// Indicates whether the `run_forever` method should continue or complete.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ControlFlow {
/// Continue looping and waiting for events.
Continue,
/// Break from the event loop.
Break,
}
impl EventsLoop {
/// Builds a new events loop.
///
/// Usage will result in display backend initialisation, this can be controlled on linux
/// using an environment variable `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
/// If it is not set, winit will try to connect to a wayland connection, and if it fails will
/// fallback on x11. If this variable is set with any other value, winit will panic.
pub fn new() -> EventsLoop {
EventsLoop {
events_loop: platform::EventsLoop::new(),
_marker: ::std::marker::PhantomData,
}
}
/// Returns the list of all the monitors available on the system.
///
// Note: should be replaced with `-> impl Iterator` once stable.
#[inline]
pub fn get_available_monitors(&self) -> AvailableMonitorsIter {
let data = self.events_loop.get_available_monitors();
AvailableMonitorsIter{ data: data.into_iter() }
}
/// Returns the primary monitor of the system.
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
MonitorId { inner: self.events_loop.get_primary_monitor() }
}
/// Fetches all the events that are pending, calls the callback function for each of them,
/// and returns.
#[inline]
pub fn poll_events<F>(&mut self, callback: F)
where F: FnMut(Event)
{
self.events_loop.poll_events(callback)
}
/// Calls `callback` every time an event is received. If no event is available, sleeps the
/// current thread and waits for an event. If the callback returns `ControlFlow::Break` then
/// `run_forever` will immediately return.
///
/// # Danger!
///
/// The callback is run after *every* event, so if its execution time is non-trivial the event queue may not empty
/// at a sufficient rate. Rendering in the callback with vsync enabled **will** cause significant lag.
#[inline]
pub fn run_forever<F>(&mut self, callback: F)
where F: FnMut(Event) -> ControlFlow
{
self.events_loop.run_forever(callback)
}
/// Creates an `EventsLoopProxy` that can be used to wake up the `EventsLoop` from another
/// thread.
pub fn create_proxy(&self) -> EventsLoopProxy {
EventsLoopProxy {
events_loop_proxy: self.events_loop.create_proxy(),
}
}
}
/// Used to wake up the `EventsLoop` from another thread.
#[derive(Clone)]
pub struct EventsLoopProxy {
events_loop_proxy: platform::EventsLoopProxy,
}
impl std::fmt::Debug for EventsLoopProxy {
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
fmtr.pad("EventsLoopProxy { .. }")
}
}
impl EventsLoopProxy {
/// Wake up the `EventsLoop` from which this proxy was created.
///
/// This causes the `EventsLoop` to emit an `Awakened` event.
///
/// Returns an `Err` if the associated `EventsLoop` no longer exists.
pub fn wakeup(&self) -> Result<(), EventsLoopClosed> {
self.events_loop_proxy.wakeup()
}
}
/// The error that is returned when an `EventsLoopProxy` attempts to wake up an `EventsLoop` that
/// no longer exists.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct EventsLoopClosed;
impl std::fmt::Display for EventsLoopClosed {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", std::error::Error::description(self))
}
}
impl std::error::Error for EventsLoopClosed {
fn description(&self) -> &str {
"Tried to wake up a closed `EventsLoop`"
}
}
/// Object that allows you to build windows.
#[derive(Clone)]
pub struct WindowBuilder {
/// The attributes to use to create the window.
pub window: WindowAttributes,
// Platform-specific configuration. Private.
platform_specific: platform::PlatformSpecificWindowBuilderAttributes,
}
impl std::fmt::Debug for WindowBuilder {
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
fmtr.debug_struct("WindowBuilder")
.field("window", &self.window)
.finish()
}
}
/// Error that can happen while creating a window or a headless renderer.
#[derive(Debug, Clone)]
pub enum CreationError {
OsError(String),
/// TODO: remove this error
NotSupported,
}
impl CreationError {
fn to_string(&self) -> &str {
match *self {
CreationError::OsError(ref text) => &text,
CreationError::NotSupported => "Some of the requested attributes are not supported",
}
}
}
impl std::fmt::Display for CreationError {
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
formatter.write_str(self.to_string())
}
}
impl std::error::Error for CreationError {
fn description(&self) -> &str {
self.to_string()
}
}
/// Describes the appearance of the mouse cursor.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseCursor {
/// The platform-dependent default cursor.
Default,
/// A simple crosshair.
Crosshair,
/// A hand (often used to indicate links in web browsers).
Hand,
/// Self explanatory.
Arrow,
/// Indicates something is to be moved.
Move,
/// Indicates text that may be selected or edited.
Text,
/// Program busy indicator.
Wait,
/// Help indicator (often rendered as a "?")
Help,
/// Progress indicator. Shows that processing is being done. But in contrast
/// with "Wait" the user may still interact with the program. Often rendered
/// as a spinning beach ball, or an arrow with a watch or hourglass.
Progress,
/// Cursor showing that something cannot be done.
NotAllowed,
ContextMenu,
Cell,
VerticalText,
Alias,
Copy,
NoDrop,
Grab,
Grabbing,
AllScroll,
ZoomIn,
ZoomOut,
/// Indicate that some edge is to be moved. For example, the 'SeResize' cursor
/// is used when the movement starts from the south-east corner of the box.
EResize,
NResize,
NeResize,
NwResize,
SResize,
SeResize,
SwResize,
WResize,
EwResize,
NsResize,
NeswResize,
NwseResize,
ColResize,
RowResize,
}
impl Default for MouseCursor {
fn default() -> Self {
MouseCursor::Default
}
}
/// Attributes to use when creating a window.
#[derive(Debug, Clone)]
pub struct WindowAttributes {
/// The dimensions of the window. If this is `None`, some platform-specific dimensions will be
/// used.
///
/// The default is `None`.
pub dimensions: Option<LogicalSize>,
/// The minimum dimensions a window can be, If this is `None`, the window will have no minimum dimensions (aside from reserved).
///
/// The default is `None`.
pub min_dimensions: Option<LogicalSize>,
/// The maximum dimensions a window can be, If this is `None`, the maximum will have no maximum or will be set to the primary monitor's dimensions by the platform.
///
/// The default is `None`.
pub max_dimensions: Option<LogicalSize>,
/// Whether the window is resizable or not.
///
/// The default is `true`.
pub resizable: bool,
/// Whether the window should be set as fullscreen upon creation.
///
/// The default is `None`.
pub fullscreen: Option<MonitorId>,
/// The title of the window in the title bar.
///
/// The default is `"winit window"`.
pub title: String,
/// Whether the window should be maximized upon creation.
///
/// The default is `false`.
pub maximized: bool,
/// Whether the window should be immediately visible upon creation.
///
/// The default is `true`.
pub visible: bool,
/// Whether the the window should be transparent. If this is true, writing colors
/// with alpha values different than `1.0` will produce a transparent window.
///
/// The default is `false`.
pub transparent: bool,
/// Whether the window should have borders and bars.
///
/// The default is `true`.
pub decorations: bool,
/// Whether the window should always be on top of other windows.
///
/// The default is `false`.
pub always_on_top: bool,
/// The window icon.
///
/// The default is `None`.
pub window_icon: Option<Icon>,
/// [iOS only] Enable multitouch,
/// see [multipleTouchEnabled](https://developer.apple.com/documentation/uikit/uiview/1622519-multipletouchenabled)
pub multitouch: bool,
}
impl Default for WindowAttributes {
#[inline]
fn default() -> WindowAttributes {
WindowAttributes {
dimensions: None,
min_dimensions: None,
max_dimensions: None,
resizable: true,
title: "winit window".to_owned(),
maximized: false,
fullscreen: None,
visible: true,
transparent: false,
decorations: true,
always_on_top: false,
window_icon: None,
multitouch: false,
}
}
}

38
src/os/android.rs Normal file
View File

@@ -0,0 +1,38 @@
#![cfg(any(target_os = "android"))]
use std::os::raw::c_void;
use EventsLoop;
use Window;
use WindowBuilder;
/// Additional methods on `EventsLoop` that are specific to Android.
pub trait EventsLoopExt {
/// Makes it possible for glutin to register a callback when a suspend event happens on Android
fn set_suspend_callback(&self, cb: Option<Box<Fn(bool) -> ()>>);
}
impl EventsLoopExt for EventsLoop {
fn set_suspend_callback(&self, cb: Option<Box<Fn(bool) -> ()>>) {
self.events_loop.set_suspend_callback(cb);
}
}
/// Additional methods on `Window` that are specific to Android.
pub trait WindowExt {
fn get_native_window(&self) -> *const c_void;
}
impl WindowExt for Window {
#[inline]
fn get_native_window(&self) -> *const c_void {
self.window.get_native_window()
}
}
/// Additional methods on `WindowBuilder` that are specific to Android.
pub trait WindowBuilderExt {
}
impl WindowBuilderExt for WindowBuilder {
}

59
src/os/ios.rs Normal file
View File

@@ -0,0 +1,59 @@
#![cfg(target_os = "ios")]
use std::os::raw::c_void;
use {MonitorId, Window, WindowBuilder};
/// Additional methods on `Window` that are specific to iOS.
pub trait WindowExt {
/// Returns a pointer to the `UIWindow` that is used by this window.
///
/// The pointer will become invalid when the `Window` is destroyed.
fn get_uiwindow(&self) -> *mut c_void;
/// Returns a pointer to the `UIView` that is used by this window.
///
/// The pointer will become invalid when the `Window` is destroyed.
fn get_uiview(&self) -> *mut c_void;
}
impl WindowExt for Window {
#[inline]
fn get_uiwindow(&self) -> *mut c_void {
self.window.get_uiwindow() as _
}
#[inline]
fn get_uiview(&self) -> *mut c_void {
self.window.get_uiview() as _
}
}
/// Additional methods on `WindowBuilder` that are specific to iOS.
pub trait WindowBuilderExt {
/// Sets the root view class used by the `Window`, otherwise a barebones `UIView` is provided.
///
/// The class will be initialized by calling `[root_view initWithFrame:CGRect]`
fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder;
}
impl WindowBuilderExt for WindowBuilder {
#[inline]
fn with_root_view_class(mut self, root_view_class: *const c_void) -> WindowBuilder {
self.platform_specific.root_view_class = unsafe { &*(root_view_class as *const _) };
self
}
}
/// Additional methods on `MonitorId` that are specific to iOS.
pub trait MonitorIdExt {
/// Returns a pointer to the `UIScreen` that is used by this monitor.
fn get_uiscreen(&self) -> *mut c_void;
}
impl MonitorIdExt for MonitorId {
#[inline]
fn get_uiscreen(&self) -> *mut c_void {
self.inner.get_uiscreen() as _
}
}

179
src/os/macos.rs Normal file
View File

@@ -0,0 +1,179 @@
#![cfg(target_os = "macos")]
use std::os::raw::c_void;
use {LogicalSize, MonitorId, Window, WindowBuilder};
/// Additional methods on `Window` that are specific to MacOS.
pub trait WindowExt {
/// Returns a pointer to the cocoa `NSWindow` that is used by this window.
///
/// The pointer will become invalid when the `Window` is destroyed.
fn get_nswindow(&self) -> *mut c_void;
/// Returns a pointer to the cocoa `NSView` that is used by this window.
///
/// The pointer will become invalid when the `Window` is destroyed.
fn get_nsview(&self) -> *mut c_void;
/// Request user attention, causing the application's dock icon to bounce.
/// Note that this has no effect if the application is already focused.
///
/// The `is_critical` flag has the following effects:
/// - `false`: the dock icon will only bounce once.
/// - `true`: the dock icon will bounce until the application is focused.
fn request_user_attention(&self, is_critical: bool);
/// Returns whether or not the window is in simple fullscreen mode.
fn get_simple_fullscreen(&self) -> bool;
/// Toggles a fullscreen mode that doesn't require a new macOS space.
/// Returns a boolean indicating whether the transition was successful (this
/// won't work if the window was already in the native fullscreen).
///
/// This is how fullscreen used to work on macOS in versions before Lion.
/// And allows the user to have a fullscreen window without using another
/// space or taking control over the entire monitor.
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool;
}
impl WindowExt for Window {
#[inline]
fn get_nswindow(&self) -> *mut c_void {
self.window.get_nswindow()
}
#[inline]
fn get_nsview(&self) -> *mut c_void {
self.window.get_nsview()
}
#[inline]
fn request_user_attention(&self, is_critical: bool) {
self.window.request_user_attention(is_critical)
}
#[inline]
fn get_simple_fullscreen(&self) -> bool {
self.window.get_simple_fullscreen()
}
#[inline]
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
self.window.set_simple_fullscreen(fullscreen)
}
}
/// Corresponds to `NSApplicationActivationPolicy`.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ActivationPolicy {
/// Corresponds to `NSApplicationActivationPolicyRegular`.
Regular,
/// Corresponds to `NSApplicationActivationPolicyAccessory`.
Accessory,
/// Corresponds to `NSApplicationActivationPolicyProhibited`.
Prohibited,
}
impl Default for ActivationPolicy {
fn default() -> Self {
ActivationPolicy::Regular
}
}
/// Additional methods on `WindowBuilder` that are specific to MacOS.
///
/// **Note:** Properties dealing with the titlebar will be overwritten by the `with_decorations` method
/// on the base `WindowBuilder`:
///
/// - `with_titlebar_transparent`
/// - `with_title_hidden`
/// - `with_titlebar_hidden`
/// - `with_titlebar_buttons_hidden`
/// - `with_fullsize_content_view`
pub trait WindowBuilderExt {
/// Sets the activation policy for the window being built.
fn with_activation_policy(self, activation_policy: ActivationPolicy) -> WindowBuilder;
/// Enables click-and-drag behavior for the entire window, not just the titlebar.
fn with_movable_by_window_background(self, movable_by_window_background: bool) -> WindowBuilder;
/// Makes the titlebar transparent and allows the content to appear behind it.
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> WindowBuilder;
/// Hides the window title.
fn with_title_hidden(self, title_hidden: bool) -> WindowBuilder;
/// Hides the window titlebar.
fn with_titlebar_hidden(self, titlebar_hidden: bool) -> WindowBuilder;
/// Hides the window titlebar buttons.
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> WindowBuilder;
/// Makes the window content appear behind the titlebar.
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
/// Build window with `resizeIncrements` property. Values must not be 0.
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
}
impl WindowBuilderExt for WindowBuilder {
#[inline]
fn with_activation_policy(mut self, activation_policy: ActivationPolicy) -> WindowBuilder {
self.platform_specific.activation_policy = activation_policy;
self
}
#[inline]
fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> WindowBuilder {
self.platform_specific.movable_by_window_background = movable_by_window_background;
self
}
#[inline]
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> WindowBuilder {
self.platform_specific.titlebar_transparent = titlebar_transparent;
self
}
#[inline]
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> WindowBuilder {
self.platform_specific.titlebar_hidden = titlebar_hidden;
self
}
#[inline]
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> WindowBuilder {
self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
self
}
#[inline]
fn with_title_hidden(mut self, title_hidden: bool) -> WindowBuilder {
self.platform_specific.title_hidden = title_hidden;
self
}
#[inline]
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> WindowBuilder {
self.platform_specific.fullsize_content_view = fullsize_content_view;
self
}
#[inline]
fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder {
self.platform_specific.resize_increments = Some(increments.into());
self
}
}
/// Additional methods on `MonitorId` that are specific to MacOS.
pub trait MonitorIdExt {
/// Returns the identifier of the monitor for Cocoa.
fn native_id(&self) -> u32;
/// Returns a pointer to the NSScreen representing this monitor.
fn get_nsscreen(&self) -> Option<*mut c_void>;
}
impl MonitorIdExt for MonitorId {
#[inline]
fn native_id(&self) -> u32 {
self.inner.get_native_identifier()
}
fn get_nsscreen(&self) -> Option<*mut c_void> {
self.inner.get_nsscreen().map(|s| s as *mut c_void)
}
}

17
src/os/mod.rs Normal file
View File

@@ -0,0 +1,17 @@
//! Contains traits with platform-specific methods in them.
//!
//! Contains the follow modules:
//!
//! - `android`
//! - `ios`
//! - `macos`
//! - `unix`
//! - `windows`
//!
//! However only the module corresponding to the platform you're compiling to will be available.
//!
pub mod android;
pub mod ios;
pub mod macos;
pub mod unix;
pub mod windows;

402
src/os/unix.rs Normal file
View File

@@ -0,0 +1,402 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
use std::os::raw;
use std::ptr;
use std::sync::Arc;
use sctk::window::{ButtonState, Theme};
use {
EventsLoop,
LogicalSize,
MonitorId,
Window,
WindowBuilder,
};
use platform::{
EventsLoop as LinuxEventsLoop,
Window as LinuxWindow,
};
use platform::x11::XConnection;
use platform::x11::ffi::XVisualInfo;
// TODO: stupid hack so that glutin can do its work
#[doc(hidden)]
pub use platform::x11;
pub use platform::XNotSupported;
pub use platform::x11::util::WindowType as XWindowType;
/// Theme for wayland client side decorations
///
/// Colors must be in ARGB8888 format
pub struct WaylandTheme {
/// Primary color when the window is focused
pub primary_active: [u8; 4],
/// Primary color when the window is unfocused
pub primary_inactive: [u8; 4],
/// Secondary color when the window is focused
pub secondary_active: [u8; 4],
/// Secondary color when the window is unfocused
pub secondary_inactive: [u8; 4],
/// Close button color when hovered over
pub close_button_hovered: [u8; 4],
/// Close button color
pub close_button: [u8; 4],
/// Close button color when hovered over
pub maximize_button_hovered: [u8; 4],
/// Maximize button color
pub maximize_button: [u8; 4],
/// Minimize button color when hovered over
pub minimize_button_hovered: [u8; 4],
/// Minimize button color
pub minimize_button: [u8; 4],
}
struct WaylandThemeObject(WaylandTheme);
impl Theme for WaylandThemeObject {
fn get_primary_color(&self, active: bool) -> [u8; 4] {
if active {
self.0.primary_active
} else {
self.0.primary_inactive
}
}
// Used for division line
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
if active {
self.0.secondary_active
} else {
self.0.secondary_inactive
}
}
fn get_close_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.close_button_hovered,
_ => self.0.close_button,
}
}
fn get_maximize_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.maximize_button_hovered,
_ => self.0.maximize_button,
}
}
fn get_minimize_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.minimize_button_hovered,
_ => self.0.minimize_button,
}
}
}
/// Additional methods on `EventsLoop` that are specific to Linux.
pub trait EventsLoopExt {
/// Builds a new `EventsLoop` that is forced to use X11.
fn new_x11() -> Result<Self, XNotSupported>
where Self: Sized;
/// Builds a new `EventsLoop` that is forced to use Wayland.
fn new_wayland() -> Self
where Self: Sized;
/// True if the `EventsLoop` uses Wayland.
fn is_wayland(&self) -> bool;
/// True if the `EventsLoop` uses X11.
fn is_x11(&self) -> bool;
#[doc(hidden)]
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this `EventsLoop`.
///
/// Returns `None` if the `EventsLoop` doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the glutin `EventsLoop` is destroyed.
fn get_wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl EventsLoopExt for EventsLoop {
#[inline]
fn new_x11() -> Result<Self, XNotSupported> {
LinuxEventsLoop::new_x11().map(|ev|
EventsLoop {
events_loop: ev,
_marker: ::std::marker::PhantomData,
}
)
}
#[inline]
fn new_wayland() -> Self {
EventsLoop {
events_loop: match LinuxEventsLoop::new_wayland() {
Ok(e) => e,
Err(_) => panic!() // TODO: propagate
},
_marker: ::std::marker::PhantomData,
}
}
#[inline]
fn is_wayland(&self) -> bool {
self.events_loop.is_wayland()
}
#[inline]
fn is_x11(&self) -> bool {
!self.events_loop.is_wayland()
}
#[inline]
#[doc(hidden)]
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>> {
self.events_loop.x_connection().cloned()
}
#[inline]
fn get_wayland_display(&self) -> Option<*mut raw::c_void> {
match self.events_loop {
LinuxEventsLoop::Wayland(ref e) => Some(e.get_display().c_ptr() as *mut _),
_ => None
}
}
}
/// Additional methods on `Window` that are specific to Unix.
pub trait WindowExt {
/// Returns the ID of the `Window` xlib object that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
fn get_xlib_window(&self) -> Option<raw::c_ulong>;
/// Returns a pointer to the `Display` object of xlib that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the glutin `Window` is destroyed.
fn get_xlib_display(&self) -> Option<*mut raw::c_void>;
fn get_xlib_screen_id(&self) -> Option<raw::c_int>;
#[doc(hidden)]
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Set window urgency hint (`XUrgencyHint`). Only relevant on X.
fn set_urgent(&self, is_urgent: bool);
/// This function returns the underlying `xcb_connection_t` of an xlib `Display`.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the glutin `Window` is destroyed.
fn get_xcb_connection(&self) -> Option<*mut raw::c_void>;
/// Returns a pointer to the `wl_surface` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the glutin `Window` is destroyed.
fn get_wayland_surface(&self) -> Option<*mut raw::c_void>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the glutin `Window` is destroyed.
fn get_wayland_display(&self) -> Option<*mut raw::c_void>;
/// Sets the color theme of the client side window decorations on wayland
fn set_wayland_theme(&self, theme: WaylandTheme);
/// Check if the window is ready for drawing
///
/// It is a remnant of a previous implementation detail for the
/// wayland backend, and is no longer relevant.
///
/// Always return true.
#[deprecated]
fn is_ready(&self) -> bool;
}
impl WindowExt for Window {
#[inline]
fn get_xlib_window(&self) -> Option<raw::c_ulong> {
match self.window {
LinuxWindow::X(ref w) => Some(w.get_xlib_window()),
_ => None
}
}
#[inline]
fn get_xlib_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.get_xlib_display()),
_ => None
}
}
#[inline]
fn get_xlib_screen_id(&self) -> Option<raw::c_int> {
match self.window {
LinuxWindow::X(ref w) => Some(w.get_xlib_screen_id()),
_ => None
}
}
#[inline]
#[doc(hidden)]
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.window {
LinuxWindow::X(ref w) => Some(w.get_xlib_xconnection()),
_ => None
}
}
#[inline]
fn get_xcb_connection(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.get_xcb_connection()),
_ => None
}
}
#[inline]
fn set_urgent(&self, is_urgent: bool) {
if let LinuxWindow::X(ref w) = self.window {
w.set_urgent(is_urgent);
}
}
#[inline]
fn get_wayland_surface(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.get_surface().c_ptr() as *mut _),
_ => None
}
}
#[inline]
fn get_wayland_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.get_display().c_ptr() as *mut _),
_ => None
}
}
#[inline]
fn set_wayland_theme(&self, theme: WaylandTheme) {
match self.window {
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandThemeObject(theme)),
_ => {}
}
}
#[inline]
fn is_ready(&self) -> bool {
true
}
}
/// Additional methods on `WindowBuilder` that are specific to Unix.
pub trait WindowBuilderExt {
fn with_x11_visual<T>(self, visual_infos: *const T) -> WindowBuilder;
fn with_x11_screen(self, screen_id: i32) -> WindowBuilder;
/// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11.
fn with_class(self, class: String, instance: String) -> WindowBuilder;
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
fn with_override_redirect(self, override_redirect: bool) -> WindowBuilder;
/// Build window with `_NET_WM_WINDOW_TYPE` hint; defaults to `Normal`. Only relevant on X11.
fn with_x11_window_type(self, x11_window_type: XWindowType) -> WindowBuilder;
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
fn with_gtk_theme_variant(self, variant: String) -> WindowBuilder;
/// Build window with resize increment hint. Only implemented on X11.
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
/// Build window with base size hint. Only implemented on X11.
fn with_base_size(self, base_size: LogicalSize) -> WindowBuilder;
/// Build window with a given application ID. It should match the `.desktop` file distributed with
/// your program. Only relevant on Wayland.
///
/// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
fn with_app_id(self, app_id: String) -> WindowBuilder;
}
impl WindowBuilderExt for WindowBuilder {
#[inline]
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> WindowBuilder {
self.platform_specific.visual_infos = Some(
unsafe { ptr::read(visual_infos as *const XVisualInfo) }
);
self
}
#[inline]
fn with_x11_screen(mut self, screen_id: i32) -> WindowBuilder {
self.platform_specific.screen_id = Some(screen_id);
self
}
#[inline]
fn with_class(mut self, instance: String, class: String) -> WindowBuilder {
self.platform_specific.class = Some((instance, class));
self
}
#[inline]
fn with_override_redirect(mut self, override_redirect: bool) -> WindowBuilder {
self.platform_specific.override_redirect = override_redirect;
self
}
#[inline]
fn with_x11_window_type(mut self, x11_window_type: XWindowType) -> WindowBuilder {
self.platform_specific.x11_window_type = x11_window_type;
self
}
#[inline]
fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder {
self.platform_specific.resize_increments = Some(increments.into());
self
}
#[inline]
fn with_base_size(mut self, base_size: LogicalSize) -> WindowBuilder {
self.platform_specific.base_size = Some(base_size.into());
self
}
#[inline]
fn with_gtk_theme_variant(mut self, variant: String) -> WindowBuilder {
self.platform_specific.gtk_theme_variant = Some(variant);
self
}
#[inline]
fn with_app_id(mut self, app_id: String) -> WindowBuilder {
self.platform_specific.app_id = Some(app_id);
self
}
}
/// Additional methods on `MonitorId` that are specific to Linux.
pub trait MonitorIdExt {
/// Returns the inner identifier of the monitor.
fn native_id(&self) -> u32;
}
impl MonitorIdExt for MonitorId {
#[inline]
fn native_id(&self) -> u32 {
self.inner.get_native_identifier()
}
}

117
src/os/windows.rs Normal file
View File

@@ -0,0 +1,117 @@
#![cfg(target_os = "windows")]
use std::os::raw::c_void;
use libc;
use winapi::shared::windef::HWND;
use {DeviceId, EventsLoop, Icon, MonitorId, Window, WindowBuilder};
use platform::EventsLoop as WindowsEventsLoop;
/// Additional methods on `EventsLoop` that are specific to Windows.
pub trait EventsLoopExt {
/// By default, winit on Windows will attempt to enable process-wide DPI awareness. If that's
/// undesirable, you can create an `EventsLoop` using this function instead.
fn new_dpi_unaware() -> Self where Self: Sized;
}
impl EventsLoopExt for EventsLoop {
#[inline]
fn new_dpi_unaware() -> Self {
EventsLoop {
events_loop: WindowsEventsLoop::with_dpi_awareness(false),
_marker: ::std::marker::PhantomData,
}
}
}
/// Additional methods on `Window` that are specific to Windows.
pub trait WindowExt {
/// Returns the native handle that is used by this window.
///
/// The pointer will become invalid when the native window was destroyed.
fn get_hwnd(&self) -> *mut libc::c_void;
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
}
impl WindowExt for Window {
#[inline]
fn get_hwnd(&self) -> *mut libc::c_void {
self.window.hwnd() as *mut _
}
#[inline]
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
self.window.set_taskbar_icon(taskbar_icon)
}
}
/// Additional methods on `WindowBuilder` that are specific to Windows.
pub trait WindowBuilderExt {
/// Sets a parent to the window to be created.
fn with_parent_window(self, parent: HWND) -> WindowBuilder;
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> WindowBuilder;
/// This sets `WS_EX_NOREDIRECTIONBITMAP`.
fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder;
}
impl WindowBuilderExt for WindowBuilder {
#[inline]
fn with_parent_window(mut self, parent: HWND) -> WindowBuilder {
self.platform_specific.parent = Some(parent);
self
}
#[inline]
fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> WindowBuilder {
self.platform_specific.taskbar_icon = taskbar_icon;
self
}
#[inline]
fn with_no_redirection_bitmap(mut self, flag: bool) -> WindowBuilder {
self.platform_specific.no_redirection_bitmap = flag;
self
}
}
/// Additional methods on `MonitorId` that are specific to Windows.
pub trait MonitorIdExt {
/// Returns the name of the monitor adapter specific to the Win32 API.
fn native_id(&self) -> String;
/// Returns the handle of the monitor - `HMONITOR`.
fn hmonitor(&self) -> *mut c_void;
}
impl MonitorIdExt for MonitorId {
#[inline]
fn native_id(&self) -> String {
self.inner.get_native_identifier()
}
#[inline]
fn hmonitor(&self) -> *mut c_void {
self.inner.get_hmonitor() as *mut _
}
}
/// Additional methods on `DeviceId` that are specific to Windows.
pub trait DeviceIdExt {
/// Returns an identifier that persistently refers to this specific device.
///
/// Will return `None` if the device is no longer available.
fn get_persistent_identifier(&self) -> Option<String>;
}
impl DeviceIdExt for DeviceId {
#[inline]
fn get_persistent_identifier(&self) -> Option<String> {
self.0.get_persistent_identifier()
}
}

108
src/platform/android/ffi.rs Normal file
View File

@@ -0,0 +1,108 @@
#![allow(dead_code)]
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
use libc;
use std::os::raw;
#[link(name = "android")]
#[link(name = "EGL")]
#[link(name = "GLESv2")]
extern {}
/**
* asset_manager.h
*/
pub type AAssetManager = raw::c_void;
/**
* native_window.h
*/
pub type ANativeWindow = raw::c_void;
extern {
pub fn ANativeWindow_getHeight(window: *const ANativeWindow) -> libc::int32_t;
pub fn ANativeWindow_getWidth(window: *const ANativeWindow) -> libc::int32_t;
}
/**
* native_activity.h
*/
pub type JavaVM = ();
pub type JNIEnv = ();
pub type jobject = *const libc::c_void;
pub type AInputQueue = (); // FIXME: wrong
pub type ARect = (); // FIXME: wrong
#[repr(C)]
pub struct ANativeActivity {
pub callbacks: *mut ANativeActivityCallbacks,
pub vm: *mut JavaVM,
pub env: *mut JNIEnv,
pub clazz: jobject,
pub internalDataPath: *const libc::c_char,
pub externalDataPath: *const libc::c_char,
pub sdkVersion: libc::int32_t,
pub instance: *mut libc::c_void,
pub assetManager: *mut AAssetManager,
pub obbPath: *const libc::c_char,
}
#[repr(C)]
pub struct ANativeActivityCallbacks {
pub onStart: extern fn(*mut ANativeActivity),
pub onResume: extern fn(*mut ANativeActivity),
pub onSaveInstanceState: extern fn(*mut ANativeActivity, *mut libc::size_t),
pub onPause: extern fn(*mut ANativeActivity),
pub onStop: extern fn(*mut ANativeActivity),
pub onDestroy: extern fn(*mut ANativeActivity),
pub onWindowFocusChanged: extern fn(*mut ANativeActivity, libc::c_int),
pub onNativeWindowCreated: extern fn(*mut ANativeActivity, *const ANativeWindow),
pub onNativeWindowResized: extern fn(*mut ANativeActivity, *const ANativeWindow),
pub onNativeWindowRedrawNeeded: extern fn(*mut ANativeActivity, *const ANativeWindow),
pub onNativeWindowDestroyed: extern fn(*mut ANativeActivity, *const ANativeWindow),
pub onInputQueueCreated: extern fn(*mut ANativeActivity, *mut AInputQueue),
pub onInputQueueDestroyed: extern fn(*mut ANativeActivity, *mut AInputQueue),
pub onContentRectChanged: extern fn(*mut ANativeActivity, *const ARect),
pub onConfigurationChanged: extern fn(*mut ANativeActivity),
pub onLowMemory: extern fn(*mut ANativeActivity),
}
/**
* looper.h
*/
pub type ALooper = ();
#[link(name = "android")]
extern {
pub fn ALooper_forThread() -> *const ALooper;
pub fn ALooper_acquire(looper: *const ALooper);
pub fn ALooper_release(looper: *const ALooper);
pub fn ALooper_prepare(opts: libc::c_int) -> *const ALooper;
pub fn ALooper_pollOnce(timeoutMillis: libc::c_int, outFd: *mut libc::c_int,
outEvents: *mut libc::c_int, outData: *mut *mut libc::c_void) -> libc::c_int;
pub fn ALooper_pollAll(timeoutMillis: libc::c_int, outFd: *mut libc::c_int,
outEvents: *mut libc::c_int, outData: *mut *mut libc::c_void) -> libc::c_int;
pub fn ALooper_wake(looper: *const ALooper);
pub fn ALooper_addFd(looper: *const ALooper, fd: libc::c_int, ident: libc::c_int,
events: libc::c_int, callback: ALooper_callbackFunc, data: *mut libc::c_void)
-> libc::c_int;
pub fn ALooper_removeFd(looper: *const ALooper, fd: libc::c_int) -> libc::c_int;
}
pub const ALOOPER_PREPARE_ALLOW_NON_CALLBACKS: libc::c_int = 1 << 0;
pub const ALOOPER_POLL_WAKE: libc::c_int = -1;
pub const ALOOPER_POLL_CALLBACK: libc::c_int = -2;
pub const ALOOPER_POLL_TIMEOUT: libc::c_int = -3;
pub const ALOOPER_POLL_ERROR: libc::c_int = -4;
pub const ALOOPER_EVENT_INPUT: libc::c_int = 1 << 0;
pub const ALOOPER_EVENT_OUTPUT: libc::c_int = 1 << 1;
pub const ALOOPER_EVENT_ERROR: libc::c_int = 1 << 2;
pub const ALOOPER_EVENT_HANGUP: libc::c_int = 1 << 3;
pub const ALOOPER_EVENT_INVALID: libc::c_int = 1 << 4;
pub type ALooper_callbackFunc = extern fn(libc::c_int, libc::c_int, *mut libc::c_void) -> libc::c_int;

431
src/platform/android/mod.rs Normal file
View File

@@ -0,0 +1,431 @@
#![cfg(target_os = "android")]
extern crate android_glue;
mod ffi;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::fmt;
use std::os::raw::c_void;
use std::sync::mpsc::{Receiver, channel};
use {
CreationError,
Event,
LogicalPosition,
LogicalSize,
MouseCursor,
PhysicalPosition,
PhysicalSize,
WindowAttributes,
WindowEvent,
WindowId as RootWindowId,
};
use CreationError::OsError;
use events::{Touch, TouchPhase};
use window::MonitorId as RootMonitorId;
pub struct EventsLoop {
event_rx: Receiver<android_glue::Event>,
suspend_callback: RefCell<Option<Box<Fn(bool) -> ()>>>,
}
#[derive(Clone)]
pub struct EventsLoopProxy;
impl EventsLoop {
pub fn new() -> EventsLoop {
let (tx, rx) = channel();
android_glue::add_sender(tx);
EventsLoop {
event_rx: rx,
suspend_callback: Default::default(),
}
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorId);
rb
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
MonitorId
}
pub fn poll_events<F>(&mut self, mut callback: F)
where F: FnMut(::Event)
{
while let Ok(event) = self.event_rx.try_recv() {
let e = match event{
android_glue::Event::EventMotion(motion) => {
let dpi_factor = MonitorId.get_hidpi_factor();
let location = LogicalPosition::from_physical(
(motion.x as f64, motion.y as f64),
dpi_factor,
);
Some(Event::WindowEvent {
window_id: RootWindowId(WindowId),
event: WindowEvent::Touch(Touch {
phase: match motion.action {
android_glue::MotionAction::Down => TouchPhase::Started,
android_glue::MotionAction::Move => TouchPhase::Moved,
android_glue::MotionAction::Up => TouchPhase::Ended,
android_glue::MotionAction::Cancel => TouchPhase::Cancelled,
},
location,
id: motion.pointer_id as u64,
device_id: DEVICE_ID,
}),
})
},
android_glue::Event::InitWindow => {
// The activity went to foreground.
if let Some(cb) = self.suspend_callback.borrow().as_ref() {
(*cb)(false);
}
Some(Event::Suspended(false))
},
android_glue::Event::TermWindow => {
// The activity went to background.
if let Some(cb) = self.suspend_callback.borrow().as_ref() {
(*cb)(true);
}
Some(Event::Suspended(true))
},
android_glue::Event::WindowResized |
android_glue::Event::ConfigChanged => {
// Activity Orientation changed or resized.
let native_window = unsafe { android_glue::get_native_window() };
if native_window.is_null() {
None
} else {
let dpi_factor = MonitorId.get_hidpi_factor();
let physical_size = MonitorId.get_dimensions();
let size = LogicalSize::from_physical(physical_size, dpi_factor);
Some(Event::WindowEvent {
window_id: RootWindowId(WindowId),
event: WindowEvent::Resized(size),
})
}
},
android_glue::Event::WindowRedrawNeeded => {
// The activity needs to be redrawn.
Some(Event::WindowEvent {
window_id: RootWindowId(WindowId),
event: WindowEvent::Refresh,
})
}
android_glue::Event::Wake => {
Some(Event::Awakened)
}
_ => {
None
}
};
if let Some(event) = e {
callback(event);
}
};
}
pub fn set_suspend_callback(&self, cb: Option<Box<Fn(bool) -> ()>>) {
*self.suspend_callback.borrow_mut() = cb;
}
pub fn run_forever<F>(&mut self, mut callback: F)
where F: FnMut(::Event) -> ::ControlFlow,
{
// Yeah that's a very bad implementation.
loop {
let mut control_flow = ::ControlFlow::Continue;
self.poll_events(|e| {
if let ::ControlFlow::Break = callback(e) {
control_flow = ::ControlFlow::Break;
}
});
if let ::ControlFlow::Break = control_flow {
break;
}
::std::thread::sleep(::std::time::Duration::from_millis(5));
}
}
pub fn create_proxy(&self) -> EventsLoopProxy {
EventsLoopProxy
}
}
impl EventsLoopProxy {
pub fn wakeup(&self) -> Result<(), ::EventsLoopClosed> {
android_glue::wake_event_loop();
Ok(())
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId;
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId
}
}
pub struct Window {
native_window: *const c_void,
}
#[derive(Clone)]
pub struct MonitorId;
impl fmt::Debug for MonitorId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct MonitorId {
name: Option<String>,
dimensions: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: f64,
}
let monitor_id_proxy = MonitorId {
name: self.get_name(),
dimensions: self.get_dimensions(),
position: self.get_position(),
hidpi_factor: self.get_hidpi_factor(),
};
monitor_id_proxy.fmt(f)
}
}
impl MonitorId {
#[inline]
pub fn get_name(&self) -> Option<String> {
Some("Primary".to_string())
}
#[inline]
pub fn get_dimensions(&self) -> PhysicalSize {
unsafe {
let window = android_glue::get_native_window();
(
ffi::ANativeWindow_getWidth(window) as f64,
ffi::ANativeWindow_getHeight(window) as f64,
).into()
}
}
#[inline]
pub fn get_position(&self) -> PhysicalPosition {
// Android assumes single screen
(0, 0).into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
1.0
}
}
#[derive(Clone, Default)]
pub struct PlatformSpecificWindowBuilderAttributes;
#[derive(Clone, Default)]
pub struct PlatformSpecificHeadlessBuilderAttributes;
impl Window {
pub fn new(_: &EventsLoop, win_attribs: WindowAttributes,
_: PlatformSpecificWindowBuilderAttributes)
-> Result<Window, CreationError>
{
let native_window = unsafe { android_glue::get_native_window() };
if native_window.is_null() {
return Err(OsError(format!("Android's native window is null")));
}
android_glue::set_multitouch(win_attribs.multitouch);
Ok(Window {
native_window: native_window as *const _,
})
}
#[inline]
pub fn get_native_window(&self) -> *const c_void {
self.native_window
}
#[inline]
pub fn set_title(&self, _: &str) {
// N/A
}
#[inline]
pub fn show(&self) {
// N/A
}
#[inline]
pub fn hide(&self) {
// N/A
}
#[inline]
pub fn get_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn set_position(&self, _position: LogicalPosition) {
// N/A
}
#[inline]
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_resizable(&self, _resizable: bool) {
// N/A
}
#[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> {
if self.native_window.is_null() {
None
} else {
let dpi_factor = self.get_hidpi_factor();
let physical_size = self.get_current_monitor().get_dimensions();
Some(LogicalSize::from_physical(physical_size, dpi_factor))
}
}
#[inline]
pub fn get_outer_size(&self) -> Option<LogicalSize> {
self.get_inner_size()
}
#[inline]
pub fn set_inner_size(&self, _size: LogicalSize) {
// N/A
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
self.get_current_monitor().get_hidpi_factor()
}
#[inline]
pub fn set_cursor(&self, _: MouseCursor) {
// N/A
}
#[inline]
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
Err("Cursor grabbing is not possible on Android.".to_owned())
}
#[inline]
pub fn hide_cursor(&self, _hide: bool) {
// N/A
}
#[inline]
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
Err("Setting cursor position is not possible on Android.".to_owned())
}
#[inline]
pub fn set_maximized(&self, _maximized: bool) {
// N/A
// Android has single screen maximized apps so nothing to do
}
#[inline]
pub fn get_fullscreen(&self) -> Option<RootMonitorId> {
// N/A
// Android has single screen maximized apps so nothing to do
None
}
#[inline]
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorId>) {
// N/A
// Android has single screen maximized apps so nothing to do
}
#[inline]
pub fn set_decorations(&self, _decorations: bool) {
// N/A
}
#[inline]
pub fn set_always_on_top(&self, _always_on_top: bool) {
// N/A
}
#[inline]
pub fn set_window_icon(&self, _icon: Option<::Icon>) {
// N/A
}
#[inline]
pub fn set_ime_spot(&self, _spot: LogicalPosition) {
// N/A
}
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorId {
RootMonitorId { inner: MonitorId }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorId);
rb
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
MonitorId
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId
}
}
unsafe impl Send for Window {}
unsafe impl Sync for Window {}
// Constant device ID, to be removed when this backend is updated to report real device IDs.
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);

View File

@@ -0,0 +1,314 @@
#![allow(dead_code, non_camel_case_types, non_snake_case)]
use std::os::raw::{c_int, c_char, c_void, c_ulong, c_double, c_long, c_ushort};
#[cfg(test)]
use std::mem;
pub type EM_BOOL = c_int;
pub type EM_UTF8 = c_char;
pub type EMSCRIPTEN_RESULT = c_int;
pub const EM_TRUE: EM_BOOL = 1;
pub const EM_FALSE: EM_BOOL = 0;
// values for EMSCRIPTEN_RESULT
pub const EMSCRIPTEN_RESULT_SUCCESS: c_int = 0;
pub const EMSCRIPTEN_RESULT_DEFERRED: c_int = 1;
pub const EMSCRIPTEN_RESULT_NOT_SUPPORTED: c_int = -1;
pub const EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED: c_int = -2;
pub const EMSCRIPTEN_RESULT_INVALID_TARGET: c_int = -3;
pub const EMSCRIPTEN_RESULT_UNKNOWN_TARGET: c_int = -4;
pub const EMSCRIPTEN_RESULT_INVALID_PARAM: c_int = -5;
pub const EMSCRIPTEN_RESULT_FAILED: c_int = -6;
pub const EMSCRIPTEN_RESULT_NO_DATA: c_int = -7;
// values for EMSCRIPTEN EVENT
pub const EMSCRIPTEN_EVENT_KEYPRESS: c_int = 1;
pub const EMSCRIPTEN_EVENT_KEYDOWN: c_int = 2;
pub const EMSCRIPTEN_EVENT_KEYUP: c_int = 3;
pub const EMSCRIPTEN_EVENT_CLICK: c_int = 4;
pub const EMSCRIPTEN_EVENT_MOUSEDOWN: c_int = 5;
pub const EMSCRIPTEN_EVENT_MOUSEUP: c_int = 6;
pub const EMSCRIPTEN_EVENT_DBLCLICK: c_int = 7;
pub const EMSCRIPTEN_EVENT_MOUSEMOVE: c_int = 8;
pub const EMSCRIPTEN_EVENT_WHEEL: c_int = 9;
pub const EMSCRIPTEN_EVENT_RESIZE: c_int = 10;
pub const EMSCRIPTEN_EVENT_SCROLL: c_int = 11;
pub const EMSCRIPTEN_EVENT_BLUR: c_int = 12;
pub const EMSCRIPTEN_EVENT_FOCUS: c_int = 13;
pub const EMSCRIPTEN_EVENT_FOCUSIN: c_int = 14;
pub const EMSCRIPTEN_EVENT_FOCUSOUT: c_int = 15;
pub const EMSCRIPTEN_EVENT_DEVICEORIENTATION: c_int = 16;
pub const EMSCRIPTEN_EVENT_DEVICEMOTION: c_int = 17;
pub const EMSCRIPTEN_EVENT_ORIENTATIONCHANGE: c_int = 18;
pub const EMSCRIPTEN_EVENT_FULLSCREENCHANGE: c_int = 19;
pub const EMSCRIPTEN_EVENT_POINTERLOCKCHANGE: c_int = 20;
pub const EMSCRIPTEN_EVENT_VISIBILITYCHANGE: c_int = 21;
pub const EMSCRIPTEN_EVENT_TOUCHSTART: c_int = 22;
pub const EMSCRIPTEN_EVENT_TOUCHEND: c_int = 23;
pub const EMSCRIPTEN_EVENT_TOUCHMOVE: c_int = 24;
pub const EMSCRIPTEN_EVENT_TOUCHCANCEL: c_int = 25;
pub const EMSCRIPTEN_EVENT_GAMEPADCONNECTED: c_int = 26;
pub const EMSCRIPTEN_EVENT_GAMEPADDISCONNECTED: c_int = 27;
pub const EMSCRIPTEN_EVENT_BEFOREUNLOAD: c_int = 28;
pub const EMSCRIPTEN_EVENT_BATTERYCHARGINGCHANGE: c_int = 29;
pub const EMSCRIPTEN_EVENT_BATTERYLEVELCHANGE: c_int = 30;
pub const EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST: c_int = 31;
pub const EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED: c_int = 32;
pub const EMSCRIPTEN_EVENT_MOUSEENTER: c_int = 33;
pub const EMSCRIPTEN_EVENT_MOUSELEAVE: c_int = 34;
pub const EMSCRIPTEN_EVENT_MOUSEOVER: c_int = 35;
pub const EMSCRIPTEN_EVENT_MOUSEOUT: c_int = 36;
pub const EMSCRIPTEN_EVENT_CANVASRESIZED: c_int = 37;
pub const EMSCRIPTEN_EVENT_POINTERLOCKERROR: c_int = 38;
pub const EM_HTML5_SHORT_STRING_LEN_BYTES: usize = 32;
pub const DOM_KEY_LOCATION_STANDARD: c_ulong = 0x00;
pub const DOM_KEY_LOCATION_LEFT: c_ulong = 0x01;
pub const DOM_KEY_LOCATION_RIGHT: c_ulong = 0x02;
pub const DOM_KEY_LOCATION_NUMPAD: c_ulong = 0x03;
pub type em_callback_func = Option<unsafe extern "C" fn()>;
pub type em_key_callback_func = Option<unsafe extern "C" fn(
eventType: c_int,
keyEvent: *const EmscriptenKeyboardEvent,
userData: *mut c_void) -> EM_BOOL>;
pub type em_mouse_callback_func = Option<unsafe extern "C" fn(
eventType: c_int,
mouseEvent: *const EmscriptenMouseEvent,
userData: *mut c_void) -> EM_BOOL>;
pub type em_pointerlockchange_callback_func = Option<unsafe extern "C" fn(
eventType: c_int,
pointerlockChangeEvent: *const EmscriptenPointerlockChangeEvent,
userData: *mut c_void) -> EM_BOOL>;
pub type em_fullscreenchange_callback_func = Option<unsafe extern "C" fn(
eventType: c_int,
fullscreenChangeEvent: *const EmscriptenFullscreenChangeEvent,
userData: *mut c_void) -> EM_BOOL>;
pub type em_touch_callback_func = Option<unsafe extern "C" fn(
eventType: c_int,
touchEvent: *const EmscriptenTouchEvent,
userData: *mut c_void) -> EM_BOOL>;
#[repr(C)]
pub struct EmscriptenFullscreenChangeEvent {
pub isFullscreen: c_int,
pub fullscreenEnabled: c_int,
pub nodeName: [c_char; 128usize],
pub id: [c_char; 128usize],
pub elementWidth: c_int,
pub elementHeight: c_int,
pub screenWidth: c_int,
pub screenHeight: c_int,
}
#[test]
fn bindgen_test_layout_EmscriptenFullscreenChangeEvent() {
assert_eq!(mem::size_of::<EmscriptenFullscreenChangeEvent>(), 280usize);
assert_eq!(mem::align_of::<EmscriptenFullscreenChangeEvent>(), 4usize);
}
#[repr(C)]
#[derive(Debug, Copy)]
pub struct EmscriptenKeyboardEvent {
pub key: [c_char; 32usize],
pub code: [c_char; 32usize],
pub location: c_ulong,
pub ctrlKey: c_int,
pub shiftKey: c_int,
pub altKey: c_int,
pub metaKey: c_int,
pub repeat: c_int,
pub locale: [c_char; 32usize],
pub charValue: [c_char; 32usize],
pub charCode: c_ulong,
pub keyCode: c_ulong,
pub which: c_ulong,
}
#[test]
fn bindgen_test_layout_EmscriptenKeyboardEvent() {
assert_eq!(mem::size_of::<EmscriptenKeyboardEvent>(), 184usize);
assert_eq!(mem::align_of::<EmscriptenKeyboardEvent>(), 8usize);
}
impl Clone for EmscriptenKeyboardEvent {
fn clone(&self) -> Self { *self }
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct EmscriptenMouseEvent {
pub timestamp: f64,
pub screenX: c_long,
pub screenY: c_long,
pub clientX: c_long,
pub clientY: c_long,
pub ctrlKey: c_int,
pub shiftKey: c_int,
pub altKey: c_int,
pub metaKey: c_int,
pub button: c_ushort,
pub buttons: c_ushort,
pub movementX: c_long,
pub movementY: c_long,
pub targetX: c_long,
pub targetY: c_long,
pub canvasX: c_long,
pub canvasY: c_long,
pub padding: c_long,
}
#[test]
fn bindgen_test_layout_EmscriptenMouseEvent() {
assert_eq!(mem::size_of::<EmscriptenMouseEvent>(), 120usize);
assert_eq!(mem::align_of::<EmscriptenMouseEvent>(), 8usize);
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct EmscriptenTouchPoint {
pub identifier: c_long,
pub screenX: c_long,
pub screenY: c_long,
pub clientX: c_long,
pub clientY: c_long,
pub pageX: c_long,
pub pageY: c_long,
pub isChanged: c_int,
pub onTarget: c_int,
pub targetX: c_long,
pub targetY: c_long,
pub canvasX: c_long,
pub canvasY: c_long,
}
#[test]
fn bindgen_test_layout_EmscriptenTouchPoint() {
assert_eq!(mem::size_of::<EmscriptenTouchPoint>(), 96usize);
assert_eq!(mem::align_of::<EmscriptenTouchPoint>(), 8usize);
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct EmscriptenTouchEvent {
pub numTouches: c_int,
pub ctrlKey: c_int,
pub shiftKey: c_int,
pub altKey: c_int,
pub metaKey: c_int,
pub touches: [EmscriptenTouchPoint; 32usize],
}
#[test]
fn bindgen_test_layout_EmscriptenTouchEvent() {
assert_eq!(mem::size_of::<EmscriptenTouchEvent>(), 3096usize);
assert_eq!(mem::align_of::<EmscriptenTouchEvent>(), 8usize);
}
#[repr(C)]
pub struct EmscriptenPointerlockChangeEvent {
pub isActive: c_int,
pub nodeName: [c_char; 128usize],
pub id: [c_char; 128usize],
}
#[test]
fn bindgen_test_layout_EmscriptenPointerlockChangeEvent() {
assert_eq!(mem::size_of::<EmscriptenPointerlockChangeEvent>(), 260usize);
assert_eq!(mem::align_of::<EmscriptenPointerlockChangeEvent>(), 4usize);
}
extern "C" {
pub fn emscripten_set_canvas_size(
width: c_int, height: c_int)
-> EMSCRIPTEN_RESULT;
pub fn emscripten_get_canvas_size(
width: *mut c_int, height: *mut c_int,
is_fullscreen: *mut c_int)
-> EMSCRIPTEN_RESULT;
pub fn emscripten_set_element_css_size(
target: *const c_char, width: c_double,
height: c_double) -> EMSCRIPTEN_RESULT;
pub fn emscripten_get_element_css_size(
target: *const c_char, width: *mut c_double,
height: *mut c_double) -> EMSCRIPTEN_RESULT;
pub fn emscripten_request_pointerlock(
target: *const c_char, deferUntilInEventHandler: EM_BOOL)
-> EMSCRIPTEN_RESULT;
pub fn emscripten_exit_pointerlock() -> EMSCRIPTEN_RESULT;
pub fn emscripten_request_fullscreen(
target: *const c_char, deferUntilInEventHandler: EM_BOOL)
-> EMSCRIPTEN_RESULT;
pub fn emscripten_exit_fullscreen() -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_keydown_callback(
target: *const c_char, userData: *mut c_void,
useCapture: EM_BOOL, callback: em_key_callback_func)
-> EMSCRIPTEN_RESULT;
pub fn emscripten_set_keyup_callback(
target: *const c_char, userData: *mut c_void,
useCapture: EM_BOOL, callback: em_key_callback_func)
-> EMSCRIPTEN_RESULT;
pub fn emscripten_set_mousemove_callback(
target: *const c_char, user_data: *mut c_void,
use_capture: EM_BOOL, callback: em_mouse_callback_func)
-> EMSCRIPTEN_RESULT;
pub fn emscripten_set_mousedown_callback(
target: *const c_char, user_data: *mut c_void,
use_capture: EM_BOOL, callback: em_mouse_callback_func)
-> EMSCRIPTEN_RESULT;
pub fn emscripten_set_mouseup_callback(
target: *const c_char, user_data: *mut c_void,
use_capture: EM_BOOL, callback: em_mouse_callback_func)
-> EMSCRIPTEN_RESULT;
pub fn emscripten_hide_mouse();
pub fn emscripten_get_device_pixel_ratio() -> f64;
pub fn emscripten_set_pointerlockchange_callback(
target: *const c_char, userData: *mut c_void, useCapture: EM_BOOL,
callback: em_pointerlockchange_callback_func) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_fullscreenchange_callback(
target: *const c_char, userData: *mut c_void, useCapture: EM_BOOL,
callback: em_fullscreenchange_callback_func) -> EMSCRIPTEN_RESULT;
pub fn emscripten_asm_const(code: *const c_char);
pub fn emscripten_set_main_loop(
func: em_callback_func, fps: c_int, simulate_infinite_loop: EM_BOOL);
pub fn emscripten_cancel_main_loop();
pub fn emscripten_set_touchstart_callback(
target: *const c_char, userData: *mut c_void,
useCapture: c_int, callback: em_touch_callback_func)
-> EMSCRIPTEN_RESULT;
pub fn emscripten_set_touchend_callback(
target: *const c_char, userData: *mut c_void,
useCapture: c_int, callback: em_touch_callback_func)
-> EMSCRIPTEN_RESULT;
pub fn emscripten_set_touchmove_callback(
target: *const c_char, userData: *mut c_void,
useCapture: c_int, callback: em_touch_callback_func)
-> EMSCRIPTEN_RESULT;
pub fn emscripten_set_touchcancel_callback(
target: *const c_char, userData: *mut c_void,
useCapture: c_int, callback: em_touch_callback_func)
-> EMSCRIPTEN_RESULT;
}

File diff suppressed because it is too large Load Diff

110
src/platform/ios/ffi.rs Normal file
View File

@@ -0,0 +1,110 @@
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
use std::ffi::CString;
use std::os::raw::*;
use objc::runtime::Object;
pub type id = *mut Object;
pub const nil: id = 0 as id;
pub type CFStringRef = *const c_void;
pub type CFTimeInterval = f64;
pub type Boolean = u32;
pub const kCFRunLoopRunHandledSource: i32 = 4;
#[cfg(target_pointer_width = "32")]
pub type CGFloat = f32;
#[cfg(target_pointer_width = "64")]
pub type CGFloat = f64;
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CGPoint {
pub x: CGFloat,
pub y: CGFloat,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CGRect {
pub origin: CGPoint,
pub size: CGSize,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CGSize {
pub width: CGFloat,
pub height: CGFloat,
}
#[link(name = "UIKit", kind = "framework")]
#[link(name = "CoreFoundation", kind = "framework")]
#[link(name = "GlKit", kind = "framework")]
extern {
pub static kCFRunLoopDefaultMode: CFStringRef;
// int UIApplicationMain ( int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName );
pub fn UIApplicationMain(
argc: c_int,
argv: *const c_char,
principalClassName: id,
delegateClassName: id,
) -> c_int;
// SInt32 CFRunLoopRunInMode ( CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled );
pub fn CFRunLoopRunInMode(
mode: CFStringRef,
seconds: CFTimeInterval,
returnAfterSourceHandled: Boolean,
) -> i32;
}
extern {
pub fn setjmp(env: *mut c_void) -> c_int;
pub fn longjmp(env: *mut c_void, val: c_int) -> !;
}
// values taken from "setjmp.h" header in xcode iPhoneOS/iPhoneSimulator SDK
#[cfg(any(target_arch = "x86_64"))]
pub const JBLEN: usize = (9 * 2) + 3 + 16;
#[cfg(any(target_arch = "x86"))]
pub const JBLEN: usize = 18;
#[cfg(target_arch = "arm")]
pub const JBLEN: usize = 10 + 16 + 2;
#[cfg(target_arch = "aarch64")]
pub const JBLEN: usize = (14 + 8 + 2) * 2;
pub type JmpBuf = [c_int; JBLEN];
pub trait NSString: Sized {
unsafe fn alloc(_: Self) -> id {
msg_send![class!(NSString), alloc]
}
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id;
unsafe fn stringByAppendingString_(self, other: id) -> id;
unsafe fn init_str(self, string: &str) -> Self;
unsafe fn UTF8String(self) -> *const c_char;
}
impl NSString for id {
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id {
msg_send![self, initWithUTF8String:c_string as id]
}
unsafe fn stringByAppendingString_(self, other: id) -> id {
msg_send![self, stringByAppendingString:other]
}
unsafe fn init_str(self, string: &str) -> id {
let cstring = CString::new(string).unwrap();
self.initWithUTF8String_(cstring.as_ptr())
}
unsafe fn UTF8String(self) -> *const c_char {
msg_send![self, UTF8String]
}
}

709
src/platform/ios/mod.rs Normal file
View File

@@ -0,0 +1,709 @@
//! iOS support
//!
//! # Building app
//! To build ios app you will need rustc built for this targets:
//!
//! - armv7-apple-ios
//! - armv7s-apple-ios
//! - i386-apple-ios
//! - aarch64-apple-ios
//! - x86_64-apple-ios
//!
//! Then
//!
//! ```
//! cargo build --target=...
//! ```
//! The simplest way to integrate your app into xcode environment is to build it
//! as a static library. Wrap your main function and export it.
//!
//! ```rust, ignore
//! #[no_mangle]
//! pub extern fn start_winit_app() {
//! start_inner()
//! }
//!
//! fn start_inner() {
//! ...
//! }
//!
//! ```
//!
//! Compile project and then drag resulting .a into Xcode project. Add winit.h to xcode.
//!
//! ```ignore
//! void start_winit_app();
//! ```
//!
//! Use start_winit_app inside your xcode's main function.
//!
//!
//! # App lifecycle and events
//!
//! iOS environment is very different from other platforms and you must be very
//! careful with it's events. Familiarize yourself with
//! [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/).
//!
//!
//! This is how those event are represented in winit:
//!
//! - applicationDidBecomeActive is Focused(true)
//! - applicationWillResignActive is Focused(false)
//! - applicationDidEnterBackground is Suspended(true)
//! - applicationWillEnterForeground is Suspended(false)
//! - applicationWillTerminate is Destroyed
//!
//! Keep in mind that after Destroyed event is received every attempt to draw with
//! opengl will result in segfault.
//!
//! Also note that app will not receive Destroyed event if suspended, it will be SIGKILL'ed
#![cfg(target_os = "ios")]
use std::{fmt, mem, ptr};
use std::cell::RefCell;
use std::collections::VecDeque;
use std::os::raw::*;
use std::sync::Arc;
use objc::declare::ClassDecl;
use objc::runtime::{BOOL, Class, Object, Sel, YES};
use {
CreationError,
Event,
LogicalPosition,
LogicalSize,
MouseCursor,
PhysicalPosition,
PhysicalSize,
WindowAttributes,
WindowEvent,
WindowId as RootEventId,
};
use events::{Touch, TouchPhase};
use window::MonitorId as RootMonitorId;
mod ffi;
use self::ffi::{
CFTimeInterval,
CFRunLoopRunInMode,
CGFloat,
CGPoint,
CGRect,
id,
JBLEN,
JmpBuf,
kCFRunLoopDefaultMode,
kCFRunLoopRunHandledSource,
longjmp,
nil,
NSString,
setjmp,
UIApplicationMain,
};
static mut JMPBUF: Option<Box<JmpBuf>> = None;
pub struct Window {
_events_queue: Arc<RefCell<VecDeque<Event>>>,
delegate_state: Box<DelegateState>,
}
unsafe impl Send for Window {}
unsafe impl Sync for Window {}
#[derive(Debug)]
struct DelegateState {
window: id,
controller: id,
view: id,
size: LogicalSize,
scale: f64,
}
impl DelegateState {
fn new(window: id, controller: id, view: id, size: LogicalSize, scale: f64) -> DelegateState {
DelegateState {
window,
controller,
view,
size,
scale,
}
}
}
impl Drop for DelegateState {
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.window, release];
let _: () = msg_send![self.controller, release];
let _: () = msg_send![self.view, release];
}
}
}
#[derive(Clone)]
pub struct MonitorId;
impl fmt::Debug for MonitorId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct MonitorId {
name: Option<String>,
dimensions: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: f64,
}
let monitor_id_proxy = MonitorId {
name: self.get_name(),
dimensions: self.get_dimensions(),
position: self.get_position(),
hidpi_factor: self.get_hidpi_factor(),
};
monitor_id_proxy.fmt(f)
}
}
impl MonitorId {
#[inline]
pub fn get_uiscreen(&self) -> id {
let class = class!(UIScreen);
unsafe { msg_send![class, mainScreen] }
}
#[inline]
pub fn get_name(&self) -> Option<String> {
Some("Primary".to_string())
}
#[inline]
pub fn get_dimensions(&self) -> PhysicalSize {
let bounds: CGRect = unsafe { msg_send![self.get_uiscreen(), nativeBounds] };
(bounds.size.width as f64, bounds.size.height as f64).into()
}
#[inline]
pub fn get_position(&self) -> PhysicalPosition {
// iOS assumes single screen
(0, 0).into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
let scale: CGFloat = unsafe { msg_send![self.get_uiscreen(), nativeScale] };
scale as f64
}
}
pub struct EventsLoop {
events_queue: Arc<RefCell<VecDeque<Event>>>,
}
#[derive(Clone)]
pub struct EventsLoopProxy;
impl EventsLoop {
pub fn new() -> EventsLoop {
unsafe {
if !msg_send![class!(NSThread), isMainThread] {
panic!("`EventsLoop` can only be created on the main thread on iOS");
}
}
EventsLoop { events_queue: Default::default() }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorId);
rb
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
MonitorId
}
pub fn poll_events<F>(&mut self, mut callback: F)
where F: FnMut(::Event)
{
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
callback(event);
return;
}
unsafe {
// jump hack, so we won't quit on willTerminate event before processing it
assert!(JMPBUF.is_some(), "`EventsLoop::poll_events` must be called after window creation on iOS");
if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 {
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
callback(event);
return;
}
}
}
unsafe {
// run runloop
let seconds: CFTimeInterval = 0.000002;
while CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, 1) == kCFRunLoopRunHandledSource {}
}
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
callback(event)
}
}
pub fn run_forever<F>(&mut self, mut callback: F)
where F: FnMut(::Event) -> ::ControlFlow,
{
// Yeah that's a very bad implementation.
loop {
let mut control_flow = ::ControlFlow::Continue;
self.poll_events(|e| {
if let ::ControlFlow::Break = callback(e) {
control_flow = ::ControlFlow::Break;
}
});
if let ::ControlFlow::Break = control_flow {
break;
}
::std::thread::sleep(::std::time::Duration::from_millis(5));
}
}
pub fn create_proxy(&self) -> EventsLoopProxy {
EventsLoopProxy
}
}
impl EventsLoopProxy {
pub fn wakeup(&self) -> Result<(), ::EventsLoopClosed> {
unimplemented!()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId;
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId
}
}
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub root_view_class: &'static Class,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> Self {
PlatformSpecificWindowBuilderAttributes {
root_view_class: class!(UIView),
}
}
}
// TODO: AFAIK transparency is enabled by default on iOS,
// so to be consistent with other platforms we have to change that.
impl Window {
pub fn new(
ev: &EventsLoop,
_attributes: WindowAttributes,
pl_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, CreationError> {
unsafe {
debug_assert!(mem::size_of_val(&JMPBUF) == mem::size_of::<Box<JmpBuf>>());
assert!(mem::replace(&mut JMPBUF, Some(Box::new([0; JBLEN]))).is_none(), "Only one `Window` is supported on iOS");
}
unsafe {
if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 {
let app_class = class!(UIApplication);
let app: id = msg_send![app_class, sharedApplication];
let delegate: id = msg_send![app, delegate];
let state: *mut c_void = *(&*delegate).get_ivar("winitState");
let mut delegate_state = Box::from_raw(state as *mut DelegateState);
let events_queue = &*ev.events_queue;
(&mut *delegate).set_ivar("eventsQueue", mem::transmute::<_, *mut c_void>(events_queue));
// easiest? way to get access to PlatformSpecificWindowBuilderAttributes to configure the view
let rect: CGRect = msg_send![MonitorId.get_uiscreen(), bounds];
let uiview_class = class!(UIView);
let root_view_class = pl_attributes.root_view_class;
let is_uiview: BOOL = msg_send![root_view_class, isSubclassOfClass:uiview_class];
assert!(is_uiview == YES, "`root_view_class` must inherit from `UIView`");
delegate_state.view = msg_send![root_view_class, alloc];
assert!(!delegate_state.view.is_null(), "Failed to create `UIView` instance");
delegate_state.view = msg_send![delegate_state.view, initWithFrame:rect];
assert!(!delegate_state.view.is_null(), "Failed to initialize `UIView` instance");
let _: () = msg_send![delegate_state.controller, setView:delegate_state.view];
let _: () = msg_send![delegate_state.window, makeKeyAndVisible];
return Ok(Window {
_events_queue: ev.events_queue.clone(),
delegate_state,
});
}
}
create_delegate_class();
start_app();
panic!("Couldn't create `UIApplication`!")
}
#[inline]
pub fn get_uiwindow(&self) -> id {
self.delegate_state.window
}
#[inline]
pub fn get_uiview(&self) -> id {
self.delegate_state.view
}
#[inline]
pub fn set_title(&self, _title: &str) {
// N/A
}
#[inline]
pub fn show(&self) {
// N/A
}
#[inline]
pub fn hide(&self) {
// N/A
}
#[inline]
pub fn get_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn set_position(&self, _position: LogicalPosition) {
// N/A
}
#[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> {
Some(self.delegate_state.size)
}
#[inline]
pub fn get_outer_size(&self) -> Option<LogicalSize> {
self.get_inner_size()
}
#[inline]
pub fn set_inner_size(&self, _size: LogicalSize) {
// N/A
}
#[inline]
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_resizable(&self, _resizable: bool) {
// N/A
}
#[inline]
pub fn set_cursor(&self, _cursor: MouseCursor) {
// N/A
}
#[inline]
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
Err("Cursor grabbing is not possible on iOS.".to_owned())
}
#[inline]
pub fn hide_cursor(&self, _hide: bool) {
// N/A
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
self.delegate_state.scale
}
#[inline]
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
Err("Setting cursor position is not possible on iOS.".to_owned())
}
#[inline]
pub fn set_maximized(&self, _maximized: bool) {
// N/A
// iOS has single screen maximized apps so nothing to do
}
#[inline]
pub fn get_fullscreen(&self) -> Option<RootMonitorId> {
// N/A
// iOS has single screen maximized apps so nothing to do
None
}
#[inline]
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorId>) {
// N/A
// iOS has single screen maximized apps so nothing to do
}
#[inline]
pub fn set_decorations(&self, _decorations: bool) {
// N/A
}
#[inline]
pub fn set_always_on_top(&self, _always_on_top: bool) {
// N/A
}
#[inline]
pub fn set_window_icon(&self, _icon: Option<::Icon>) {
// N/A
}
#[inline]
pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) {
// N/A
}
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorId {
RootMonitorId { inner: MonitorId }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorId);
rb
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
MonitorId
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId
}
}
fn create_delegate_class() {
extern fn did_finish_launching(this: &mut Object, _: Sel, _: id, _: id) -> BOOL {
let screen_class = class!(UIScreen);
let window_class = class!(UIWindow);
let controller_class = class!(UIViewController);
unsafe {
let main_screen: id = msg_send![screen_class, mainScreen];
let bounds: CGRect = msg_send![main_screen, bounds];
let scale: CGFloat = msg_send![main_screen, nativeScale];
let window: id = msg_send![window_class, alloc];
let window: id = msg_send![window, initWithFrame:bounds.clone()];
let size = (bounds.size.width as f64, bounds.size.height as f64).into();
let view_controller: id = msg_send![controller_class, alloc];
let view_controller: id = msg_send![view_controller, init];
let _: () = msg_send![window, setRootViewController:view_controller];
let state = Box::new(DelegateState::new(window, view_controller, ptr::null_mut(), size, scale as f64));
let state_ptr: *mut DelegateState = mem::transmute(state);
this.set_ivar("winitState", state_ptr as *mut c_void);
// The `UIView` is setup in `Window::new` which gets `longjmp`'ed to here.
// This makes it easier to configure the specific `UIView` type.
let _: () = msg_send![this, performSelector:sel!(postLaunch:) withObject:nil afterDelay:0.0];
}
YES
}
extern fn post_launch(_: &Object, _: Sel, _: id) {
unsafe { longjmp(mem::transmute_copy(&mut JMPBUF), 1); }
}
extern fn did_become_active(this: &Object, _: Sel, _: id) {
unsafe {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Focused(true),
});
}
}
extern fn will_resign_active(this: &Object, _: Sel, _: id) {
unsafe {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Focused(false),
});
}
}
extern fn will_enter_foreground(this: &Object, _: Sel, _: id) {
unsafe {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::Suspended(false));
}
}
extern fn did_enter_background(this: &Object, _: Sel, _: id) {
unsafe {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::Suspended(true));
}
}
extern fn will_terminate(this: &Object, _: Sel, _: id) {
unsafe {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
// push event to the front to garantee that we'll process it
// immidiatly after jump
events_queue.borrow_mut().push_front(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Destroyed,
});
longjmp(mem::transmute_copy(&mut JMPBUF), 1);
}
}
extern fn handle_touches(this: &Object, _: Sel, touches: id, _:id) {
unsafe {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
let touches_enum: id = msg_send![touches, objectEnumerator];
loop {
let touch: id = msg_send![touches_enum, nextObject];
if touch == nil {
break
}
let location: CGPoint = msg_send![touch, locationInView:nil];
let touch_id = touch as u64;
let phase: i32 = msg_send![touch, phase];
events_queue.borrow_mut().push_back(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Touch(Touch {
device_id: DEVICE_ID,
id: touch_id,
location: (location.x as f64, location.y as f64).into(),
phase: match phase {
0 => TouchPhase::Started,
1 => TouchPhase::Moved,
// 2 is UITouchPhaseStationary and is not expected here
3 => TouchPhase::Ended,
4 => TouchPhase::Cancelled,
_ => panic!("unexpected touch phase: {:?}", phase)
}
}),
});
}
}
}
let ui_responder = class!(UIResponder);
let mut decl = ClassDecl::new("AppDelegate", ui_responder).expect("Failed to declare class `AppDelegate`");
unsafe {
decl.add_method(sel!(application:didFinishLaunchingWithOptions:),
did_finish_launching as extern fn(&mut Object, Sel, id, id) -> BOOL);
decl.add_method(sel!(applicationDidBecomeActive:),
did_become_active as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationWillResignActive:),
will_resign_active as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationWillEnterForeground:),
will_enter_foreground as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationDidEnterBackground:),
did_enter_background as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationWillTerminate:),
will_terminate as extern fn(&Object, Sel, id));
decl.add_method(sel!(touchesBegan:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(touchesMoved:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(touchesEnded:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(touchesCancelled:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(postLaunch:),
post_launch as extern fn(&Object, Sel, id));
decl.add_ivar::<*mut c_void>("winitState");
decl.add_ivar::<*mut c_void>("eventsQueue");
decl.register();
}
}
#[inline]
fn start_app() {
unsafe {
UIApplicationMain(0, ptr::null(), nil, NSString::alloc(nil).init_str("AppDelegate"));
}
}
// Constant device ID, to be removed when this backend is updated to report real device IDs.
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);

View File

@@ -0,0 +1,15 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
#![allow(dead_code)]
use std::os::raw::{c_void, c_char, c_int};
pub const RTLD_LAZY: c_int = 0x001;
pub const RTLD_NOW: c_int = 0x002;
#[link(name = "dl")]
extern {
pub fn dlopen(filename: *const c_char, flag: c_int) -> *mut c_void;
pub fn dlerror() -> *mut c_char;
pub fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
pub fn dlclose(handle: *mut c_void) -> c_int;
}

551
src/platform/linux/mod.rs Normal file
View File

@@ -0,0 +1,551 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
use std::collections::VecDeque;
use std::{env, mem};
use std::ffi::CStr;
use std::os::raw::*;
use std::sync::Arc;
use parking_lot::Mutex;
use sctk::reexports::client::ConnectError;
use {
CreationError,
EventsLoopClosed,
Icon,
MouseCursor,
ControlFlow,
WindowAttributes,
};
use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
use window::MonitorId as RootMonitorId;
use self::x11::{XConnection, XError};
use self::x11::ffi::XVisualInfo;
pub use self::x11::XNotSupported;
mod dlopen;
pub mod wayland;
pub mod x11;
/// Environment variable specifying which backend should be used on unix platform.
///
/// Legal values are x11 and wayland. If this variable is set only the named backend
/// will be tried by winit. If it is not set, winit will try to connect to a wayland connection,
/// and if it fails will fallback on x11.
///
/// If this variable is set with any other value, winit will panic.
const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
#[derive(Clone, Default)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub visual_infos: Option<XVisualInfo>,
pub screen_id: Option<i32>,
pub resize_increments: Option<(u32, u32)>,
pub base_size: Option<(u32, u32)>,
pub class: Option<(String, String)>,
pub override_redirect: bool,
pub x11_window_type: x11::util::WindowType,
pub gtk_theme_variant: Option<String>,
pub app_id: Option<String>
}
lazy_static!(
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> = {
Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new))
};
);
pub enum Window {
X(x11::Window),
Wayland(wayland::Window),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum WindowId {
X(x11::WindowId),
Wayland(wayland::WindowId),
}
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId::X(x11::WindowId::dummy())
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DeviceId {
X(x11::DeviceId),
Wayland(wayland::DeviceId),
}
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId::X(x11::DeviceId::dummy())
}
}
#[derive(Debug, Clone)]
pub enum MonitorId {
X(x11::MonitorId),
Wayland(wayland::MonitorId),
}
impl MonitorId {
#[inline]
pub fn get_name(&self) -> Option<String> {
match self {
&MonitorId::X(ref m) => m.get_name(),
&MonitorId::Wayland(ref m) => m.get_name(),
}
}
#[inline]
pub fn get_native_identifier(&self) -> u32 {
match self {
&MonitorId::X(ref m) => m.get_native_identifier(),
&MonitorId::Wayland(ref m) => m.get_native_identifier(),
}
}
#[inline]
pub fn get_dimensions(&self) -> PhysicalSize {
match self {
&MonitorId::X(ref m) => m.get_dimensions(),
&MonitorId::Wayland(ref m) => m.get_dimensions(),
}
}
#[inline]
pub fn get_position(&self) -> PhysicalPosition {
match self {
&MonitorId::X(ref m) => m.get_position(),
&MonitorId::Wayland(ref m) => m.get_position(),
}
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
match self {
&MonitorId::X(ref m) => m.get_hidpi_factor(),
&MonitorId::Wayland(ref m) => m.get_hidpi_factor() as f64,
}
}
}
impl Window {
#[inline]
pub fn new(
events_loop: &EventsLoop,
attribs: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
) -> Result<Self, CreationError> {
match *events_loop {
EventsLoop::Wayland(ref events_loop) => {
wayland::Window::new(events_loop, attribs, pl_attribs).map(Window::Wayland)
},
EventsLoop::X(ref events_loop) => {
x11::Window::new(events_loop, attribs, pl_attribs).map(Window::X)
},
}
}
#[inline]
pub fn id(&self) -> WindowId {
match self {
&Window::X(ref w) => WindowId::X(w.id()),
&Window::Wayland(ref w) => WindowId::Wayland(w.id()),
}
}
#[inline]
pub fn set_title(&self, title: &str) {
match self {
&Window::X(ref w) => w.set_title(title),
&Window::Wayland(ref w) => w.set_title(title),
}
}
#[inline]
pub fn show(&self) {
match self {
&Window::X(ref w) => w.show(),
&Window::Wayland(ref w) => w.show(),
}
}
#[inline]
pub fn hide(&self) {
match self {
&Window::X(ref w) => w.hide(),
&Window::Wayland(ref w) => w.hide(),
}
}
#[inline]
pub fn get_position(&self) -> Option<LogicalPosition> {
match self {
&Window::X(ref w) => w.get_position(),
&Window::Wayland(ref w) => w.get_position(),
}
}
#[inline]
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
match self {
&Window::X(ref m) => m.get_inner_position(),
&Window::Wayland(ref m) => m.get_inner_position(),
}
}
#[inline]
pub fn set_position(&self, position: LogicalPosition) {
match self {
&Window::X(ref w) => w.set_position(position),
&Window::Wayland(ref w) => w.set_position(position),
}
}
#[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> {
match self {
&Window::X(ref w) => w.get_inner_size(),
&Window::Wayland(ref w) => w.get_inner_size(),
}
}
#[inline]
pub fn get_outer_size(&self) -> Option<LogicalSize> {
match self {
&Window::X(ref w) => w.get_outer_size(),
&Window::Wayland(ref w) => w.get_outer_size(),
}
}
#[inline]
pub fn set_inner_size(&self, size: LogicalSize) {
match self {
&Window::X(ref w) => w.set_inner_size(size),
&Window::Wayland(ref w) => w.set_inner_size(size),
}
}
#[inline]
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
match self {
&Window::X(ref w) => w.set_min_dimensions(dimensions),
&Window::Wayland(ref w) => w.set_min_dimensions(dimensions),
}
}
#[inline]
pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) {
match self {
&Window::X(ref w) => w.set_max_dimensions(dimensions),
&Window::Wayland(ref w) => w.set_max_dimensions(dimensions),
}
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
match self {
&Window::X(ref w) => w.set_resizable(resizable),
&Window::Wayland(ref w) => w.set_resizable(resizable),
}
}
#[inline]
pub fn set_cursor(&self, cursor: MouseCursor) {
match self {
&Window::X(ref w) => w.set_cursor(cursor),
&Window::Wayland(ref w) => w.set_cursor(cursor)
}
}
#[inline]
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
match self {
&Window::X(ref window) => window.grab_cursor(grab),
&Window::Wayland(ref window) => window.grab_cursor(grab),
}
}
#[inline]
pub fn hide_cursor(&self, hide: bool) {
match self {
&Window::X(ref window) => window.hide_cursor(hide),
&Window::Wayland(ref window) => window.hide_cursor(hide),
}
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
match self {
&Window::X(ref w) => w.get_hidpi_factor(),
&Window::Wayland(ref w) => w.hidpi_factor() as f64,
}
}
#[inline]
pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> {
match self {
&Window::X(ref w) => w.set_cursor_position(position),
&Window::Wayland(ref w) => w.set_cursor_position(position),
}
}
#[inline]
pub fn set_maximized(&self, maximized: bool) {
match self {
&Window::X(ref w) => w.set_maximized(maximized),
&Window::Wayland(ref w) => w.set_maximized(maximized),
}
}
#[inline]
pub fn get_fullscreen(&self) -> Option<RootMonitorId> {
match self {
&Window::X(ref w) => w.get_fullscreen(),
&Window::Wayland(ref w) => w.get_fullscreen()
.map(|monitor_id| RootMonitorId { inner: MonitorId::Wayland(monitor_id) })
}
}
#[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
match self {
&Window::X(ref w) => w.set_fullscreen(monitor),
&Window::Wayland(ref w) => w.set_fullscreen(monitor)
}
}
#[inline]
pub fn set_decorations(&self, decorations: bool) {
match self {
&Window::X(ref w) => w.set_decorations(decorations),
&Window::Wayland(ref w) => w.set_decorations(decorations)
}
}
#[inline]
pub fn set_always_on_top(&self, always_on_top: bool) {
match self {
&Window::X(ref w) => w.set_always_on_top(always_on_top),
&Window::Wayland(_) => (),
}
}
#[inline]
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
match self {
&Window::X(ref w) => w.set_window_icon(window_icon),
&Window::Wayland(_) => (),
}
}
#[inline]
pub fn set_ime_spot(&self, position: LogicalPosition) {
match self {
&Window::X(ref w) => w.set_ime_spot(position),
&Window::Wayland(_) => (),
}
}
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorId {
match self {
&Window::X(ref window) => RootMonitorId { inner: MonitorId::X(window.get_current_monitor()) },
&Window::Wayland(ref window) => RootMonitorId { inner: MonitorId::Wayland(window.get_current_monitor()) },
}
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
match self {
&Window::X(ref window) => window.get_available_monitors()
.into_iter()
.map(MonitorId::X)
.collect(),
&Window::Wayland(ref window) => window.get_available_monitors()
.into_iter()
.map(MonitorId::Wayland)
.collect(),
}
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
match self {
&Window::X(ref window) => MonitorId::X(window.get_primary_monitor()),
&Window::Wayland(ref window) => MonitorId::Wayland(window.get_primary_monitor()),
}
}
}
unsafe extern "C" fn x_error_callback(
display: *mut x11::ffi::Display,
event: *mut x11::ffi::XErrorEvent,
) -> c_int {
let xconn_lock = X11_BACKEND.lock();
if let Ok(ref xconn) = *xconn_lock {
let mut buf: [c_char; 1024] = mem::uninitialized();
(xconn.xlib.XGetErrorText)(
display,
(*event).error_code as c_int,
buf.as_mut_ptr(),
buf.len() as c_int,
);
let description = CStr::from_ptr(buf.as_ptr()).to_string_lossy();
let error = XError {
description: description.into_owned(),
error_code: (*event).error_code,
request_code: (*event).request_code,
minor_code: (*event).minor_code,
};
error!("X11 error: {:#?}", error);
*xconn.latest_error.lock() = Some(error);
}
// Fun fact: this return value is completely ignored.
0
}
pub enum EventsLoop {
Wayland(wayland::EventsLoop),
X(x11::EventsLoop)
}
#[derive(Clone)]
pub enum EventsLoopProxy {
X(x11::EventsLoopProxy),
Wayland(wayland::EventsLoopProxy),
}
impl EventsLoop {
pub fn new() -> EventsLoop {
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
match env_var.as_str() {
"x11" => {
// TODO: propagate
return EventsLoop::new_x11().expect("Failed to initialize X11 backend");
},
"wayland" => {
return EventsLoop::new_wayland()
.expect("Failed to initialize Wayland backend");
},
_ => panic!(
"Unknown environment variable value for {}, try one of `x11`,`wayland`",
BACKEND_PREFERENCE_ENV_VAR,
),
}
}
let wayland_err = match EventsLoop::new_wayland() {
Ok(event_loop) => return event_loop,
Err(err) => err,
};
let x11_err = match EventsLoop::new_x11() {
Ok(event_loop) => return event_loop,
Err(err) => err,
};
let err_string = format!(
"Failed to initialize any backend! Wayland status: {:?} X11 status: {:?}",
wayland_err,
x11_err,
);
panic!(err_string);
}
pub fn new_wayland() -> Result<EventsLoop, ConnectError> {
wayland::EventsLoop::new()
.map(EventsLoop::Wayland)
}
pub fn new_x11() -> Result<EventsLoop, XNotSupported> {
X11_BACKEND
.lock()
.as_ref()
.map(Arc::clone)
.map(x11::EventsLoop::new)
.map(EventsLoop::X)
.map_err(|err| err.clone())
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
match *self {
EventsLoop::Wayland(ref evlp) => evlp
.get_available_monitors()
.into_iter()
.map(MonitorId::Wayland)
.collect(),
EventsLoop::X(ref evlp) => evlp
.x_connection()
.get_available_monitors()
.into_iter()
.map(MonitorId::X)
.collect(),
}
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
match *self {
EventsLoop::Wayland(ref evlp) => MonitorId::Wayland(evlp.get_primary_monitor()),
EventsLoop::X(ref evlp) => MonitorId::X(evlp.x_connection().get_primary_monitor()),
}
}
pub fn create_proxy(&self) -> EventsLoopProxy {
match *self {
EventsLoop::Wayland(ref evlp) => EventsLoopProxy::Wayland(evlp.create_proxy()),
EventsLoop::X(ref evlp) => EventsLoopProxy::X(evlp.create_proxy()),
}
}
pub fn poll_events<F>(&mut self, callback: F)
where F: FnMut(::Event)
{
match *self {
EventsLoop::Wayland(ref mut evlp) => evlp.poll_events(callback),
EventsLoop::X(ref mut evlp) => evlp.poll_events(callback)
}
}
pub fn run_forever<F>(&mut self, callback: F)
where F: FnMut(::Event) -> ControlFlow
{
match *self {
EventsLoop::Wayland(ref mut evlp) => evlp.run_forever(callback),
EventsLoop::X(ref mut evlp) => evlp.run_forever(callback)
}
}
#[inline]
pub fn is_wayland(&self) -> bool {
match *self {
EventsLoop::Wayland(_) => true,
EventsLoop::X(_) => false,
}
}
#[inline]
pub fn x_connection(&self) -> Option<&Arc<XConnection>> {
match *self {
EventsLoop::Wayland(_) => None,
EventsLoop::X(ref ev) => Some(ev.x_connection()),
}
}
}
impl EventsLoopProxy {
pub fn wakeup(&self) -> Result<(), EventsLoopClosed> {
match *self {
EventsLoopProxy::Wayland(ref proxy) => proxy.wakeup(),
EventsLoopProxy::X(ref proxy) => proxy.wakeup(),
}
}
}

View File

@@ -0,0 +1,538 @@
use std::cell::RefCell;
use std::collections::VecDeque;
use std::fmt;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, Weak};
use {ControlFlow, EventsLoopClosed, PhysicalPosition, PhysicalSize};
use super::window::WindowStore;
use super::WindowId;
use sctk::output::OutputMgr;
use sctk::reexports::client::protocol::{
wl_keyboard, wl_output, wl_pointer, wl_registry, wl_seat, wl_touch,
};
use sctk::reexports::client::{ConnectError, Display, EventQueue, GlobalEvent, Proxy};
use sctk::Environment;
use sctk::reexports::client::protocol::wl_display::RequestsTrait as DisplayRequests;
use sctk::reexports::client::protocol::wl_surface::RequestsTrait;
use ModifiersState;
pub struct EventsLoopSink {
buffer: VecDeque<::Event>,
}
impl EventsLoopSink {
pub fn new() -> EventsLoopSink {
EventsLoopSink {
buffer: VecDeque::new(),
}
}
pub fn send_event(&mut self, evt: ::WindowEvent, wid: WindowId) {
let evt = ::Event::WindowEvent {
event: evt,
window_id: ::WindowId(::platform::WindowId::Wayland(wid)),
};
self.buffer.push_back(evt);
}
pub fn send_raw_event(&mut self, evt: ::Event) {
self.buffer.push_back(evt);
}
fn empty_with<F>(&mut self, callback: &mut F)
where
F: FnMut(::Event),
{
for evt in self.buffer.drain(..) {
callback(evt)
}
}
}
pub struct EventsLoop {
// The Event Queue
pub evq: RefCell<EventQueue>,
// our sink, shared with some handlers, buffering the events
sink: Arc<Mutex<EventsLoopSink>>,
// Whether or not there is a pending `Awakened` event to be emitted.
pending_wakeup: Arc<AtomicBool>,
// The window store
pub store: Arc<Mutex<WindowStore>>,
// the env
pub env: Environment,
// a cleanup switch to prune dead windows
pub cleanup_needed: Arc<Mutex<bool>>,
// The wayland display
pub display: Arc<Display>,
// The list of seats
pub seats: Arc<Mutex<Vec<(u32, Proxy<wl_seat::WlSeat>)>>>,
}
// A handle that can be sent across threads and used to wake up the `EventsLoop`.
//
// We should only try and wake up the `EventsLoop` if it still exists, so we hold Weak ptrs.
#[derive(Clone)]
pub struct EventsLoopProxy {
display: Weak<Display>,
pending_wakeup: Weak<AtomicBool>,
}
impl EventsLoopProxy {
// Causes the `EventsLoop` to stop blocking on `run_forever` and emit an `Awakened` event.
//
// Returns `Err` if the associated `EventsLoop` no longer exists.
pub fn wakeup(&self) -> Result<(), EventsLoopClosed> {
let display = self.display.upgrade();
let wakeup = self.pending_wakeup.upgrade();
match (display, wakeup) {
(Some(display), Some(wakeup)) => {
// Update the `EventsLoop`'s `pending_wakeup` flag.
wakeup.store(true, Ordering::Relaxed);
// Cause the `EventsLoop` to break from `dispatch` if it is currently blocked.
let _ = display.sync(|callback| callback.implement(|_, _| {}, ()));
display.flush().map_err(|_| EventsLoopClosed)?;
Ok(())
}
_ => Err(EventsLoopClosed),
}
}
}
impl EventsLoop {
pub fn new() -> Result<EventsLoop, ConnectError> {
let (display, mut event_queue) = Display::connect_to_env()?;
let display = Arc::new(display);
let pending_wakeup = Arc::new(AtomicBool::new(false));
let sink = Arc::new(Mutex::new(EventsLoopSink::new()));
let store = Arc::new(Mutex::new(WindowStore::new()));
let seats = Arc::new(Mutex::new(Vec::new()));
let mut seat_manager = SeatManager {
sink: sink.clone(),
store: store.clone(),
seats: seats.clone(),
events_loop_proxy: EventsLoopProxy {
display: Arc::downgrade(&display),
pending_wakeup: Arc::downgrade(&pending_wakeup),
},
};
let env = Environment::from_display_with_cb(
&display,
&mut event_queue,
move |event, registry| {
match event {
GlobalEvent::New { id, ref interface, version } => {
if interface == "wl_seat" {
seat_manager.add_seat(id, version, registry)
}
},
GlobalEvent::Removed { id, ref interface } => {
if interface == "wl_seat" {
seat_manager.remove_seat(id)
}
},
}
},
).unwrap();
Ok(EventsLoop {
display,
evq: RefCell::new(event_queue),
sink,
pending_wakeup,
store,
env,
cleanup_needed: Arc::new(Mutex::new(false)),
seats,
})
}
pub fn create_proxy(&self) -> EventsLoopProxy {
EventsLoopProxy {
display: Arc::downgrade(&self.display),
pending_wakeup: Arc::downgrade(&self.pending_wakeup),
}
}
pub fn poll_events<F>(&mut self, mut callback: F)
where
F: FnMut(::Event),
{
// send pending events to the server
self.display.flush().expect("Wayland connection lost.");
// dispatch any pre-buffered events
self.sink.lock().unwrap().empty_with(&mut callback);
// try to read pending events
if let Some(h) = self.evq.get_mut().prepare_read() {
h.read_events().expect("Wayland connection lost.");
}
// dispatch wayland events
self.evq
.get_mut()
.dispatch_pending()
.expect("Wayland connection lost.");
self.post_dispatch_triggers();
// dispatch buffered events to client
self.sink.lock().unwrap().empty_with(&mut callback);
}
pub fn run_forever<F>(&mut self, mut callback: F)
where
F: FnMut(::Event) -> ControlFlow,
{
// send pending events to the server
self.display.flush().expect("Wayland connection lost.");
// Check for control flow by wrapping the callback.
let control_flow = ::std::cell::Cell::new(ControlFlow::Continue);
let mut callback = |event| {
if let ControlFlow::Break = callback(event) {
control_flow.set(ControlFlow::Break);
}
};
// dispatch any pre-buffered events
self.post_dispatch_triggers();
self.sink.lock().unwrap().empty_with(&mut callback);
loop {
// dispatch events blocking if needed
self.evq
.get_mut()
.dispatch()
.expect("Wayland connection lost.");
self.post_dispatch_triggers();
// empty buffer of events
self.sink.lock().unwrap().empty_with(&mut callback);
if let ControlFlow::Break = control_flow.get() {
break;
}
}
}
pub fn get_primary_monitor(&self) -> MonitorId {
get_primary_monitor(&self.env.outputs)
}
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
get_available_monitors(&self.env.outputs)
}
pub fn get_display(&self) -> &Display {
&*self.display
}
}
/*
* Private EventsLoop Internals
*/
impl EventsLoop {
fn post_dispatch_triggers(&mut self) {
let mut sink = self.sink.lock().unwrap();
// process a possible pending wakeup call
if self.pending_wakeup.load(Ordering::Relaxed) {
sink.send_raw_event(::Event::Awakened);
self.pending_wakeup.store(false, Ordering::Relaxed);
}
// prune possible dead windows
{
let mut cleanup_needed = self.cleanup_needed.lock().unwrap();
if *cleanup_needed {
let pruned = self.store.lock().unwrap().cleanup();
*cleanup_needed = false;
for wid in pruned {
sink.send_event(::WindowEvent::Destroyed, wid);
}
}
}
// process pending resize/refresh
self.store.lock().unwrap().for_each(
|newsize, size, new_dpi, refresh, frame_refresh, closed, wid, frame| {
if let Some(frame) = frame {
if let Some((w, h)) = newsize {
frame.resize(w, h);
frame.refresh();
let logical_size = ::LogicalSize::new(w as f64, h as f64);
sink.send_event(::WindowEvent::Resized(logical_size), wid);
*size = (w, h);
} else if frame_refresh {
frame.refresh();
if !refresh {
frame.surface().commit()
}
}
}
if let Some(dpi) = new_dpi {
sink.send_event(::WindowEvent::HiDpiFactorChanged(dpi as f64), wid);
}
if refresh {
sink.send_event(::WindowEvent::Refresh, wid);
}
if closed {
sink.send_event(::WindowEvent::CloseRequested, wid);
}
},
)
}
}
/*
* Wayland protocol implementations
*/
struct SeatManager {
sink: Arc<Mutex<EventsLoopSink>>,
store: Arc<Mutex<WindowStore>>,
seats: Arc<Mutex<Vec<(u32, Proxy<wl_seat::WlSeat>)>>>,
events_loop_proxy: EventsLoopProxy,
}
impl SeatManager {
fn add_seat(&mut self, id: u32, version: u32, registry: Proxy<wl_registry::WlRegistry>) {
use self::wl_registry::RequestsTrait as RegistryRequests;
use std::cmp::min;
let mut seat_data = SeatData {
sink: self.sink.clone(),
store: self.store.clone(),
pointer: None,
keyboard: None,
touch: None,
events_loop_proxy: self.events_loop_proxy.clone(),
modifiers_tracker: Arc::new(Mutex::new(ModifiersState::default())),
};
let seat = registry
.bind(min(version, 5), id, move |seat| {
seat.implement(move |event, seat| {
seat_data.receive(event, seat)
}, ())
})
.unwrap();
self.store.lock().unwrap().new_seat(&seat);
self.seats.lock().unwrap().push((id, seat));
}
fn remove_seat(&mut self, id: u32) {
use self::wl_seat::RequestsTrait as SeatRequests;
let mut seats = self.seats.lock().unwrap();
if let Some(idx) = seats.iter().position(|&(i, _)| i == id) {
let (_, seat) = seats.swap_remove(idx);
if seat.version() >= 5 {
seat.release();
}
}
}
}
struct SeatData {
sink: Arc<Mutex<EventsLoopSink>>,
store: Arc<Mutex<WindowStore>>,
pointer: Option<Proxy<wl_pointer::WlPointer>>,
keyboard: Option<Proxy<wl_keyboard::WlKeyboard>>,
touch: Option<Proxy<wl_touch::WlTouch>>,
events_loop_proxy: EventsLoopProxy,
modifiers_tracker: Arc<Mutex<ModifiersState>>,
}
impl SeatData {
fn receive(&mut self, evt: wl_seat::Event, seat: Proxy<wl_seat::WlSeat>) {
match evt {
wl_seat::Event::Name { .. } => (),
wl_seat::Event::Capabilities { capabilities } => {
// create pointer if applicable
if capabilities.contains(wl_seat::Capability::Pointer) && self.pointer.is_none() {
self.pointer = Some(super::pointer::implement_pointer(
&seat,
self.sink.clone(),
self.store.clone(),
self.modifiers_tracker.clone(),
))
}
// destroy pointer if applicable
if !capabilities.contains(wl_seat::Capability::Pointer) {
if let Some(pointer) = self.pointer.take() {
if pointer.version() >= 3 {
use self::wl_pointer::RequestsTrait;
pointer.release();
}
}
}
// create keyboard if applicable
if capabilities.contains(wl_seat::Capability::Keyboard) && self.keyboard.is_none() {
self.keyboard = Some(super::keyboard::init_keyboard(
&seat,
self.sink.clone(),
self.events_loop_proxy.clone(),
self.modifiers_tracker.clone(),
))
}
// destroy keyboard if applicable
if !capabilities.contains(wl_seat::Capability::Keyboard) {
if let Some(kbd) = self.keyboard.take() {
if kbd.version() >= 3 {
use self::wl_keyboard::RequestsTrait;
kbd.release();
}
}
}
// create touch if applicable
if capabilities.contains(wl_seat::Capability::Touch) && self.touch.is_none() {
self.touch = Some(super::touch::implement_touch(
&seat,
self.sink.clone(),
self.store.clone(),
))
}
// destroy touch if applicable
if !capabilities.contains(wl_seat::Capability::Touch) {
if let Some(touch) = self.touch.take() {
if touch.version() >= 3 {
use self::wl_touch::RequestsTrait;
touch.release();
}
}
}
}
}
}
}
impl Drop for SeatData {
fn drop(&mut self) {
if let Some(pointer) = self.pointer.take() {
if pointer.version() >= 3 {
use self::wl_pointer::RequestsTrait;
pointer.release();
}
}
if let Some(kbd) = self.keyboard.take() {
if kbd.version() >= 3 {
use self::wl_keyboard::RequestsTrait;
kbd.release();
}
}
if let Some(touch) = self.touch.take() {
if touch.version() >= 3 {
use self::wl_touch::RequestsTrait;
touch.release();
}
}
}
}
/*
* Monitor stuff
*/
pub struct MonitorId {
pub(crate) proxy: Proxy<wl_output::WlOutput>,
pub(crate) mgr: OutputMgr,
}
impl Clone for MonitorId {
fn clone(&self) -> MonitorId {
MonitorId {
proxy: self.proxy.clone(),
mgr: self.mgr.clone(),
}
}
}
impl fmt::Debug for MonitorId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct MonitorId {
name: Option<String>,
native_identifier: u32,
dimensions: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: i32,
}
let monitor_id_proxy = MonitorId {
name: self.get_name(),
native_identifier: self.get_native_identifier(),
dimensions: self.get_dimensions(),
position: self.get_position(),
hidpi_factor: self.get_hidpi_factor(),
};
monitor_id_proxy.fmt(f)
}
}
impl MonitorId {
pub fn get_name(&self) -> Option<String> {
self.mgr.with_info(&self.proxy, |_, info| {
format!("{} ({})", info.model, info.make)
})
}
#[inline]
pub fn get_native_identifier(&self) -> u32 {
self.mgr.with_info(&self.proxy, |id, _| id).unwrap_or(0)
}
pub fn get_dimensions(&self) -> PhysicalSize {
match self.mgr.with_info(&self.proxy, |_, info| {
info.modes
.iter()
.find(|m| m.is_current)
.map(|m| m.dimensions)
}) {
Some(Some((w, h))) => (w as u32, h as u32),
_ => (0, 0),
}.into()
}
pub fn get_position(&self) -> PhysicalPosition {
self.mgr
.with_info(&self.proxy, |_, info| info.location)
.unwrap_or((0, 0))
.into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> i32 {
self.mgr
.with_info(&self.proxy, |_, info| info.scale_factor)
.unwrap_or(1)
}
}
pub fn get_primary_monitor(outputs: &OutputMgr) -> MonitorId {
outputs.with_all(|list| {
if let Some(&(_, ref proxy, _)) = list.first() {
MonitorId {
proxy: proxy.clone(),
mgr: outputs.clone(),
}
} else {
panic!("No monitor is available.")
}
})
}
pub fn get_available_monitors(outputs: &OutputMgr) -> VecDeque<MonitorId> {
outputs.with_all(|list| {
list.iter()
.map(|&(_, ref proxy, _)| MonitorId {
proxy: proxy.clone(),
mgr: outputs.clone(),
})
.collect()
})
}

View File

@@ -0,0 +1,370 @@
use std::sync::{Arc, Mutex};
use super::{make_wid, DeviceId, EventsLoopProxy, EventsLoopSink};
use sctk::keyboard::{
self, map_keyboard_auto_with_repeat, Event as KbEvent, KeyRepeatEvent, KeyRepeatKind,
};
use sctk::reexports::client::protocol::wl_keyboard;
use sctk::reexports::client::Proxy;
use sctk::reexports::client::protocol::wl_seat;
use sctk::reexports::client::protocol::wl_seat::RequestsTrait as SeatRequests;
use {ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent};
pub fn init_keyboard(
seat: &Proxy<wl_seat::WlSeat>,
sink: Arc<Mutex<EventsLoopSink>>,
events_loop_proxy: EventsLoopProxy,
modifiers_tracker: Arc<Mutex<ModifiersState>>,
) -> Proxy<wl_keyboard::WlKeyboard> {
// { variables to be captured by the closures
let target = Arc::new(Mutex::new(None));
let my_sink = sink.clone();
let repeat_sink = sink.clone();
let repeat_target = target.clone();
let my_modifiers = modifiers_tracker.clone();
// }
let ret = map_keyboard_auto_with_repeat(
seat,
KeyRepeatKind::System,
move |evt: KbEvent, _| match evt {
KbEvent::Enter { surface, .. } => {
let wid = make_wid(&surface);
my_sink
.lock()
.unwrap()
.send_event(WindowEvent::Focused(true), wid);
*target.lock().unwrap() = Some(wid);
}
KbEvent::Leave { surface, .. } => {
let wid = make_wid(&surface);
my_sink
.lock()
.unwrap()
.send_event(WindowEvent::Focused(false), wid);
*target.lock().unwrap() = None;
}
KbEvent::Key {
rawkey,
keysym,
state,
utf8,
..
} => {
if let Some(wid) = *target.lock().unwrap() {
let state = match state {
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
wl_keyboard::KeyState::Released => ElementState::Released,
};
let vkcode = key_to_vkey(rawkey, keysym);
let mut guard = my_sink.lock().unwrap();
guard.send_event(
WindowEvent::KeyboardInput {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
input: KeyboardInput {
state: state,
scancode: rawkey,
virtual_keycode: vkcode,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
},
wid,
);
// send char event only on key press, not release
if let ElementState::Released = state {
return;
}
if let Some(txt) = utf8 {
for chr in txt.chars() {
guard.send_event(WindowEvent::ReceivedCharacter(chr), wid);
}
}
}
}
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
KbEvent::Modifiers { modifiers: event_modifiers } => {
*modifiers_tracker.lock().unwrap() = event_modifiers.into()
}
},
move |repeat_event: KeyRepeatEvent, _| {
if let Some(wid) = *repeat_target.lock().unwrap() {
let state = ElementState::Pressed;
let vkcode = key_to_vkey(repeat_event.rawkey, repeat_event.keysym);
let mut guard = repeat_sink.lock().unwrap();
guard.send_event(
WindowEvent::KeyboardInput {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
input: KeyboardInput {
state: state,
scancode: repeat_event.rawkey,
virtual_keycode: vkcode,
modifiers: my_modifiers.lock().unwrap().clone(),
},
},
wid,
);
if let Some(txt) = repeat_event.utf8 {
for chr in txt.chars() {
guard.send_event(WindowEvent::ReceivedCharacter(chr), wid);
}
}
events_loop_proxy.wakeup().unwrap();
}
},
);
match ret {
Ok(keyboard) => keyboard,
Err(_) => {
// This is a fallback impl if libxkbcommon was not available
// This case should probably never happen, as most wayland
// compositors _need_ libxkbcommon anyway...
//
// In this case, we don't have the keymap information (it is
// supposed to be serialized by the compositor using libxkbcommon)
// { variables to be captured by the closure
let mut target = None;
let my_sink = sink;
// }
seat.get_keyboard(|keyboard| {
keyboard.implement(move |evt, _| match evt {
wl_keyboard::Event::Enter { surface, .. } => {
let wid = make_wid(&surface);
my_sink
.lock()
.unwrap()
.send_event(WindowEvent::Focused(true), wid);
target = Some(wid);
}
wl_keyboard::Event::Leave { surface, .. } => {
let wid = make_wid(&surface);
my_sink
.lock()
.unwrap()
.send_event(WindowEvent::Focused(false), wid);
target = None;
}
wl_keyboard::Event::Key { key, state, .. } => {
if let Some(wid) = target {
let state = match state {
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
wl_keyboard::KeyState::Released => ElementState::Released,
};
my_sink.lock().unwrap().send_event(
WindowEvent::KeyboardInput {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
input: KeyboardInput {
state: state,
scancode: key,
virtual_keycode: None,
modifiers: ModifiersState::default(),
},
},
wid,
);
}
}
_ => (),
}, ())
}).unwrap()
}
}
}
fn key_to_vkey(rawkey: u32, keysym: u32) -> Option<VirtualKeyCode> {
match rawkey {
1 => Some(VirtualKeyCode::Escape),
2 => Some(VirtualKeyCode::Key1),
3 => Some(VirtualKeyCode::Key2),
4 => Some(VirtualKeyCode::Key3),
5 => Some(VirtualKeyCode::Key4),
6 => Some(VirtualKeyCode::Key5),
7 => Some(VirtualKeyCode::Key6),
8 => Some(VirtualKeyCode::Key7),
9 => Some(VirtualKeyCode::Key8),
10 => Some(VirtualKeyCode::Key9),
11 => Some(VirtualKeyCode::Key0),
_ => keysym_to_vkey(keysym),
}
}
fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
use sctk::keyboard::keysyms;
match keysym {
// letters
keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(VirtualKeyCode::A),
keysyms::XKB_KEY_B | keysyms::XKB_KEY_b => Some(VirtualKeyCode::B),
keysyms::XKB_KEY_C | keysyms::XKB_KEY_c => Some(VirtualKeyCode::C),
keysyms::XKB_KEY_D | keysyms::XKB_KEY_d => Some(VirtualKeyCode::D),
keysyms::XKB_KEY_E | keysyms::XKB_KEY_e => Some(VirtualKeyCode::E),
keysyms::XKB_KEY_F | keysyms::XKB_KEY_f => Some(VirtualKeyCode::F),
keysyms::XKB_KEY_G | keysyms::XKB_KEY_g => Some(VirtualKeyCode::G),
keysyms::XKB_KEY_H | keysyms::XKB_KEY_h => Some(VirtualKeyCode::H),
keysyms::XKB_KEY_I | keysyms::XKB_KEY_i => Some(VirtualKeyCode::I),
keysyms::XKB_KEY_J | keysyms::XKB_KEY_j => Some(VirtualKeyCode::J),
keysyms::XKB_KEY_K | keysyms::XKB_KEY_k => Some(VirtualKeyCode::K),
keysyms::XKB_KEY_L | keysyms::XKB_KEY_l => Some(VirtualKeyCode::L),
keysyms::XKB_KEY_M | keysyms::XKB_KEY_m => Some(VirtualKeyCode::M),
keysyms::XKB_KEY_N | keysyms::XKB_KEY_n => Some(VirtualKeyCode::N),
keysyms::XKB_KEY_O | keysyms::XKB_KEY_o => Some(VirtualKeyCode::O),
keysyms::XKB_KEY_P | keysyms::XKB_KEY_p => Some(VirtualKeyCode::P),
keysyms::XKB_KEY_Q | keysyms::XKB_KEY_q => Some(VirtualKeyCode::Q),
keysyms::XKB_KEY_R | keysyms::XKB_KEY_r => Some(VirtualKeyCode::R),
keysyms::XKB_KEY_S | keysyms::XKB_KEY_s => Some(VirtualKeyCode::S),
keysyms::XKB_KEY_T | keysyms::XKB_KEY_t => Some(VirtualKeyCode::T),
keysyms::XKB_KEY_U | keysyms::XKB_KEY_u => Some(VirtualKeyCode::U),
keysyms::XKB_KEY_V | keysyms::XKB_KEY_v => Some(VirtualKeyCode::V),
keysyms::XKB_KEY_W | keysyms::XKB_KEY_w => Some(VirtualKeyCode::W),
keysyms::XKB_KEY_X | keysyms::XKB_KEY_x => Some(VirtualKeyCode::X),
keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(VirtualKeyCode::Y),
keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(VirtualKeyCode::Z),
// F--
keysyms::XKB_KEY_F1 => Some(VirtualKeyCode::F1),
keysyms::XKB_KEY_F2 => Some(VirtualKeyCode::F2),
keysyms::XKB_KEY_F3 => Some(VirtualKeyCode::F3),
keysyms::XKB_KEY_F4 => Some(VirtualKeyCode::F4),
keysyms::XKB_KEY_F5 => Some(VirtualKeyCode::F5),
keysyms::XKB_KEY_F6 => Some(VirtualKeyCode::F6),
keysyms::XKB_KEY_F7 => Some(VirtualKeyCode::F7),
keysyms::XKB_KEY_F8 => Some(VirtualKeyCode::F8),
keysyms::XKB_KEY_F9 => Some(VirtualKeyCode::F9),
keysyms::XKB_KEY_F10 => Some(VirtualKeyCode::F10),
keysyms::XKB_KEY_F11 => Some(VirtualKeyCode::F11),
keysyms::XKB_KEY_F12 => Some(VirtualKeyCode::F12),
keysyms::XKB_KEY_F13 => Some(VirtualKeyCode::F13),
keysyms::XKB_KEY_F14 => Some(VirtualKeyCode::F14),
keysyms::XKB_KEY_F15 => Some(VirtualKeyCode::F15),
keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16),
keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17),
keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18),
keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19),
keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20),
keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21),
keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22),
keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23),
keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24),
// flow control
keysyms::XKB_KEY_Print => Some(VirtualKeyCode::Snapshot),
keysyms::XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll),
keysyms::XKB_KEY_Pause => Some(VirtualKeyCode::Pause),
keysyms::XKB_KEY_Insert => Some(VirtualKeyCode::Insert),
keysyms::XKB_KEY_Home => Some(VirtualKeyCode::Home),
keysyms::XKB_KEY_Delete => Some(VirtualKeyCode::Delete),
keysyms::XKB_KEY_End => Some(VirtualKeyCode::End),
keysyms::XKB_KEY_Page_Down => Some(VirtualKeyCode::PageDown),
keysyms::XKB_KEY_Page_Up => Some(VirtualKeyCode::PageUp),
// arrows
keysyms::XKB_KEY_Left => Some(VirtualKeyCode::Left),
keysyms::XKB_KEY_Up => Some(VirtualKeyCode::Up),
keysyms::XKB_KEY_Right => Some(VirtualKeyCode::Right),
keysyms::XKB_KEY_Down => Some(VirtualKeyCode::Down),
//
keysyms::XKB_KEY_BackSpace => Some(VirtualKeyCode::Back),
keysyms::XKB_KEY_Return => Some(VirtualKeyCode::Return),
keysyms::XKB_KEY_space => Some(VirtualKeyCode::Space),
// keypad
keysyms::XKB_KEY_Num_Lock => Some(VirtualKeyCode::Numlock),
keysyms::XKB_KEY_KP_0 => Some(VirtualKeyCode::Numpad0),
keysyms::XKB_KEY_KP_1 => Some(VirtualKeyCode::Numpad1),
keysyms::XKB_KEY_KP_2 => Some(VirtualKeyCode::Numpad2),
keysyms::XKB_KEY_KP_3 => Some(VirtualKeyCode::Numpad3),
keysyms::XKB_KEY_KP_4 => Some(VirtualKeyCode::Numpad4),
keysyms::XKB_KEY_KP_5 => Some(VirtualKeyCode::Numpad5),
keysyms::XKB_KEY_KP_6 => Some(VirtualKeyCode::Numpad6),
keysyms::XKB_KEY_KP_7 => Some(VirtualKeyCode::Numpad7),
keysyms::XKB_KEY_KP_8 => Some(VirtualKeyCode::Numpad8),
keysyms::XKB_KEY_KP_9 => Some(VirtualKeyCode::Numpad9),
// misc
// => Some(VirtualKeyCode::AbntC1),
// => Some(VirtualKeyCode::AbntC2),
keysyms::XKB_KEY_plus => Some(VirtualKeyCode::Add),
keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe),
// => Some(VirtualKeyCode::Apps),
// => Some(VirtualKeyCode::At),
// => Some(VirtualKeyCode::Ax),
keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash),
// => Some(VirtualKeyCode::Calculator),
// => Some(VirtualKeyCode::Capital),
keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon),
keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma),
// => Some(VirtualKeyCode::Convert),
// => Some(VirtualKeyCode::Decimal),
// => Some(VirtualKeyCode::Divide),
keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals),
// => Some(VirtualKeyCode::Grave),
// => Some(VirtualKeyCode::Kana),
// => Some(VirtualKeyCode::Kanji),
keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt),
// => Some(VirtualKeyCode::LBracket),
keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl),
keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift),
// => Some(VirtualKeyCode::LWin),
// => Some(VirtualKeyCode::Mail),
// => Some(VirtualKeyCode::MediaSelect),
// => Some(VirtualKeyCode::MediaStop),
keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus),
keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Multiply),
// => Some(VirtualKeyCode::Mute),
// => Some(VirtualKeyCode::MyComputer),
// => Some(VirtualKeyCode::NextTrack),
// => Some(VirtualKeyCode::NoConvert),
keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma),
keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter),
keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals),
keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::Add),
keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::Subtract),
keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::Divide),
keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp),
keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown),
keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home),
keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End),
// => Some(VirtualKeyCode::OEM102),
// => Some(VirtualKeyCode::Period),
// => Some(VirtualKeyCode::Playpause),
// => Some(VirtualKeyCode::Power),
// => Some(VirtualKeyCode::Prevtrack),
keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt),
// => Some(VirtualKeyCode::RBracket),
keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl),
keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift),
// => Some(VirtualKeyCode::RWin),
keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon),
keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash),
// => Some(VirtualKeyCode::Sleep),
// => Some(VirtualKeyCode::Stop),
// => Some(VirtualKeyCode::Subtract),
// => Some(VirtualKeyCode::Sysrq),
keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab),
keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab),
// => Some(VirtualKeyCode::Underline),
// => Some(VirtualKeyCode::Unlabeled),
keysyms::XKB_KEY_XF86AudioLowerVolume => Some(VirtualKeyCode::VolumeDown),
keysyms::XKB_KEY_XF86AudioRaiseVolume => Some(VirtualKeyCode::VolumeUp),
// => Some(VirtualKeyCode::Wake),
// => Some(VirtualKeyCode::Webback),
// => Some(VirtualKeyCode::WebFavorites),
// => Some(VirtualKeyCode::WebForward),
// => Some(VirtualKeyCode::WebHome),
// => Some(VirtualKeyCode::WebRefresh),
// => Some(VirtualKeyCode::WebSearch),
// => Some(VirtualKeyCode::WebStop),
// => Some(VirtualKeyCode::Yen),
keysyms::XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy),
keysyms::XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste),
keysyms::XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut),
// fallback
_ => None,
}
}
impl From<keyboard::ModifiersState> for ModifiersState {
fn from(mods: keyboard::ModifiersState) -> ModifiersState {
ModifiersState {
shift: mods.shift,
ctrl: mods.ctrl,
alt: mods.alt,
logo: mods.logo,
}
}
}

View File

@@ -0,0 +1,25 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd",
target_os = "netbsd", target_os = "openbsd"))]
pub use self::window::Window;
pub use self::event_loop::{EventsLoop, EventsLoopProxy, EventsLoopSink, MonitorId};
use sctk::reexports::client::protocol::wl_surface;
use sctk::reexports::client::Proxy;
mod event_loop;
mod pointer;
mod touch;
mod keyboard;
mod window;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(usize);
#[inline]
fn make_wid(s: &Proxy<wl_surface::WlSurface>) -> WindowId {
WindowId(s.c_ptr() as usize)
}

View File

@@ -0,0 +1,189 @@
use std::sync::{Arc, Mutex};
use {ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent};
use events::ModifiersState;
use super::DeviceId;
use super::event_loop::EventsLoopSink;
use super::window::WindowStore;
use sctk::reexports::client::Proxy;
use sctk::reexports::client::protocol::wl_pointer::{self, Event as PtrEvent, WlPointer};
use sctk::reexports::client::protocol::wl_seat;
use sctk::reexports::client::protocol::wl_seat::RequestsTrait as SeatRequests;
pub fn implement_pointer(
seat: &Proxy<wl_seat::WlSeat>,
sink: Arc<Mutex<EventsLoopSink>>,
store: Arc<Mutex<WindowStore>>,
modifiers_tracker: Arc<Mutex<ModifiersState>>,
) -> Proxy<WlPointer> {
let mut mouse_focus = None;
let mut axis_buffer = None;
let mut axis_discrete_buffer = None;
let mut axis_state = TouchPhase::Ended;
seat.get_pointer(|pointer| {
pointer.implement(move |evt, pointer| {
let mut sink = sink.lock().unwrap();
let store = store.lock().unwrap();
match evt {
PtrEvent::Enter {
surface,
surface_x,
surface_y,
..
} => {
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
mouse_focus = Some(wid);
sink.send_event(
WindowEvent::CursorEntered {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
},
wid,
);
sink.send_event(
WindowEvent::CursorMoved {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
position: (surface_x, surface_y).into(),
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
}
}
PtrEvent::Leave { surface, .. } => {
mouse_focus = None;
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
sink.send_event(
WindowEvent::CursorLeft {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
},
wid,
);
}
}
PtrEvent::Motion {
surface_x,
surface_y,
..
} => {
if let Some(wid) = mouse_focus {
sink.send_event(
WindowEvent::CursorMoved {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
position: (surface_x, surface_y).into(),
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
}
}
PtrEvent::Button { button, state, .. } => {
if let Some(wid) = mouse_focus {
let state = match state {
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
wl_pointer::ButtonState::Released => ElementState::Released,
};
let button = match button {
0x110 => MouseButton::Left,
0x111 => MouseButton::Right,
0x112 => MouseButton::Middle,
// TODO figure out the translation ?
_ => return,
};
sink.send_event(
WindowEvent::MouseInput {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
state: state,
button: button,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
}
}
PtrEvent::Axis { axis, value, .. } => {
if let Some(wid) = mouse_focus {
if pointer.version() < 5 {
let (mut x, mut y) = (0.0, 0.0);
// old seat compatibility
match axis {
// wayland vertical sign convention is the inverse of winit
wl_pointer::Axis::VerticalScroll => y -= value as f32,
wl_pointer::Axis::HorizontalScroll => x += value as f32,
}
sink.send_event(
WindowEvent::MouseWheel {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
delta: MouseScrollDelta::PixelDelta((x as f64, y as f64).into()),
phase: TouchPhase::Moved,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
} else {
let (mut x, mut y) = axis_buffer.unwrap_or((0.0, 0.0));
match axis {
// wayland vertical sign convention is the inverse of winit
wl_pointer::Axis::VerticalScroll => y -= value as f32,
wl_pointer::Axis::HorizontalScroll => x += value as f32,
}
axis_buffer = Some((x, y));
axis_state = match axis_state {
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
_ => TouchPhase::Started,
}
}
}
}
PtrEvent::Frame => {
let axis_buffer = axis_buffer.take();
let axis_discrete_buffer = axis_discrete_buffer.take();
if let Some(wid) = mouse_focus {
if let Some((x, y)) = axis_discrete_buffer {
sink.send_event(
WindowEvent::MouseWheel {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
delta: MouseScrollDelta::LineDelta(x as f32, y as f32),
phase: axis_state,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
} else if let Some((x, y)) = axis_buffer {
sink.send_event(
WindowEvent::MouseWheel {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
delta: MouseScrollDelta::PixelDelta((x as f64, y as f64).into()),
phase: axis_state,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
}
}
}
PtrEvent::AxisSource { .. } => (),
PtrEvent::AxisStop { .. } => {
axis_state = TouchPhase::Ended;
}
PtrEvent::AxisDiscrete { axis, discrete } => {
let (mut x, mut y) = axis_discrete_buffer.unwrap_or((0, 0));
match axis {
// wayland vertical sign convention is the inverse of winit
wl_pointer::Axis::VerticalScroll => y -= discrete,
wl_pointer::Axis::HorizontalScroll => x += discrete,
}
axis_discrete_buffer = Some((x, y));
axis_state = match axis_state {
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
_ => TouchPhase::Started,
}
}
}
}, ())
}).unwrap()
}

View File

@@ -0,0 +1,97 @@
use std::sync::{Arc, Mutex};
use {TouchPhase, WindowEvent};
use super::{DeviceId, WindowId};
use super::event_loop::EventsLoopSink;
use super::window::WindowStore;
use sctk::reexports::client::Proxy;
use sctk::reexports::client::protocol::wl_touch::{Event as TouchEvent, WlTouch};
use sctk::reexports::client::protocol::wl_seat;
use sctk::reexports::client::protocol::wl_seat::RequestsTrait as SeatRequests;
struct TouchPoint {
wid: WindowId,
location: (f64, f64),
id: i32,
}
pub(crate) fn implement_touch(
seat: &Proxy<wl_seat::WlSeat>,
sink: Arc<Mutex<EventsLoopSink>>,
store: Arc<Mutex<WindowStore>>,
) -> Proxy<WlTouch> {
let mut pending_ids = Vec::new();
seat.get_touch(|touch| {
touch.implement(move |evt, _| {
let mut sink = sink.lock().unwrap();
let store = store.lock().unwrap();
match evt {
TouchEvent::Down {
surface, id, x, y, ..
} => {
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
sink.send_event(
WindowEvent::Touch(::Touch {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
phase: TouchPhase::Started,
location: (x, y).into(),
id: id as u64,
}),
wid,
);
pending_ids.push(TouchPoint {
wid: wid,
location: (x, y),
id: id,
});
}
}
TouchEvent::Up { id, .. } => {
let idx = pending_ids.iter().position(|p| p.id == id);
if let Some(idx) = idx {
let pt = pending_ids.remove(idx);
sink.send_event(
WindowEvent::Touch(::Touch {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
phase: TouchPhase::Ended,
location: pt.location.into(),
id: id as u64,
}),
pt.wid,
);
}
}
TouchEvent::Motion { id, x, y, .. } => {
let pt = pending_ids.iter_mut().find(|p| p.id == id);
if let Some(pt) = pt {
pt.location = (x, y);
sink.send_event(
WindowEvent::Touch(::Touch {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
phase: TouchPhase::Moved,
location: (x, y).into(),
id: id as u64,
}),
pt.wid,
);
}
}
TouchEvent::Frame => (),
TouchEvent::Cancel => for pt in pending_ids.drain(..) {
sink.send_event(
WindowEvent::Touch(::Touch {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
phase: TouchPhase::Cancelled,
location: pt.location.into(),
id: pt.id as u64,
}),
pt.wid,
);
},
}
}, ())
}).unwrap()
}

View File

@@ -0,0 +1,406 @@
use std::collections::VecDeque;
use std::sync::{Arc, Mutex, Weak};
use {CreationError, MouseCursor, WindowAttributes};
use dpi::{LogicalPosition, LogicalSize};
use platform::{MonitorId as PlatformMonitorId, PlatformSpecificWindowBuilderAttributes as PlAttributes};
use window::MonitorId as RootMonitorId;
use sctk::surface::{get_dpi_factor, get_outputs};
use sctk::window::{ConceptFrame, Event as WEvent, State as WState, Window as SWindow, Theme};
use sctk::reexports::client::{Display, Proxy};
use sctk::reexports::client::protocol::{wl_seat, wl_surface};
use sctk::reexports::client::protocol::wl_surface::RequestsTrait as SurfaceRequests;
use sctk::output::OutputMgr;
use super::{make_wid, EventsLoop, MonitorId, WindowId};
use platform::platform::wayland::event_loop::{get_available_monitors, get_primary_monitor};
pub struct Window {
surface: Proxy<wl_surface::WlSurface>,
frame: Arc<Mutex<SWindow<ConceptFrame>>>,
outputs: OutputMgr, // Access to info for all monitors
size: Arc<Mutex<(u32, u32)>>,
kill_switch: (Arc<Mutex<bool>>, Arc<Mutex<bool>>),
display: Arc<Display>,
need_frame_refresh: Arc<Mutex<bool>>,
fullscreen: Arc<Mutex<bool>>,
}
impl Window {
pub fn new(evlp: &EventsLoop, attributes: WindowAttributes, pl_attribs: PlAttributes) -> Result<Window, CreationError> {
let (width, height) = attributes.dimensions.map(Into::into).unwrap_or((800, 600));
// Create the window
let size = Arc::new(Mutex::new((width, height)));
let fullscreen = Arc::new(Mutex::new(false));
let window_store = evlp.store.clone();
let surface = evlp.env.create_surface(move |dpi, surface| {
window_store.lock().unwrap().dpi_change(&surface, dpi);
surface.set_buffer_scale(dpi);
});
let window_store = evlp.store.clone();
let my_surface = surface.clone();
let mut frame = SWindow::<ConceptFrame>::init_from_env(
&evlp.env,
surface.clone(),
(width, height),
move |event| match event {
WEvent::Configure { new_size, states } => {
let mut store = window_store.lock().unwrap();
let is_fullscreen = states.contains(&WState::Fullscreen);
for window in &mut store.windows {
if window.surface.equals(&my_surface) {
window.newsize = new_size;
window.need_refresh = true;
*(window.fullscreen.lock().unwrap()) = is_fullscreen;
*(window.need_frame_refresh.lock().unwrap()) = true;
return;
}
}
}
WEvent::Refresh => {
let store = window_store.lock().unwrap();
for window in &store.windows {
if window.surface.equals(&my_surface) {
*(window.need_frame_refresh.lock().unwrap()) = true;
return;
}
}
}
WEvent::Close => {
let mut store = window_store.lock().unwrap();
for window in &mut store.windows {
if window.surface.equals(&my_surface) {
window.closed = true;
return;
}
}
}
},
).unwrap();
if let Some(app_id) = pl_attribs.app_id {
frame.set_app_id(app_id);
}
frame.set_title(attributes.title);
for &(_, ref seat) in evlp.seats.lock().unwrap().iter() {
frame.new_seat(seat);
}
// Check for fullscreen requirements
if let Some(RootMonitorId {
inner: PlatformMonitorId::Wayland(ref monitor_id),
}) = attributes.fullscreen
{
frame.set_fullscreen(Some(&monitor_id.proxy));
} else if attributes.maximized {
frame.set_maximized();
}
frame.set_resizable(attributes.resizable);
// set decorations
frame.set_decorate(attributes.decorations);
// min-max dimensions
frame.set_min_size(attributes.min_dimensions.map(Into::into));
frame.set_max_size(attributes.max_dimensions.map(Into::into));
let kill_switch = Arc::new(Mutex::new(false));
let need_frame_refresh = Arc::new(Mutex::new(true));
let frame = Arc::new(Mutex::new(frame));
evlp.store.lock().unwrap().windows.push(InternalWindow {
closed: false,
newsize: None,
size: size.clone(),
fullscreen: fullscreen.clone(),
need_refresh: false,
need_frame_refresh: need_frame_refresh.clone(),
surface: surface.clone(),
kill_switch: kill_switch.clone(),
frame: Arc::downgrade(&frame),
current_dpi: 1,
new_dpi: None,
});
evlp.evq.borrow_mut().sync_roundtrip().unwrap();
Ok(Window {
display: evlp.display.clone(),
surface: surface,
frame: frame,
outputs: evlp.env.outputs.clone(),
size: size,
kill_switch: (kill_switch, evlp.cleanup_needed.clone()),
need_frame_refresh: need_frame_refresh,
fullscreen: fullscreen,
})
}
#[inline]
pub fn id(&self) -> WindowId {
make_wid(&self.surface)
}
pub fn set_title(&self, title: &str) {
self.frame.lock().unwrap().set_title(title.into());
}
#[inline]
pub fn show(&self) {
// TODO
}
#[inline]
pub fn hide(&self) {
// TODO
}
#[inline]
pub fn get_position(&self) -> Option<LogicalPosition> {
// Not possible with wayland
None
}
#[inline]
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
// Not possible with wayland
None
}
#[inline]
pub fn set_position(&self, _pos: LogicalPosition) {
// Not possible with wayland
}
pub fn get_inner_size(&self) -> Option<LogicalSize> {
Some(self.size.lock().unwrap().clone().into())
}
#[inline]
pub fn get_outer_size(&self) -> Option<LogicalSize> {
let (w, h) = self.size.lock().unwrap().clone();
// let (w, h) = super::wayland_window::add_borders(w as i32, h as i32);
Some((w, h).into())
}
#[inline]
// NOTE: This will only resize the borders, the contents must be updated by the user
pub fn set_inner_size(&self, size: LogicalSize) {
let (w, h) = size.into();
self.frame.lock().unwrap().resize(w, h);
*(self.size.lock().unwrap()) = (w, h);
}
#[inline]
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
self.frame.lock().unwrap().set_min_size(dimensions.map(Into::into));
}
#[inline]
pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) {
self.frame.lock().unwrap().set_max_size(dimensions.map(Into::into));
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
self.frame.lock().unwrap().set_resizable(resizable);
}
#[inline]
pub fn hidpi_factor(&self) -> i32 {
get_dpi_factor(&self.surface)
}
pub fn set_decorations(&self, decorate: bool) {
self.frame.lock().unwrap().set_decorate(decorate);
*(self.need_frame_refresh.lock().unwrap()) = true;
}
pub fn set_maximized(&self, maximized: bool) {
if maximized {
self.frame.lock().unwrap().set_maximized();
} else {
self.frame.lock().unwrap().unset_maximized();
}
}
pub fn get_fullscreen(&self) -> Option<MonitorId> {
if *(self.fullscreen.lock().unwrap()) {
Some(self.get_current_monitor())
} else {
None
}
}
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
if let Some(RootMonitorId {
inner: PlatformMonitorId::Wayland(ref monitor_id),
}) = monitor
{
self.frame
.lock()
.unwrap()
.set_fullscreen(Some(&monitor_id.proxy));
} else {
self.frame.lock().unwrap().unset_fullscreen();
}
}
pub fn set_theme<T: Theme>(&self, theme: T) {
self.frame.lock().unwrap().set_theme(theme)
}
#[inline]
pub fn set_cursor(&self, _cursor: MouseCursor) {
// TODO
}
#[inline]
pub fn hide_cursor(&self, _hide: bool) {
// TODO: This isn't possible on Wayland yet
}
#[inline]
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
Err("Cursor grabbing is not yet possible on Wayland.".to_owned())
}
#[inline]
pub fn set_cursor_position(&self, _pos: LogicalPosition) -> Result<(), String> {
Err("Setting the cursor position is not yet possible on Wayland.".to_owned())
}
pub fn get_display(&self) -> &Display {
&*self.display
}
pub fn get_surface(&self) -> &Proxy<wl_surface::WlSurface> {
&self.surface
}
pub fn get_current_monitor(&self) -> MonitorId {
let output = get_outputs(&self.surface).last().unwrap().clone();
MonitorId {
proxy: output,
mgr: self.outputs.clone(),
}
}
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
get_available_monitors(&self.outputs)
}
pub fn get_primary_monitor(&self) -> MonitorId {
get_primary_monitor(&self.outputs)
}
}
impl Drop for Window {
fn drop(&mut self) {
*(self.kill_switch.0.lock().unwrap()) = true;
*(self.kill_switch.1.lock().unwrap()) = true;
}
}
/*
* Internal store for windows
*/
struct InternalWindow {
surface: Proxy<wl_surface::WlSurface>,
newsize: Option<(u32, u32)>,
size: Arc<Mutex<(u32, u32)>>,
fullscreen: Arc<Mutex<bool>>,
need_refresh: bool,
need_frame_refresh: Arc<Mutex<bool>>,
closed: bool,
kill_switch: Arc<Mutex<bool>>,
frame: Weak<Mutex<SWindow<ConceptFrame>>>,
current_dpi: i32,
new_dpi: Option<i32>,
}
pub struct WindowStore {
windows: Vec<InternalWindow>,
}
impl WindowStore {
pub fn new() -> WindowStore {
WindowStore {
windows: Vec::new(),
}
}
pub fn find_wid(&self, surface: &Proxy<wl_surface::WlSurface>) -> Option<WindowId> {
for window in &self.windows {
if surface.equals(&window.surface) {
return Some(make_wid(surface));
}
}
None
}
pub fn cleanup(&mut self) -> Vec<WindowId> {
let mut pruned = Vec::new();
self.windows.retain(|w| {
if *w.kill_switch.lock().unwrap() {
// window is dead, cleanup
pruned.push(make_wid(&w.surface));
w.surface.destroy();
false
} else {
true
}
});
pruned
}
pub fn new_seat(&self, seat: &Proxy<wl_seat::WlSeat>) {
for window in &self.windows {
if let Some(w) = window.frame.upgrade() {
w.lock().unwrap().new_seat(seat);
}
}
}
fn dpi_change(&mut self, surface: &Proxy<wl_surface::WlSurface>, new: i32) {
for window in &mut self.windows {
if surface.equals(&window.surface) {
window.new_dpi = Some(new);
}
}
}
pub fn for_each<F>(&mut self, mut f: F)
where
F: FnMut(Option<(u32, u32)>, &mut (u32, u32), Option<i32>, bool, bool, bool, WindowId, Option<&mut SWindow<ConceptFrame>>),
{
for window in &mut self.windows {
let opt_arc = window.frame.upgrade();
let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap());
f(
window.newsize.take(),
&mut *(window.size.lock().unwrap()),
window.new_dpi,
window.need_refresh,
::std::mem::replace(&mut *window.need_frame_refresh.lock().unwrap(), false),
window.closed,
make_wid(&window.surface),
opt_mutex_lock.as_mut().map(|m| &mut **m),
);
if let Some(dpi) = window.new_dpi.take() {
window.current_dpi = dpi;
}
window.need_refresh = false;
// avoid re-spamming the event
window.closed = false;
}
}
}

View File

@@ -0,0 +1,219 @@
use std::io;
use std::sync::Arc;
use std::path::{Path, PathBuf};
use std::str::Utf8Error;
use std::os::raw::*;
use percent_encoding::percent_decode;
use super::{ffi, util, XConnection, XError};
#[derive(Debug)]
pub struct DndAtoms {
pub aware: ffi::Atom,
pub enter: ffi::Atom,
pub leave: ffi::Atom,
pub drop: ffi::Atom,
pub position: ffi::Atom,
pub status: ffi::Atom,
pub action_private: ffi::Atom,
pub selection: ffi::Atom,
pub finished: ffi::Atom,
pub type_list: ffi::Atom,
pub uri_list: ffi::Atom,
pub none: ffi::Atom,
}
impl DndAtoms {
pub fn new(xconn: &Arc<XConnection>) -> Result<Self, XError> {
let names = [
b"XdndAware\0".as_ptr() as *mut c_char,
b"XdndEnter\0".as_ptr() as *mut c_char,
b"XdndLeave\0".as_ptr() as *mut c_char,
b"XdndDrop\0".as_ptr() as *mut c_char,
b"XdndPosition\0".as_ptr() as *mut c_char,
b"XdndStatus\0".as_ptr() as *mut c_char,
b"XdndActionPrivate\0".as_ptr() as *mut c_char,
b"XdndSelection\0".as_ptr() as *mut c_char,
b"XdndFinished\0".as_ptr() as *mut c_char,
b"XdndTypeList\0".as_ptr() as *mut c_char,
b"text/uri-list\0".as_ptr() as *mut c_char,
b"None\0".as_ptr() as *mut c_char,
];
let atoms = unsafe { xconn.get_atoms(&names) }?;
Ok(DndAtoms {
aware: atoms[0],
enter: atoms[1],
leave: atoms[2],
drop: atoms[3],
position: atoms[4],
status: atoms[5],
action_private: atoms[6],
selection: atoms[7],
finished: atoms[8],
type_list: atoms[9],
uri_list: atoms[10],
none: atoms[11],
})
}
}
#[derive(Debug, Clone, Copy)]
pub enum DndState {
Accepted,
Rejected,
}
#[derive(Debug)]
pub enum DndDataParseError {
EmptyData,
InvalidUtf8(Utf8Error),
HostnameSpecified(String),
UnexpectedProtocol(String),
UnresolvablePath(io::Error),
}
impl From<Utf8Error> for DndDataParseError {
fn from(e: Utf8Error) -> Self {
DndDataParseError::InvalidUtf8(e)
}
}
impl From<io::Error> for DndDataParseError {
fn from(e: io::Error) -> Self {
DndDataParseError::UnresolvablePath(e)
}
}
pub struct Dnd {
xconn: Arc<XConnection>,
pub atoms: DndAtoms,
// Populated by XdndEnter event handler
pub version: Option<c_long>,
pub type_list: Option<Vec<c_ulong>>,
// Populated by XdndPosition event handler
pub source_window: Option<c_ulong>,
// Populated by SelectionNotify event handler (triggered by XdndPosition event handler)
pub result: Option<Result<Vec<PathBuf>, DndDataParseError>>,
}
impl Dnd {
pub fn new(xconn: Arc<XConnection>) -> Result<Self, XError> {
let atoms = DndAtoms::new(&xconn)?;
Ok(Dnd {
xconn,
atoms,
version: None,
type_list: None,
source_window: None,
result: None,
})
}
pub fn reset(&mut self) {
self.version = None;
self.type_list = None;
self.source_window = None;
self.result = None;
}
pub unsafe fn send_status(
&self,
this_window: c_ulong,
target_window: c_ulong,
state: DndState,
) -> Result<(), XError> {
let (accepted, action) = match state {
DndState::Accepted => (1, self.atoms.action_private as c_long),
DndState::Rejected => (0, self.atoms.none as c_long),
};
self.xconn.send_client_msg(
target_window,
target_window,
self.atoms.status,
None,
[this_window as c_long, accepted, 0, 0, action],
).flush()
}
pub unsafe fn send_finished(
&self,
this_window: c_ulong,
target_window: c_ulong,
state: DndState,
) -> Result<(), XError> {
let (accepted, action) = match state {
DndState::Accepted => (1, self.atoms.action_private as c_long),
DndState::Rejected => (0, self.atoms.none as c_long),
};
self.xconn.send_client_msg(
target_window,
target_window,
self.atoms.finished,
None,
[this_window as c_long, accepted, action, 0, 0],
).flush()
}
pub unsafe fn get_type_list(
&self,
source_window: c_ulong,
) -> Result<Vec<ffi::Atom>, util::GetPropertyError> {
self.xconn.get_property(
source_window,
self.atoms.type_list,
ffi::XA_ATOM,
)
}
pub unsafe fn convert_selection(&self, window: c_ulong, time: c_ulong) {
(self.xconn.xlib.XConvertSelection)(
self.xconn.display,
self.atoms.selection,
self.atoms.uri_list,
self.atoms.selection,
window,
time,
);
}
pub unsafe fn read_data(
&self,
window: c_ulong,
) -> Result<Vec<c_uchar>, util::GetPropertyError> {
self.xconn.get_property(
window,
self.atoms.selection,
self.atoms.uri_list,
)
}
pub fn parse_data(&self, data: &mut Vec<c_uchar>) -> Result<Vec<PathBuf>, DndDataParseError> {
if !data.is_empty() {
let mut path_list = Vec::new();
let decoded = percent_decode(data).decode_utf8()?.into_owned();
for uri in decoded.split("\r\n").filter(|u| !u.is_empty()) {
// The format is specified as protocol://host/path
// However, it's typically simply protocol:///path
let path_str = if uri.starts_with("file://") {
let path_str = uri.replace("file://", "");
if !path_str.starts_with('/') {
// A hostname is specified
// Supporting this case is beyond the scope of my mental health
return Err(DndDataParseError::HostnameSpecified(path_str));
}
path_str
} else {
// Only the file protocol is supported
return Err(DndDataParseError::UnexpectedProtocol(uri.to_owned()));
};
let path = Path::new(&path_str).canonicalize()?;
path_list.push(path);
}
Ok(path_list)
} else {
Err(DndDataParseError::EmptyData)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
pub use x11_dl::keysym::*;
pub use x11_dl::xcursor::*;
pub use x11_dl::xlib::*;
pub use x11_dl::xinput::*;
pub use x11_dl::xinput2::*;
pub use x11_dl::xlib_xcb::*;
pub use x11_dl::error::OpenError;
pub use x11_dl::xrandr::*;

View File

@@ -0,0 +1,185 @@
use std::ptr;
use std::sync::Arc;
use std::collections::HashMap;
use std::os::raw::c_char;
use super::{ffi, XConnection, XError};
use super::inner::{close_im, ImeInner};
use super::input_method::PotentialInputMethods;
use super::context::{ImeContextCreationError, ImeContext};
pub unsafe fn xim_set_callback(
xconn: &Arc<XConnection>,
xim: ffi::XIM,
field: *const c_char,
callback: *mut ffi::XIMCallback,
) -> Result<(), XError> {
// It's advisable to wrap variadic FFI functions in our own functions, as we want to minimize
// access that isn't type-checked.
(xconn.xlib.XSetIMValues)(
xim,
field,
callback,
ptr::null_mut::<()>(),
);
xconn.check_errors()
}
// Set a callback for when an input method matching the current locale modifiers becomes
// available. Note that this has nothing to do with what input methods are open or able to be
// opened, and simply uses the modifiers that are set when the callback is set.
// * This is called per locale modifier, not per input method opened with that locale modifier.
// * Trying to set this for multiple locale modifiers causes problems, i.e. one of the rebuilt
// input contexts would always silently fail to use the input method.
pub unsafe fn set_instantiate_callback(
xconn: &Arc<XConnection>,
client_data: ffi::XPointer,
) -> Result<(), XError> {
(xconn.xlib.XRegisterIMInstantiateCallback)(
xconn.display,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
Some(xim_instantiate_callback),
client_data,
);
xconn.check_errors()
}
pub unsafe fn unset_instantiate_callback(
xconn: &Arc<XConnection>,
client_data: ffi::XPointer,
) -> Result<(), XError> {
(xconn.xlib.XUnregisterIMInstantiateCallback)(
xconn.display,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
Some(xim_instantiate_callback),
client_data,
);
xconn.check_errors()
}
pub unsafe fn set_destroy_callback(
xconn: &Arc<XConnection>,
im: ffi::XIM,
inner: &ImeInner,
) -> Result<(), XError> {
xim_set_callback(
&xconn,
im,
ffi::XNDestroyCallback_0.as_ptr() as *const _,
&inner.destroy_callback as *const _ as *mut _,
)
}
#[derive(Debug)]
enum ReplaceImError {
MethodOpenFailed(PotentialInputMethods),
ContextCreationFailed(ImeContextCreationError),
SetDestroyCallbackFailed(XError),
}
// Attempt to replace current IM (which may or may not be presently valid) with a new one. This
// includes replacing all existing input contexts and free'ing resources as necessary. This only
// modifies existing state if all operations succeed.
unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
let xconn = &(*inner).xconn;
let (new_im, is_fallback) = {
let new_im = (*inner).potential_input_methods.open_im(xconn, None);
let is_fallback = new_im.is_fallback();
(
new_im.ok().ok_or_else(|| {
ReplaceImError::MethodOpenFailed((*inner).potential_input_methods.clone())
})?,
is_fallback,
)
};
// It's important to always set a destroy callback, since there's otherwise potential for us
// to try to use or free a resource that's already been destroyed on the server.
{
let result = set_destroy_callback(xconn, new_im.im, &*inner);
if result.is_err() {
let _ = close_im(xconn, new_im.im);
}
result
}.map_err(ReplaceImError::SetDestroyCallbackFailed)?;
let mut new_contexts = HashMap::new();
for (window, old_context) in (*inner).contexts.iter() {
let spot = old_context.as_ref().map(|old_context| old_context.ic_spot);
let new_context = {
let result = ImeContext::new(
xconn,
new_im.im,
*window,
spot,
);
if result.is_err() {
let _ = close_im(xconn, new_im.im);
}
result.map_err(ReplaceImError::ContextCreationFailed)?
};
new_contexts.insert(*window, Some(new_context));
}
// If we've made it this far, everything succeeded.
let _ = (*inner).destroy_all_contexts_if_necessary();
let _ = (*inner).close_im_if_necessary();
(*inner).im = new_im.im;
(*inner).contexts = new_contexts;
(*inner).is_destroyed = false;
(*inner).is_fallback = is_fallback;
Ok(())
}
pub unsafe extern fn xim_instantiate_callback(
_display: *mut ffi::Display,
client_data: ffi::XPointer,
// This field is unsupplied.
_call_data: ffi::XPointer,
) {
let inner: *mut ImeInner = client_data as _;
if !inner.is_null() {
let xconn = &(*inner).xconn;
let result = replace_im(inner);
if result.is_ok() {
let _ = unset_instantiate_callback(xconn, client_data);
(*inner).is_fallback = false;
} else if result.is_err() && (*inner).is_destroyed {
// We have no usable input methods!
result.expect("Failed to reopen input method");
}
}
}
// This callback is triggered when the input method is closed on the server end. When this
// happens, XCloseIM/XDestroyIC doesn't need to be called, as the resources have already been
// free'd (attempting to do so causes our connection to freeze).
pub unsafe extern fn xim_destroy_callback(
_xim: ffi::XIM,
client_data: ffi::XPointer,
// This field is unsupplied.
_call_data: ffi::XPointer,
) {
let inner: *mut ImeInner = client_data as _;
if !inner.is_null() {
(*inner).is_destroyed = true;
let xconn = &(*inner).xconn;
if !(*inner).is_fallback {
let _ = set_instantiate_callback(xconn, client_data);
// Attempt to open fallback input method.
let result = replace_im(inner);
if result.is_ok() {
(*inner).is_fallback = true;
} else {
// We have no usable input methods!
result.expect("Failed to open fallback input method");
}
}
}
}

View File

@@ -0,0 +1,134 @@
use std::ptr;
use std::sync::Arc;
use std::os::raw::{c_short, c_void};
use super::{ffi, util, XConnection, XError};
#[derive(Debug)]
pub enum ImeContextCreationError {
XError(XError),
Null,
}
unsafe fn create_pre_edit_attr<'a>(
xconn: &'a Arc<XConnection>,
ic_spot: &'a ffi::XPoint,
) -> util::XSmartPointer<'a, c_void> {
util::XSmartPointer::new(
xconn,
(xconn.xlib.XVaCreateNestedList)(
0,
ffi::XNSpotLocation_0.as_ptr() as *const _,
ic_spot,
ptr::null_mut::<()>(),
),
).expect("XVaCreateNestedList returned NULL")
}
// WARNING: this struct doesn't destroy its XIC resource when dropped.
// This is intentional, as it doesn't have enough information to know whether or not the context
// still exists on the server. Since `ImeInner` has that awareness, destruction must be handled
// through `ImeInner`.
#[derive(Debug)]
pub struct ImeContext {
pub ic: ffi::XIC,
pub ic_spot: ffi::XPoint,
}
impl ImeContext {
pub unsafe fn new(
xconn: &Arc<XConnection>,
im: ffi::XIM,
window: ffi::Window,
ic_spot: Option<ffi::XPoint>,
) -> Result<Self, ImeContextCreationError> {
let ic = if let Some(ic_spot) = ic_spot {
ImeContext::create_ic_with_spot(xconn, im, window, ic_spot)
} else {
ImeContext::create_ic(xconn, im, window)
};
let ic = ic.ok_or(ImeContextCreationError::Null)?;
xconn.check_errors().map_err(ImeContextCreationError::XError)?;
Ok(ImeContext {
ic,
ic_spot: ic_spot.unwrap_or_else(|| ffi::XPoint { x: 0, y: 0 }),
})
}
unsafe fn create_ic(
xconn: &Arc<XConnection>,
im: ffi::XIM,
window: ffi::Window,
) -> Option<ffi::XIC> {
let ic = (xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ptr::null_mut::<()>(),
);
if ic.is_null() {
None
} else {
Some(ic)
}
}
unsafe fn create_ic_with_spot(
xconn: &Arc<XConnection>,
im: ffi::XIM,
window: ffi::Window,
ic_spot: ffi::XPoint,
) -> Option<ffi::XIC> {
let pre_edit_attr = create_pre_edit_attr(xconn, &ic_spot);
let ic = (xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
pre_edit_attr.ptr,
ptr::null_mut::<()>(),
);
if ic.is_null() {
None
} else {
Some(ic)
}
}
pub fn focus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
unsafe {
(xconn.xlib.XSetICFocus)(self.ic);
}
xconn.check_errors()
}
pub fn unfocus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
unsafe {
(xconn.xlib.XUnsetICFocus)(self.ic);
}
xconn.check_errors()
}
pub fn set_spot(&mut self, xconn: &Arc<XConnection>, x: c_short, y: c_short) {
if self.ic_spot.x == x && self.ic_spot.y == y {
return;
}
self.ic_spot = ffi::XPoint { x, y };
unsafe {
let pre_edit_attr = create_pre_edit_attr(xconn, &self.ic_spot);
(xconn.xlib.XSetICValues)(
self.ic,
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
pre_edit_attr.ptr,
ptr::null_mut::<()>(),
);
}
}
}

View File

@@ -1,31 +1,31 @@
use std::{collections::HashMap, mem, sync::Arc};
use std::mem;
use std::ptr;
use std::sync::Arc;
use std::collections::HashMap;
use super::{ffi, XConnection, XError};
use super::{
context::ImeContext,
input_method::{InputMethod, PotentialInputMethods},
};
use crate::platform_impl::platform::x11::ime::ImeEventSender;
use super::input_method::PotentialInputMethods;
use super::context::ImeContext;
pub(crate) unsafe fn close_im(xconn: &Arc<XConnection>, im: ffi::XIM) -> Result<(), XError> {
unsafe { (xconn.xlib.XCloseIM)(im) };
pub unsafe fn close_im(xconn: &Arc<XConnection>, im: ffi::XIM) -> Result<(), XError> {
(xconn.xlib.XCloseIM)(im);
xconn.check_errors()
}
pub(crate) unsafe fn destroy_ic(xconn: &Arc<XConnection>, ic: ffi::XIC) -> Result<(), XError> {
unsafe { (xconn.xlib.XDestroyIC)(ic) };
pub unsafe fn destroy_ic(xconn: &Arc<XConnection>, ic: ffi::XIC) -> Result<(), XError> {
(xconn.xlib.XDestroyIC)(ic);
xconn.check_errors()
}
pub(crate) struct ImeInner {
pub struct ImeInner {
pub xconn: Arc<XConnection>,
pub im: Option<InputMethod>,
// WARNING: this is initially null!
pub im: ffi::XIM,
pub potential_input_methods: PotentialInputMethods,
pub contexts: HashMap<ffi::Window, Option<ImeContext>>,
// WARNING: this is initially zeroed!
pub destroy_callback: ffi::XIMCallback,
pub event_sender: ImeEventSender,
// Indicates whether or not the the input method was destroyed on the server end
// (i.e. if ibus/fcitx/etc. was terminated/restarted)
pub is_destroyed: bool,
@@ -33,26 +33,24 @@ pub(crate) struct ImeInner {
}
impl ImeInner {
pub(crate) fn new(
pub fn new(
xconn: Arc<XConnection>,
potential_input_methods: PotentialInputMethods,
event_sender: ImeEventSender,
) -> Self {
ImeInner {
xconn,
im: None,
im: ptr::null_mut(),
potential_input_methods,
contexts: HashMap::new(),
destroy_callback: unsafe { mem::zeroed() },
event_sender,
is_destroyed: false,
is_fallback: false,
}
}
pub unsafe fn close_im_if_necessary(&self) -> Result<bool, XError> {
if !self.is_destroyed && self.im.is_some() {
unsafe { close_im(&self.xconn, self.im.as_ref().unwrap().im) }.map(|_| true)
if !self.is_destroyed {
close_im(&self.xconn, self.im).map(|_| true)
} else {
Ok(false)
}
@@ -60,15 +58,17 @@ impl ImeInner {
pub unsafe fn destroy_ic_if_necessary(&self, ic: ffi::XIC) -> Result<bool, XError> {
if !self.is_destroyed {
unsafe { destroy_ic(&self.xconn, ic) }.map(|_| true)
destroy_ic(&self.xconn, ic).map(|_| true)
} else {
Ok(false)
}
}
pub unsafe fn destroy_all_contexts_if_necessary(&self) -> Result<bool, XError> {
for context in self.contexts.values().flatten() {
unsafe { self.destroy_ic_if_necessary(context.ic)? };
for context in self.contexts.values() {
if let &Some(ref context) = context {
self.destroy_ic_if_necessary(context.ic)?;
}
}
Ok(!self.is_destroyed)
}

View File

@@ -1,36 +1,36 @@
use std::{
env,
ffi::{CStr, CString, IntoStringError},
fmt,
os::raw::{c_char, c_ulong, c_ushort},
ptr,
sync::{Arc, Mutex},
};
use std::env;
use std::fmt;
use std::ptr;
use std::sync::Arc;
use std::os::raw::c_char;
use std::ffi::{CStr, CString, IntoStringError};
use super::{super::atoms::*, ffi, util, XConnection, XError};
use once_cell::sync::Lazy;
use x11rb::protocol::xproto;
use parking_lot::Mutex;
static GLOBAL_LOCK: Lazy<Mutex<()>> = Lazy::new(Default::default);
use super::{ffi, util, XConnection, XError};
unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<ffi::XIM> {
lazy_static! {
static ref GLOBAL_LOCK: Mutex<()> = Default::default();
}
unsafe fn open_im(
xconn: &Arc<XConnection>,
locale_modifiers: &CStr,
) -> Option<ffi::XIM> {
let _lock = GLOBAL_LOCK.lock();
// XSetLocaleModifiers returns...
// * The current locale modifiers if it's given a NULL pointer.
// * The new locale modifiers if we succeeded in setting them.
// * NULL if the locale modifiers string is malformed or if the
// current locale is not supported by Xlib.
unsafe { (xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr()) };
// * NULL if the locale modifiers string is malformed.
(xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr());
let im = unsafe {
(xconn.xlib.XOpenIM)(
xconn.display,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
)
};
let im = (xconn.xlib.XOpenIM)(
xconn.display,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
);
if im.is_null() {
None
@@ -42,97 +42,15 @@ unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<f
#[derive(Debug)]
pub struct InputMethod {
pub im: ffi::XIM,
pub preedit_style: Style,
pub none_style: Style,
_name: String,
name: String,
}
impl InputMethod {
fn new(xconn: &Arc<XConnection>, im: ffi::XIM, name: String) -> Option<Self> {
let mut styles: *mut XIMStyles = std::ptr::null_mut();
// Query the styles supported by the XIM.
unsafe {
if !(xconn.xlib.XGetIMValues)(
im,
ffi::XNQueryInputStyle_0.as_ptr() as *const _,
(&mut styles) as *mut _,
std::ptr::null_mut::<()>(),
)
.is_null()
{
return None;
}
}
let mut preedit_style = None;
let mut none_style = None;
unsafe {
std::slice::from_raw_parts((*styles).supported_styles, (*styles).count_styles as _)
.iter()
.for_each(|style| match *style {
XIM_PREEDIT_STYLE => {
preedit_style = Some(Style::Preedit(*style));
}
XIM_NOTHING_STYLE if preedit_style.is_none() => {
preedit_style = Some(Style::Nothing(*style))
}
XIM_NONE_STYLE => none_style = Some(Style::None(*style)),
_ => (),
});
(xconn.xlib.XFree)(styles.cast());
};
if preedit_style.is_none() && none_style.is_none() {
return None;
}
let preedit_style = preedit_style.unwrap_or_else(|| none_style.unwrap());
let none_style = none_style.unwrap_or(preedit_style);
Some(InputMethod {
im,
_name: name,
preedit_style,
none_style,
})
fn new(im: ffi::XIM, name: String) -> Self {
InputMethod { im, name }
}
}
const XIM_PREEDIT_STYLE: XIMStyle = (ffi::XIMPreeditCallbacks | ffi::XIMStatusNothing) as XIMStyle;
const XIM_NOTHING_STYLE: XIMStyle = (ffi::XIMPreeditNothing | ffi::XIMStatusNothing) as XIMStyle;
const XIM_NONE_STYLE: XIMStyle = (ffi::XIMPreeditNone | ffi::XIMStatusNone) as XIMStyle;
/// Style of the IME context.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Style {
/// Preedit callbacks.
Preedit(XIMStyle),
/// Nothing.
Nothing(XIMStyle),
/// No IME.
None(XIMStyle),
}
impl Default for Style {
fn default() -> Self {
Style::None(XIM_NONE_STYLE)
}
}
#[repr(C)]
#[derive(Debug)]
struct XIMStyles {
count_styles: c_ushort,
supported_styles: *const XIMStyle,
}
pub(crate) type XIMStyle = c_ulong;
#[derive(Debug)]
pub enum InputMethodResult {
/// Input method used locale modifier from `XMODIFIERS` environment variable.
@@ -145,7 +63,11 @@ pub enum InputMethodResult {
impl InputMethodResult {
pub fn is_fallback(&self) -> bool {
matches!(self, InputMethodResult::Fallback(_))
if let &InputMethodResult::Fallback(_) = self {
true
} else {
false
}
}
pub fn ok(self) -> Option<InputMethod> {
@@ -159,15 +81,9 @@ impl InputMethodResult {
#[derive(Debug, Clone)]
enum GetXimServersError {
XError(#[allow(dead_code)] XError),
GetPropertyError(#[allow(dead_code)] util::GetPropertyError),
InvalidUtf8(#[allow(dead_code)] IntoStringError),
}
impl From<util::GetPropertyError> for GetXimServersError {
fn from(error: util::GetPropertyError) -> Self {
GetXimServersError::GetPropertyError(error)
}
XError(XError),
GetPropertyError(util::GetPropertyError),
InvalidUtf8(IntoStringError),
}
// The root window has a property named XIM_SERVERS, which contains a list of atoms represeting
@@ -177,40 +93,32 @@ impl From<util::GetPropertyError> for GetXimServersError {
// modifiers, since we don't want a user who's looking at logs to ask "am I supposed to set
// XMODIFIERS to `@server=ibus`?!?"
unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXimServersError> {
let atoms = xconn.atoms();
let servers_atom = atoms[XIM_SERVERS];
let servers_atom = xconn.get_atom_unchecked(b"XIM_SERVERS\0");
let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
let root = (xconn.xlib.XDefaultRootWindow)(xconn.display);
let mut atoms: Vec<ffi::Atom> = xconn
.get_property::<xproto::Atom>(
root as xproto::Window,
servers_atom,
xproto::Atom::from(xproto::AtomEnum::ATOM),
)
.map_err(GetXimServersError::GetPropertyError)?
.into_iter()
.map(ffi::Atom::from)
.collect::<Vec<_>>();
let mut atoms: Vec<ffi::Atom> = xconn.get_property(
root,
servers_atom,
ffi::XA_ATOM,
).map_err(GetXimServersError::GetPropertyError)?;
let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());
unsafe {
(xconn.xlib.XGetAtomNames)(
xconn.display,
atoms.as_mut_ptr(),
atoms.len() as _,
names.as_mut_ptr() as _,
)
};
unsafe { names.set_len(atoms.len()) };
(xconn.xlib.XGetAtomNames)(
xconn.display,
atoms.as_mut_ptr(),
atoms.len() as _,
names.as_mut_ptr() as _,
);
names.set_len(atoms.len());
let mut formatted_names = Vec::with_capacity(names.len());
for name in names {
let string = unsafe { CStr::from_ptr(name) }
let string = CStr::from_ptr(name)
.to_owned()
.into_string()
.map_err(GetXimServersError::InvalidUtf8)?;
unsafe { (xconn.xlib.XFree)(name as _) };
(xconn.xlib.XFree)(name as _);
formatted_names.push(string.replace("@server=", "@im="));
}
xconn.check_errors().map_err(GetXimServersError::XError)?;
@@ -227,12 +135,15 @@ impl InputMethodName {
pub fn from_string(string: String) -> Self {
let c_string = CString::new(string.clone())
.expect("String used to construct CString contained null byte");
InputMethodName { c_string, string }
InputMethodName {
c_string,
string,
}
}
pub fn from_str(string: &str) -> Self {
let c_string =
CString::new(string).expect("String used to construct CString contained null byte");
let c_string = CString::new(string)
.expect("String used to construct CString contained null byte");
InputMethodName {
c_string,
string: string.to_owned(),
@@ -241,7 +152,7 @@ impl InputMethodName {
}
impl fmt::Debug for InputMethodName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.string.fmt(f)
}
}
@@ -274,15 +185,15 @@ impl PotentialInputMethod {
pub fn open_im(&mut self, xconn: &Arc<XConnection>) -> Option<InputMethod> {
let im = unsafe { open_im(xconn, &self.name.c_string) };
self.successful = Some(im.is_some());
im.and_then(|im| InputMethod::new(xconn, im, self.name.string.clone()))
im.map(|im| InputMethod::new(im, self.name.string.clone()))
}
}
// By logging this struct, you get a sequential listing of every locale modifier tried, where it
// came from, and if it succeeded.
// came from, and if it succceeded.
#[derive(Debug, Clone)]
pub(crate) struct PotentialInputMethods {
// On correctly configured systems, the XMODIFIERS environment variable tells us everything we
pub struct PotentialInputMethods {
// On correctly configured systems, the XMODIFIERS environemnt variable tells us everything we
// need to know.
xmodifiers: Option<PotentialInputMethod>,
// We have some standard options at our disposal that should ostensibly always work. For users
@@ -312,7 +223,7 @@ impl PotentialInputMethods {
// that case, we get `None` and end up skipping ahead to the next method.
xmodifiers,
fallbacks: [
// This is a standard input method that supports compose sequences, which should
// This is a standard input method that supports compose equences, which should
// always be available. `@im=none` appears to mean the same thing.
PotentialInputMethod::from_str("@im=local"),
// This explicitly specifies to use the implementation-dependent default, though
@@ -343,7 +254,7 @@ impl PotentialInputMethods {
pub fn open_im(
&mut self,
xconn: &Arc<XConnection>,
callback: Option<&dyn Fn()>,
callback: Option<&Fn() -> ()>,
) -> InputMethodResult {
use self::InputMethodResult::*;
@@ -353,8 +264,10 @@ impl PotentialInputMethods {
let im = input_method.open_im(xconn);
if let Some(im) = im {
return XModifiers(im);
} else if let Some(ref callback) = callback {
callback();
} else {
if let Some(ref callback) = callback {
callback();
}
}
}

View File

@@ -1,61 +1,30 @@
// Important: all XIM calls need to happen from the same thread!
mod callbacks;
mod context;
mod inner;
mod input_method;
mod context;
mod callbacks;
use std::sync::{
mpsc::{Receiver, Sender},
Arc,
};
use log::debug;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use std::sync::mpsc::{Receiver, Sender};
use super::{ffi, util, XConnection, XError};
pub use self::context::ImeContextCreationError;
use self::{
callbacks::*,
context::ImeContext,
inner::{close_im, ImeInner},
input_method::{PotentialInputMethods, Style},
};
use self::inner::{close_im, ImeInner};
use self::input_method::PotentialInputMethods;
use self::context::{ImeContextCreationError, ImeContext};
use self::callbacks::*;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ImeEvent {
Enabled,
Start,
Update(String, usize),
End,
Disabled,
}
pub type ImeReceiver = Receiver<ImeRequest>;
pub type ImeSender = Sender<ImeRequest>;
pub type ImeEventReceiver = Receiver<(ffi::Window, ImeEvent)>;
pub type ImeEventSender = Sender<(ffi::Window, ImeEvent)>;
/// Request to control XIM handler from the window.
pub enum ImeRequest {
/// Set IME spot position for given `window_id`.
Position(ffi::Window, i16, i16),
/// Allow IME input for the given `window_id`.
Allow(ffi::Window, bool),
}
pub type ImeReceiver = Receiver<(ffi::Window, i16, i16)>;
pub type ImeSender = Sender<(ffi::Window, i16, i16)>;
#[derive(Debug)]
pub(crate) enum ImeCreationError {
// Boxed to prevent large error type
OpenFailure(Box<PotentialInputMethods>),
SetDestroyCallbackFailed(#[allow(dead_code)] XError),
pub enum ImeCreationError {
OpenFailure(PotentialInputMethods),
SetDestroyCallbackFailed(XError),
}
pub(crate) struct Ime {
pub struct Ime {
xconn: Arc<XConnection>,
// The actual meat of this struct is boxed away, since it needs to have a fixed location in
// memory so we can pass a pointer to it around.
@@ -63,14 +32,14 @@ pub(crate) struct Ime {
}
impl Ime {
pub fn new(
xconn: Arc<XConnection>,
event_sender: ImeEventSender,
) -> Result<Self, ImeCreationError> {
pub fn new(xconn: Arc<XConnection>) -> Result<Self, ImeCreationError> {
let potential_input_methods = PotentialInputMethods::new(&xconn);
let (mut inner, client_data) = {
let mut inner = Box::new(ImeInner::new(xconn, potential_input_methods, event_sender));
let mut inner = Box::new(ImeInner::new(
xconn,
potential_input_methods,
));
let inner_ptr = Box::into_raw(inner);
let client_data = inner_ptr as _;
let destroy_callback = ffi::XIMCallback {
@@ -84,30 +53,25 @@ impl Ime {
let xconn = Arc::clone(&inner.xconn);
let input_method = inner.potential_input_methods.open_im(
&xconn,
Some(&|| {
let _ = unsafe { set_instantiate_callback(&xconn, client_data) };
}),
);
let input_method = inner.potential_input_methods.open_im(&xconn, Some(&|| {
let _ = unsafe { set_instantiate_callback(&xconn, client_data) };
}));
let is_fallback = input_method.is_fallback();
if let Some(input_method) = input_method.ok() {
inner.im = input_method.im;
inner.is_fallback = is_fallback;
unsafe {
let result = set_destroy_callback(&xconn, input_method.im, &inner)
let result = set_destroy_callback(&xconn, input_method.im, &*inner)
.map_err(ImeCreationError::SetDestroyCallbackFailed);
if result.is_err() {
let _ = close_im(&xconn, input_method.im);
}
result?;
}
inner.im = Some(input_method);
Ok(Ime { xconn, inner })
} else {
Err(ImeCreationError::OpenFailure(Box::new(
inner.potential_input_methods,
)))
Err(ImeCreationError::OpenFailure(inner.potential_input_methods))
}
}
@@ -119,54 +83,20 @@ impl Ime {
// Ok(_) indicates that nothing went wrong internally
// Ok(true) indicates that the action was actually performed
// Ok(false) indicates that the action is not presently applicable
pub fn create_context(
&mut self,
window: ffi::Window,
with_preedit: bool,
) -> Result<bool, ImeContextCreationError> {
pub fn create_context(&mut self, window: ffi::Window)
-> Result<bool, ImeContextCreationError>
{
let context = if self.is_destroyed() {
// Create empty entry in map, so that when IME is rebuilt, this window has a context.
None
} else {
let im = self.inner.im.as_ref().unwrap();
let style = if with_preedit {
im.preedit_style
} else {
im.none_style
};
let context = unsafe {
ImeContext::new(
&self.inner.xconn,
im.im,
style,
window,
None,
self.inner.event_sender.clone(),
)?
};
// Check the state on the context, since it could fail to enable or disable preedit.
let event = if matches!(style, Style::None(_)) {
if with_preedit {
debug!("failed to create IME context with preedit support.")
}
ImeEvent::Disabled
} else {
if !with_preedit {
debug!("failed to create IME context without preedit support.")
}
ImeEvent::Enabled
};
self.inner
.event_sender
.send((window, event))
.expect("Failed to send enabled event");
Some(context)
Some(unsafe { ImeContext::new(
&self.inner.xconn,
self.inner.im,
window,
None,
) }?)
};
self.inner.contexts.insert(window, context);
Ok(!self.is_destroyed())
}
@@ -175,7 +105,7 @@ impl Ime {
if self.is_destroyed() {
return None;
}
if let Some(Some(context)) = self.inner.contexts.get(&window) {
if let Some(&Some(ref context)) = self.inner.contexts.get(&window) {
Some(context.ic)
} else {
None
@@ -223,24 +153,6 @@ impl Ime {
context.set_spot(&self.xconn, x as _, y as _);
}
}
pub fn set_ime_allowed(&mut self, window: ffi::Window, allowed: bool) {
if self.is_destroyed() {
return;
}
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
if allowed == context.is_allowed() {
return;
}
}
// Remove context for that window.
let _ = self.remove_context(window);
// Create new context supporting IME input.
let _ = self.create_context(window, allowed);
}
}
impl Drop for Ime {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,274 @@
use std::os::raw::*;
use parking_lot::Mutex;
use {PhysicalPosition, PhysicalSize};
use super::{util, XConnection, XError};
use super::ffi::{
RRCrtcChangeNotifyMask,
RROutputPropertyNotifyMask,
RRScreenChangeNotifyMask,
True,
Window,
XRRScreenResources,
};
// Used to test XRandR < 1.5 code path. This should always be committed as false.
const FORCE_RANDR_COMPAT: bool = false;
// Also used for testing. This should always be committed as false.
const DISABLE_MONITOR_LIST_CACHING: bool = false;
lazy_static! {
static ref XRANDR_VERSION: Mutex<Option<(c_int, c_int)>> = Mutex::default();
static ref MONITORS: Mutex<Option<Vec<MonitorId>>> = Mutex::default();
}
fn version_is_at_least(major: c_int, minor: c_int) -> bool {
if let Some((avail_major, avail_minor)) = *XRANDR_VERSION.lock() {
if avail_major == major {
avail_minor >= minor
} else {
avail_major > major
}
} else {
unreachable!();
}
}
pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorId>> {
// We update this lazily.
(*MONITORS.lock()).take()
}
#[derive(Debug, Clone)]
pub struct MonitorId {
/// The actual id
id: u32,
/// The name of the monitor
pub(crate) name: String,
/// The size of the monitor
dimensions: (u32, u32),
/// The position of the monitor in the X screen
position: (i32, i32),
/// If the monitor is the primary one
primary: bool,
/// The DPI scale factor
pub(crate) hidpi_factor: f64,
/// Used to determine which windows are on this monitor
pub(crate) rect: util::AaRect,
}
impl MonitorId {
fn from_repr(
xconn: &XConnection,
resources: *mut XRRScreenResources,
id: u32,
repr: util::MonitorRepr,
primary: bool,
) -> Option<Self> {
let (name, hidpi_factor) = unsafe { xconn.get_output_info(resources, &repr)? };
let (dimensions, position) = unsafe { (repr.get_dimensions(), repr.get_position()) };
let rect = util::AaRect::new(position, dimensions);
Some(MonitorId {
id,
name,
hidpi_factor,
dimensions,
position,
primary,
rect,
})
}
pub fn get_name(&self) -> Option<String> {
Some(self.name.clone())
}
#[inline]
pub fn get_native_identifier(&self) -> u32 {
self.id as u32
}
pub fn get_dimensions(&self) -> PhysicalSize {
self.dimensions.into()
}
pub fn get_position(&self) -> PhysicalPosition {
self.position.into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
self.hidpi_factor
}
}
impl XConnection {
pub fn get_monitor_for_window(&self, window_rect: Option<util::AaRect>) -> MonitorId {
let monitors = self.get_available_monitors();
let default = monitors
.get(0)
.expect("[winit] Failed to find any monitors using XRandR.");
let window_rect = match window_rect {
Some(rect) => rect,
None => return default.to_owned(),
};
let mut largest_overlap = 0;
let mut matched_monitor = default;
for monitor in &monitors {
let overlapping_area = window_rect.get_overlapping_area(&monitor.rect);
if overlapping_area > largest_overlap {
largest_overlap = overlapping_area;
matched_monitor = &monitor;
}
}
matched_monitor.to_owned()
}
fn query_monitor_list(&self) -> Vec<MonitorId> {
unsafe {
let root = (self.xlib.XDefaultRootWindow)(self.display);
let resources = if version_is_at_least(1, 3) {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
} else {
// WARNING: this function is supposedly very slow, on the order of hundreds of ms.
// Upon failure, `resources` will be null.
(self.xrandr.XRRGetScreenResources)(self.display, root)
};
if resources.is_null() {
panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist.");
}
let mut available;
let mut has_primary = false;
if self.xrandr_1_5.is_some() && version_is_at_least(1, 5) && !FORCE_RANDR_COMPAT {
// We're in XRandR >= 1.5, enumerate monitors. This supports things like MST and
// videowalls.
let xrandr_1_5 = self.xrandr_1_5.as_ref().unwrap();
let mut monitor_count = 0;
let monitors = (xrandr_1_5.XRRGetMonitors)(self.display, root, 1, &mut monitor_count);
assert!(monitor_count >= 0);
available = Vec::with_capacity(monitor_count as usize);
for monitor_index in 0..monitor_count {
let monitor = monitors.offset(monitor_index as isize);
let is_primary = (*monitor).primary != 0;
has_primary |= is_primary;
MonitorId::from_repr(
self,
resources,
monitor_index as u32,
monitor.into(),
is_primary,
).map(|monitor_id| available.push(monitor_id));
}
(xrandr_1_5.XRRFreeMonitors)(monitors);
} else {
// We're in XRandR < 1.5, enumerate CRTCs. Everything will work except MST and
// videowall setups will also show monitors that aren't in the logical groups the user
// cares about.
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
available = Vec::with_capacity((*resources).ncrtc as usize);
for crtc_index in 0..(*resources).ncrtc {
let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
if is_active {
let crtc = util::MonitorRepr::from(crtc);
let is_primary = crtc.get_output() == primary;
has_primary |= is_primary;
MonitorId::from_repr(
self,
resources,
crtc_id as u32,
crtc,
is_primary,
).map(|monitor_id| available.push(monitor_id));
}
(self.xrandr.XRRFreeCrtcInfo)(crtc);
}
}
// If no monitors were detected as being primary, we just pick one ourselves!
if !has_primary {
if let Some(ref mut fallback) = available.first_mut() {
// Setting this here will come in handy if we ever add an `is_primary` method.
fallback.primary = true;
}
}
(self.xrandr.XRRFreeScreenResources)(resources);
available
}
}
pub fn get_available_monitors(&self) -> Vec<MonitorId> {
let mut monitors_lock = MONITORS.lock();
(*monitors_lock)
.as_ref()
.cloned()
.or_else(|| {
let monitors = Some(self.query_monitor_list());
if !DISABLE_MONITOR_LIST_CACHING {
(*monitors_lock) = monitors.clone();
}
monitors
})
.unwrap()
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
self.get_available_monitors()
.into_iter()
.find(|monitor| monitor.primary)
.expect("[winit] Failed to find any monitors using XRandR.")
}
pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, XError> {
{
let mut version_lock = XRANDR_VERSION.lock();
if version_lock.is_none() {
let mut major = 0;
let mut minor = 0;
let has_extension = unsafe {
(self.xrandr.XRRQueryVersion)(
self.display,
&mut major,
&mut minor,
)
};
if has_extension != True {
panic!("[winit] XRandR extension not available.");
}
*version_lock = Some((major, minor));
}
}
let mut event_offset = 0;
let mut error_offset = 0;
let status = unsafe {
(self.xrandr.XRRQueryExtension)(
self.display,
&mut event_offset,
&mut error_offset,
)
};
if status != True {
self.check_errors()?;
unreachable!("[winit] `XRRQueryExtension` failed but no error was received.");
}
let mask = RRCrtcChangeNotifyMask
| RROutputPropertyNotifyMask
| RRScreenChangeNotifyMask;
unsafe { (self.xrandr.XRRSelectInput)(self.display, root, mask) };
Ok(event_offset)
}
}

View File

@@ -0,0 +1,72 @@
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::fmt::Debug;
use std::os::raw::*;
use parking_lot::Mutex;
use super::*;
type AtomCache = HashMap<CString, ffi::Atom>;
lazy_static! {
static ref ATOM_CACHE: Mutex<AtomCache> = Mutex::new(HashMap::with_capacity(2048));
}
impl XConnection {
pub fn get_atom<T: AsRef<CStr> + Debug>(&self, name: T) -> ffi::Atom {
let name = name.as_ref();
let mut atom_cache_lock = ATOM_CACHE.lock();
let cached_atom = (*atom_cache_lock).get(name).cloned();
if let Some(atom) = cached_atom {
atom
} else {
let atom = unsafe { (self.xlib.XInternAtom)(
self.display,
name.as_ptr() as *const c_char,
ffi::False,
) };
if atom == 0 {
let msg = format!(
"`XInternAtom` failed, which really shouldn't happen. Atom: {:?}, Error: {:#?}",
name,
self.check_errors(),
);
panic!(msg);
}
/*println!(
"XInternAtom name:{:?} atom:{:?}",
name,
atom,
);*/
(*atom_cache_lock).insert(name.to_owned(), atom);
atom
}
}
pub unsafe fn get_atom_unchecked(&self, name: &[u8]) -> ffi::Atom {
debug_assert!(CStr::from_bytes_with_nul(name).is_ok());
let name = CStr::from_bytes_with_nul_unchecked(name);
self.get_atom(name)
}
// Note: this doesn't use caching, for the sake of simplicity.
// If you're dealing with this many atoms, you'll usually want to cache them locally anyway.
pub unsafe fn get_atoms(&self, names: &[*mut c_char]) -> Result<Vec<ffi::Atom>, XError> {
let mut atoms = Vec::with_capacity(names.len());
(self.xlib.XInternAtoms)(
self.display,
names.as_ptr() as *mut _,
names.len() as c_int,
ffi::False,
atoms.as_mut_ptr(),
);
self.check_errors()?;
atoms.set_len(names.len());
/*println!(
"XInternAtoms atoms:{:?}",
atoms,
);*/
Ok(atoms)
}
}

View File

@@ -0,0 +1,95 @@
use super::*;
pub type ClientMsgPayload = [c_long; 5];
impl XConnection {
pub fn send_event<T: Into<ffi::XEvent>>(
&self,
target_window: c_ulong,
event_mask: Option<c_long>,
event: T,
) -> Flusher {
let event_mask = event_mask.unwrap_or(ffi::NoEventMask);
unsafe {
(self.xlib.XSendEvent)(
self.display,
target_window,
ffi::False,
event_mask,
&mut event.into(),
);
}
Flusher::new(self)
}
pub fn send_client_msg(
&self,
window: c_ulong, // The window this is "about"; not necessarily this window
target_window: c_ulong, // The window we're sending to
message_type: ffi::Atom,
event_mask: Option<c_long>,
data: ClientMsgPayload,
) -> Flusher {
let mut event: ffi::XClientMessageEvent = unsafe { mem::uninitialized() };
event.type_ = ffi::ClientMessage;
event.display = self.display;
event.window = window;
event.message_type = message_type;
event.format = c_long::FORMAT as c_int;
event.data = unsafe { mem::transmute(data) };
self.send_event(target_window, event_mask, event)
}
// Prepare yourself for the ultimate in unsafety!
// You should favor `send_client_msg` whenever possible, but some protocols (i.e. startup notification) require you
// to send more than one message worth of data.
pub fn send_client_msg_multi<T: Formattable>(
&self,
window: c_ulong, // The window this is "about"; not necessarily this window
target_window: c_ulong, // The window we're sending to
message_type: ffi::Atom,
event_mask: Option<c_long>,
data: &[T],
) -> Flusher {
let format = T::FORMAT;
let size_of_t = mem::size_of::<T>();
debug_assert_eq!(size_of_t, format.get_actual_size());
let mut event: ffi::XClientMessageEvent = unsafe { mem::uninitialized() };
event.type_ = ffi::ClientMessage;
event.display = self.display;
event.window = window;
event.message_type = message_type;
event.format = format as c_int;
let t_per_payload = format.get_payload_size() / size_of_t;
assert!(t_per_payload > 0);
let payload_count = data.len() / t_per_payload;
let payload_remainder = data.len() % t_per_payload;
let payload_ptr = data.as_ptr() as *const ClientMsgPayload;
let mut payload_index = 0;
while payload_index < payload_count {
let payload = unsafe { payload_ptr.offset(payload_index as isize) };
payload_index += 1;
event.data = unsafe { mem::transmute(*payload) };
self.send_event(target_window, event_mask, &event).queue();
}
if payload_remainder > 0 {
let mut payload: ClientMsgPayload = [0; 5];
let t_payload = payload.as_mut_ptr() as *mut T;
let invalid_payload = unsafe { payload_ptr.offset(payload_index as isize) };
let invalid_t_payload = invalid_payload as *const T;
let mut t_index = 0;
while t_index < payload_remainder {
let valid_t = unsafe { invalid_t_payload.offset(t_index as isize) };
unsafe { (*t_payload.offset(t_index as isize)) = (*valid_t).clone() };
t_index += 1;
}
event.data = unsafe { mem::transmute(payload) };
self.send_event(target_window, event_mask, &event).queue();
}
Flusher::new(self)
}
}

View File

@@ -0,0 +1,58 @@
use std::fmt::Debug;
use std::mem;
use std::os::raw::*;
// This isn't actually the number of the bits in the format.
// X11 does a match on this value to determine which type to call sizeof on.
// Thus, we use 32 for c_long, since 32 maps to c_long which maps to 64.
// ...if that sounds confusing, then you know why this enum is here.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Format {
Char = 8,
Short = 16,
Long = 32,
}
impl Format {
pub fn from_format(format: usize) -> Option<Self> {
match format {
8 => Some(Format::Char),
16 => Some(Format::Short),
32 => Some(Format::Long),
_ => None,
}
}
pub fn is_same_size_as<T>(&self) -> bool {
mem::size_of::<T>() == self.get_actual_size()
}
pub fn get_actual_size(&self) -> usize {
match self {
&Format::Char => mem::size_of::<c_char>(),
&Format::Short => mem::size_of::<c_short>(),
&Format::Long => mem::size_of::<c_long>(),
}
}
pub fn get_payload_size(&self) -> usize {
match self {
// Due to the wonders of X11, half the space goes unused if you're not using longs (on 64-bit).
&Format::Char => mem::size_of::<c_char>() * 20,
&Format::Short => mem::size_of::<c_short>() * 10,
&Format::Long => mem::size_of::<c_long>() * 5,
}
}
}
pub trait Formattable: Debug + Clone + Copy + PartialEq + PartialOrd {
const FORMAT: Format;
}
// You might be surprised by the absence of c_int, but not as surprised as X11 would be by the presence of it.
impl Formattable for c_schar { const FORMAT: Format = Format::Char; }
impl Formattable for c_uchar { const FORMAT: Format = Format::Char; }
impl Formattable for c_short { const FORMAT: Format = Format::Short; }
impl Formattable for c_ushort { const FORMAT: Format = Format::Short; }
impl Formattable for c_long { const FORMAT: Format = Format::Long; }
impl Formattable for c_ulong { const FORMAT: Format = Format::Long; }

View File

@@ -1,6 +1,7 @@
use std::cmp;
use super::*;
use {LogicalPosition, LogicalSize};
// Friendly neighborhood axis-aligned rectangle
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -15,12 +16,7 @@ impl AaRect {
pub fn new((x, y): (i32, i32), (width, height): (u32, u32)) -> Self {
let (x, y) = (x as i64, y as i64);
let (width, height) = (width as i64, height as i64);
AaRect {
x,
y,
width,
height,
}
AaRect { x, y, width, height }
}
pub fn contains_point(&self, x: i64, y: i64) -> bool {
@@ -40,9 +36,16 @@ impl AaRect {
}
}
#[derive(Debug, Default)]
#[derive(Debug)]
pub struct TranslatedCoords {
pub x_rel_root: c_int,
pub y_rel_root: c_int,
pub child: ffi::Window,
}
#[derive(Debug)]
pub struct Geometry {
pub root: xproto::Window,
pub root: ffi::Window,
// If you want positions relative to the root window, use translate_coords.
// Note that the overwhelming majority of window managers are reparenting WMs, thus the window
// ID we get from window creation is for a nested window used as the window's client area. If
@@ -62,25 +65,30 @@ pub struct Geometry {
#[derive(Debug, Clone)]
pub struct FrameExtents {
pub left: u32,
pub right: u32,
pub top: u32,
pub bottom: u32,
pub left: c_ulong,
pub right: c_ulong,
pub top: c_ulong,
pub bottom: c_ulong,
}
impl FrameExtents {
pub fn new(left: u32, right: u32, top: u32, bottom: u32) -> Self {
FrameExtents {
left,
right,
top,
bottom,
}
pub fn new(left: c_ulong, right: c_ulong, top: c_ulong, bottom: c_ulong) -> Self {
FrameExtents { left, right, top, bottom }
}
pub fn from_border(border: u32) -> Self {
pub fn from_border(border: c_ulong) -> Self {
Self::new(border, border, border, border)
}
pub fn as_logical(&self, factor: f64) -> LogicalFrameExtents {
let logicalize = |value: c_ulong| value as f64 / factor;
LogicalFrameExtents {
left: logicalize(self.left),
right: logicalize(self.right),
top: logicalize(self.top),
bottom: logicalize(self.bottom),
}
}
}
#[derive(Debug, Clone)]
@@ -91,7 +99,7 @@ pub struct LogicalFrameExtents {
pub bottom: f64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq)]
pub enum FrameExtentsHeuristicPath {
Supported,
UnsupportedNested,
@@ -108,58 +116,83 @@ impl FrameExtentsHeuristic {
pub fn inner_pos_to_outer(&self, x: i32, y: i32) -> (i32, i32) {
use self::FrameExtentsHeuristicPath::*;
if self.heuristic_path != UnsupportedBordered {
(
x - self.frame_extents.left as i32,
y - self.frame_extents.top as i32,
)
(x - self.frame_extents.left as i32, y - self.frame_extents.top as i32)
} else {
(x, y)
}
}
pub fn inner_pos_to_outer_logical(&self, mut logical: LogicalPosition, factor: f64) -> LogicalPosition {
use self::FrameExtentsHeuristicPath::*;
if self.heuristic_path != UnsupportedBordered {
let frame_extents = self.frame_extents.as_logical(factor);
logical.x -= frame_extents.left;
logical.y -= frame_extents.top;
}
logical
}
pub fn inner_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) {
(
width.saturating_add(
self.frame_extents
.left
.saturating_add(self.frame_extents.right) as _,
self.frame_extents.left.saturating_add(self.frame_extents.right) as u32
),
height.saturating_add(
self.frame_extents
.top
.saturating_add(self.frame_extents.bottom) as _,
self.frame_extents.top.saturating_add(self.frame_extents.bottom) as u32
),
)
}
pub fn inner_size_to_outer_logical(&self, mut logical: LogicalSize, factor: f64) -> LogicalSize {
let frame_extents = self.frame_extents.as_logical(factor);
logical.width += frame_extents.left + frame_extents.right;
logical.height += frame_extents.top + frame_extents.bottom;
logical
}
}
impl XConnection {
// This is adequate for inner_position
pub fn translate_coords(
&self,
window: xproto::Window,
root: xproto::Window,
) -> Result<xproto::TranslateCoordinatesReply, X11Error> {
self.xcb_connection()
.translate_coordinates(window, root, 0, 0)?
.reply()
.map_err(Into::into)
// This is adequate for get_inner_position
pub fn translate_coords(&self, window: ffi::Window, root: ffi::Window) -> Result<TranslatedCoords, XError> {
let mut translated_coords: TranslatedCoords = unsafe { mem::uninitialized() };
unsafe {
(self.xlib.XTranslateCoordinates)(
self.display,
window,
root,
0,
0,
&mut translated_coords.x_rel_root,
&mut translated_coords.y_rel_root,
&mut translated_coords.child,
);
}
//println!("XTranslateCoordinates coords:{:?}", translated_coords);
self.check_errors().map(|_| translated_coords)
}
// This is adequate for inner_size
pub fn get_geometry(
&self,
window: xproto::Window,
) -> Result<xproto::GetGeometryReply, X11Error> {
self.xcb_connection()
.get_geometry(window)?
.reply()
.map_err(Into::into)
// This is adequate for get_inner_size
pub fn get_geometry(&self, window: ffi::Window) -> Result<Geometry, XError> {
let mut geometry: Geometry = unsafe { mem::uninitialized() };
let _status = unsafe {
(self.xlib.XGetGeometry)(
self.display,
window,
&mut geometry.root,
&mut geometry.x_rel_parent,
&mut geometry.y_rel_parent,
&mut geometry.width,
&mut geometry.height,
&mut geometry.border,
&mut geometry.depth,
)
};
//println!("XGetGeometry geo:{:?}", geometry);
self.check_errors().map(|_| geometry)
}
fn get_frame_extents(&self, window: xproto::Window) -> Option<FrameExtents> {
let atoms = self.atoms();
let extents_atom = atoms[_NET_FRAME_EXTENTS];
fn get_frame_extents(&self, window: ffi::Window) -> Option<FrameExtents> {
let extents_atom = unsafe { self.get_atom_unchecked(b"_NET_FRAME_EXTENTS\0") };
if !hint_is_supported(extents_atom) {
return None;
@@ -168,13 +201,11 @@ impl XConnection {
// Of the WMs tested, xmonad, i3, dwm, IceWM (1.3.x and earlier), and blackbox don't
// support this. As this is part of EWMH (Extended Window Manager Hints), it's likely to
// be unsupported by many smaller WMs.
let extents: Option<Vec<u32>> = self
.get_property(
window,
extents_atom,
xproto::Atom::from(xproto::AtomEnum::CARDINAL),
)
.ok();
let extents: Option<Vec<c_ulong>> = self.get_property(
window,
extents_atom,
ffi::XA_CARDINAL,
).ok();
extents.and_then(|extents| {
if extents.len() >= 4 {
@@ -190,35 +221,50 @@ impl XConnection {
})
}
pub fn is_top_level(&self, window: xproto::Window, root: xproto::Window) -> Option<bool> {
let atoms = self.atoms();
let client_list_atom = atoms[_NET_CLIENT_LIST];
pub fn is_top_level(&self, window: ffi::Window, root: ffi::Window) -> Option<bool> {
let client_list_atom = unsafe { self.get_atom_unchecked(b"_NET_CLIENT_LIST\0") };
if !hint_is_supported(client_list_atom) {
return None;
}
let client_list: Option<Vec<xproto::Window>> = self
.get_property(
root,
client_list_atom,
xproto::Atom::from(xproto::AtomEnum::WINDOW),
)
.ok();
let client_list: Option<Vec<ffi::Window>> = self.get_property(
root,
client_list_atom,
ffi::XA_WINDOW,
).ok();
client_list.map(|client_list| client_list.contains(&(window as xproto::Window)))
client_list.map(|client_list| client_list.contains(&window))
}
fn get_parent_window(&self, window: xproto::Window) -> Result<xproto::Window, X11Error> {
let parent = self.xcb_connection().query_tree(window)?.reply()?.parent;
Ok(parent)
fn get_parent_window(&self, window: ffi::Window) -> Result<ffi::Window, XError> {
let parent = unsafe {
let mut root: ffi::Window = mem::uninitialized();
let mut parent: ffi::Window = mem::uninitialized();
let mut children: *mut ffi::Window = ptr::null_mut();
let mut nchildren: c_uint = mem::uninitialized();
// What's filled into `parent` if `window` is the root window?
let _status = (self.xlib.XQueryTree)(
self.display,
window,
&mut root,
&mut parent,
&mut children,
&mut nchildren,
);
// The list of children isn't used
if children != ptr::null_mut() {
(self.xlib.XFree)(children as *mut _);
}
parent
};
self.check_errors().map(|_| parent)
}
fn climb_hierarchy(
&self,
window: xproto::Window,
root: xproto::Window,
) -> Result<xproto::Window, X11Error> {
fn climb_hierarchy(&self, window: ffi::Window, root: ffi::Window) -> Result<ffi::Window, XError> {
let mut outer_window = window;
loop {
let candidate = self.get_parent_window(outer_window)?;
@@ -230,11 +276,7 @@ impl XConnection {
Ok(outer_window)
}
pub fn get_frame_extents_heuristic(
&self,
window: xproto::Window,
root: xproto::Window,
) -> FrameExtentsHeuristic {
pub fn get_frame_extents_heuristic(&self, window: ffi::Window, root: ffi::Window) -> FrameExtentsHeuristic {
use self::FrameExtentsHeuristicPath::*;
// Position relative to root window.
@@ -242,20 +284,19 @@ impl XConnection {
// isn't nested are outlined in the comments throghout this function, but in addition to
// that, fullscreen windows often aren't nested.
let (inner_y_rel_root, child) = {
let coords = self
.translate_coords(window, root)
.expect("Failed to translate window coordinates");
(coords.dst_y, coords.child)
let coords = self.translate_coords(window, root).expect("Failed to translate window coordinates");
(
coords.y_rel_root,
coords.child,
)
};
let (width, height, border) = {
let inner_geometry = self
.get_geometry(window)
.expect("Failed to get inner window geometry");
let inner_geometry = self.get_geometry(window).expect("Failed to get inner window geometry");
(
inner_geometry.width,
inner_geometry.height,
inner_geometry.border_width,
inner_geometry.border,
)
};
@@ -302,15 +343,11 @@ impl XConnection {
// If the position value we have is for a nested window used as the client area, we'll
// just climb up the hierarchy and get the geometry of the outermost window we're
// nested in.
let outer_window = self
.climb_hierarchy(window, root)
.expect("Failed to climb window hierarchy");
let outer_window = self.climb_hierarchy(window, root).expect("Failed to climb window hierarchy");
let (outer_y, outer_width, outer_height) = {
let outer_geometry = self
.get_geometry(outer_window)
.expect("Failed to get outer window geometry");
let outer_geometry = self.get_geometry(outer_window).expect("Failed to get outer window geometry");
(
outer_geometry.y,
outer_geometry.y_rel_parent,
outer_geometry.width,
outer_geometry.height,
)
@@ -318,16 +355,21 @@ impl XConnection {
// Since we have the geometry of the outermost window and the geometry of the client
// area, we can figure out what's in between.
let diff_x = outer_width.saturating_sub(width) as u32;
let diff_y = outer_height.saturating_sub(height) as u32;
let offset_y = inner_y_rel_root.saturating_sub(outer_y) as u32;
let diff_x = outer_width.saturating_sub(width);
let diff_y = outer_height.saturating_sub(height);
let offset_y = inner_y_rel_root.saturating_sub(outer_y) as c_uint;
let left = diff_x / 2;
let right = left;
let top = offset_y;
let bottom = diff_y.saturating_sub(offset_y);
let frame_extents = FrameExtents::new(left, right, top, bottom);
let frame_extents = FrameExtents::new(
left.into(),
right.into(),
top.into(),
bottom.into(),
);
FrameExtentsHeuristic {
frame_extents,
heuristic_path: UnsupportedNested,

View File

@@ -0,0 +1,236 @@
use std::sync::Arc;
use super::*;
pub const MWM_HINTS_DECORATIONS: c_ulong = 2;
#[derive(Debug)]
pub enum StateOperation {
Remove = 0, // _NET_WM_STATE_REMOVE
Add = 1, // _NET_WM_STATE_ADD
Toggle = 2, // _NET_WM_STATE_TOGGLE
}
impl From<bool> for StateOperation {
fn from(op: bool) -> Self {
if op {
StateOperation::Add
} else {
StateOperation::Remove
}
}
}
/// X window type. Maps directly to
/// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum WindowType {
/// A desktop feature. This can include a single window containing desktop icons with the same dimensions as the
/// screen, allowing the desktop environment to have full control of the desktop, without the need for proxying
/// root window clicks.
Desktop,
/// A dock or panel feature. Typically a Window Manager would keep such windows on top of all other windows.
Dock,
/// Toolbar windows. "Torn off" from the main application.
Toolbar,
/// Pinnable menu windows. "Torn off" from the main application.
Menu,
/// A small persistent utility window, such as a palette or toolbox.
Utility,
/// The window is a splash screen displayed as an application is starting up.
Splash,
/// This is a dialog window.
Dialog,
/// A dropdown menu that usually appears when the user clicks on an item in a menu bar.
/// This property is typically used on override-redirect windows.
DropdownMenu,
/// A popup menu that usually appears when the user right clicks on an object.
/// This property is typically used on override-redirect windows.
PopupMenu,
/// A tooltip window. Usually used to show additional information when hovering over an object with the cursor.
/// This property is typically used on override-redirect windows.
Tooltip,
/// The window is a notification.
/// This property is typically used on override-redirect windows.
Notification,
/// This should be used on the windows that are popped up by combo boxes.
/// This property is typically used on override-redirect windows.
Combo,
/// This indicates the the window is being dragged.
/// This property is typically used on override-redirect windows.
Dnd,
/// This is a normal, top-level window.
Normal,
}
impl Default for WindowType {
fn default() -> Self {
WindowType::Normal
}
}
impl WindowType {
pub(crate) fn as_atom(&self, xconn: &Arc<XConnection>) -> ffi::Atom {
use self::WindowType::*;
let atom_name: &[u8] = match self {
&Desktop => b"_NET_WM_WINDOW_TYPE_DESKTOP\0",
&Dock => b"_NET_WM_WINDOW_TYPE_DOCK\0",
&Toolbar => b"_NET_WM_WINDOW_TYPE_TOOLBAR\0",
&Menu => b"_NET_WM_WINDOW_TYPE_MENU\0",
&Utility => b"_NET_WM_WINDOW_TYPE_UTILITY\0",
&Splash => b"_NET_WM_WINDOW_TYPE_SPLASH\0",
&Dialog => b"_NET_WM_WINDOW_TYPE_DIALOG\0",
&DropdownMenu => b"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0",
&PopupMenu => b"_NET_WM_WINDOW_TYPE_POPUP_MENU\0",
&Tooltip => b"_NET_WM_WINDOW_TYPE_TOOLTIP\0",
&Notification => b"_NET_WM_WINDOW_TYPE_NOTIFICATION\0",
&Combo => b"_NET_WM_WINDOW_TYPE_COMBO\0",
&Dnd => b"_NET_WM_WINDOW_TYPE_DND\0",
&Normal => b"_NET_WM_WINDOW_TYPE_NORMAL\0",
};
unsafe { xconn.get_atom_unchecked(atom_name) }
}
}
pub struct NormalHints<'a> {
size_hints: XSmartPointer<'a, ffi::XSizeHints>,
}
impl<'a> NormalHints<'a> {
pub fn new(xconn: &'a XConnection) -> Self {
NormalHints { size_hints: xconn.alloc_size_hints() }
}
pub fn has_flag(&self, flag: c_long) -> bool {
has_flag(self.size_hints.flags, flag)
}
fn getter(&self, flag: c_long, field1: &c_int, field2: &c_int) -> Option<(u32, u32)> {
if self.has_flag(flag) {
Some((*field1 as _, *field2 as _))
} else {
None
}
}
pub fn get_size(&self) -> Option<(u32, u32)> {
self.getter(ffi::PSize, &self.size_hints.width, &self.size_hints.height)
}
// WARNING: This hint is obsolete
pub fn set_size(&mut self, size: Option<(u32, u32)>) {
if let Some((width, height)) = size {
self.size_hints.flags |= ffi::PSize;
self.size_hints.width = width as c_int;
self.size_hints.height = height as c_int;
} else {
self.size_hints.flags &= !ffi::PSize;
}
}
pub fn get_max_size(&self) -> Option<(u32, u32)> {
self.getter(ffi::PMaxSize, &self.size_hints.max_width, &self.size_hints.max_height)
}
pub fn set_max_size(&mut self, max_size: Option<(u32, u32)>) {
if let Some((max_width, max_height)) = max_size {
self.size_hints.flags |= ffi::PMaxSize;
self.size_hints.max_width = max_width as c_int;
self.size_hints.max_height = max_height as c_int;
} else {
self.size_hints.flags &= !ffi::PMaxSize;
}
}
pub fn get_min_size(&self) -> Option<(u32, u32)> {
self.getter(ffi::PMinSize, &self.size_hints.min_width, &self.size_hints.min_height)
}
pub fn set_min_size(&mut self, min_size: Option<(u32, u32)>) {
if let Some((min_width, min_height)) = min_size {
self.size_hints.flags |= ffi::PMinSize;
self.size_hints.min_width = min_width as c_int;
self.size_hints.min_height = min_height as c_int;
} else {
self.size_hints.flags &= !ffi::PMinSize;
}
}
pub fn get_resize_increments(&self) -> Option<(u32, u32)> {
self.getter(ffi::PResizeInc, &self.size_hints.width_inc, &self.size_hints.height_inc)
}
pub fn set_resize_increments(&mut self, resize_increments: Option<(u32, u32)>) {
if let Some((width_inc, height_inc)) = resize_increments {
self.size_hints.flags |= ffi::PResizeInc;
self.size_hints.width_inc = width_inc as c_int;
self.size_hints.height_inc = height_inc as c_int;
} else {
self.size_hints.flags &= !ffi::PResizeInc;
}
}
pub fn get_base_size(&self) -> Option<(u32, u32)> {
self.getter(ffi::PBaseSize, &self.size_hints.base_width, &self.size_hints.base_height)
}
pub fn set_base_size(&mut self, base_size: Option<(u32, u32)>) {
if let Some((base_width, base_height)) = base_size {
self.size_hints.flags |= ffi::PBaseSize;
self.size_hints.base_width = base_width as c_int;
self.size_hints.base_height = base_height as c_int;
} else {
self.size_hints.flags &= !ffi::PBaseSize;
}
}
}
impl XConnection {
pub fn get_wm_hints(&self, window: ffi::Window) -> Result<XSmartPointer<ffi::XWMHints>, XError> {
let wm_hints = unsafe { (self.xlib.XGetWMHints)(self.display, window) };
self.check_errors()?;
let wm_hints = if wm_hints.is_null() {
self.alloc_wm_hints()
} else {
XSmartPointer::new(self, wm_hints).unwrap()
};
Ok(wm_hints)
}
pub fn set_wm_hints(&self, window: ffi::Window, wm_hints: XSmartPointer<ffi::XWMHints>) -> Flusher {
unsafe {
(self.xlib.XSetWMHints)(
self.display,
window,
wm_hints.ptr,
);
}
Flusher::new(self)
}
pub fn get_normal_hints(&self, window: ffi::Window) -> Result<NormalHints, XError> {
let size_hints = self.alloc_size_hints();
let mut supplied_by_user: c_long = unsafe { mem::uninitialized() };
unsafe {
(self.xlib.XGetWMNormalHints)(
self.display,
window,
size_hints.ptr,
&mut supplied_by_user,
);
}
self.check_errors().map(|_| NormalHints { size_hints })
}
pub fn set_normal_hints(&self, window: ffi::Window, normal_hints: NormalHints) -> Flusher {
unsafe {
(self.xlib.XSetWMNormalHints)(
self.display,
window,
normal_hints.size_hints.ptr,
);
}
Flusher::new(self)
}
}

View File

@@ -1,7 +1,5 @@
#![allow(clippy::assertions_on_constants)]
use {Icon, Pixel, PIXEL_SIZE};
use super::*;
use crate::icon::{Pixel, RgbaIcon, PIXEL_SIZE};
impl Pixel {
pub fn to_packed_argb(&self) -> Cardinal {
@@ -18,7 +16,7 @@ impl Pixel {
}
}
impl RgbaIcon {
impl Icon {
pub(crate) fn to_cardinals(&self) -> Vec<Cardinal> {
assert_eq!(self.rgba.len() % PIXEL_SIZE, 0);
let pixel_count = self.rgba.len() / PIXEL_SIZE;
@@ -28,7 +26,7 @@ impl RgbaIcon {
data.push(self.height as Cardinal);
let pixels = self.rgba.as_ptr() as *const Pixel;
for pixel_index in 0..pixel_count {
let pixel = unsafe { &*pixels.add(pixel_index) };
let pixel = unsafe { &*pixels.offset(pixel_index as isize) };
data.push(pixel.to_packed_argb());
}
data

View File

@@ -0,0 +1,159 @@
use std::str;
use super::*;
use events::ModifiersState;
pub const VIRTUAL_CORE_POINTER: c_int = 2;
pub const VIRTUAL_CORE_KEYBOARD: c_int = 3;
// A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to
// re-allocate (and make another round-trip) in the *vast* majority of cases.
// To test if `lookup_utf8` works correctly, set this to 1.
const TEXT_BUFFER_SIZE: usize = 1024;
impl From<ffi::XIModifierState> for ModifiersState {
fn from(mods: ffi::XIModifierState) -> Self {
let state = mods.effective as c_uint;
ModifiersState {
alt: state & ffi::Mod1Mask != 0,
shift: state & ffi::ShiftMask != 0,
ctrl: state & ffi::ControlMask != 0,
logo: state & ffi::Mod4Mask != 0,
}
}
}
pub struct PointerState<'a> {
xconn: &'a XConnection,
root: ffi::Window,
child: ffi::Window,
pub root_x: c_double,
pub root_y: c_double,
win_x: c_double,
win_y: c_double,
buttons: ffi::XIButtonState,
modifiers: ffi::XIModifierState,
group: ffi::XIGroupState,
relative_to_window: bool,
}
impl<'a> PointerState<'a> {
pub fn get_modifier_state(&self) -> ModifiersState {
self.modifiers.into()
}
}
impl<'a> Drop for PointerState<'a> {
fn drop(&mut self) {
if !self.buttons.mask.is_null() {
unsafe {
// This is why you need to read the docs carefully...
(self.xconn.xlib.XFree)(self.buttons.mask as _);
}
}
}
}
impl XConnection {
pub fn select_xinput_events(&self, window: c_ulong, device_id: c_int, mask: i32) -> Flusher {
let mut event_mask = ffi::XIEventMask {
deviceid: device_id,
mask: &mask as *const _ as *mut c_uchar,
mask_len: mem::size_of_val(&mask) as c_int,
};
unsafe {
(self.xinput2.XISelectEvents)(
self.display,
window,
&mut event_mask as *mut ffi::XIEventMask,
1, // number of masks to read from pointer above
);
}
Flusher::new(self)
}
#[allow(dead_code)]
pub fn select_xkb_events(&self, device_id: c_uint, mask: c_ulong) -> Option<Flusher> {
let status = unsafe {
(self.xlib.XkbSelectEvents)(
self.display,
device_id,
mask,
mask,
)
};
if status == ffi::True {
Some(Flusher::new(self))
} else {
None
}
}
pub fn query_pointer(&self, window: ffi::Window, device_id: c_int) -> Result<PointerState, XError> {
unsafe {
let mut pointer_state: PointerState = mem::uninitialized();
pointer_state.xconn = self;
pointer_state.relative_to_window = (self.xinput2.XIQueryPointer)(
self.display,
device_id,
window,
&mut pointer_state.root,
&mut pointer_state.child,
&mut pointer_state.root_x,
&mut pointer_state.root_y,
&mut pointer_state.win_x,
&mut pointer_state.win_y,
&mut pointer_state.buttons,
&mut pointer_state.modifiers,
&mut pointer_state.group,
) == ffi::True;
if let Err(err) = self.check_errors() {
// Running the destrutor would be bad news for us...
mem::forget(pointer_state);
Err(err)
} else {
Ok(pointer_state)
}
}
}
fn lookup_utf8_inner(
&self,
ic: ffi::XIC,
key_event: &mut ffi::XKeyEvent,
buffer: &mut [u8],
) -> (ffi::KeySym, ffi::Status, c_int) {
let mut keysym: ffi::KeySym = 0;
let mut status: ffi::Status = 0;
let count = unsafe {
(self.xlib.Xutf8LookupString)(
ic,
key_event,
buffer.as_mut_ptr() as *mut c_char,
buffer.len() as c_int,
&mut keysym,
&mut status,
)
};
(keysym, status, count)
}
pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String {
let mut buffer: [u8; TEXT_BUFFER_SIZE] = unsafe { mem::uninitialized() };
let (_, status, count) = self.lookup_utf8_inner(ic, key_event, &mut buffer);
// The buffer overflowed, so we'll make a new one on the heap.
if status == ffi::XBufferOverflow {
let mut buffer = Vec::with_capacity(count as usize);
unsafe { buffer.set_len(count as usize) };
let (_, _, new_count) = self.lookup_utf8_inner(ic, key_event, &mut buffer);
debug_assert_eq!(count, new_count);
str::from_utf8(&buffer[..count as usize])
.unwrap_or("")
.to_string()
} else {
str::from_utf8(&buffer[..count as usize])
.unwrap_or("")
.to_string()
}
}
}

View File

@@ -2,7 +2,7 @@ use std::ops::{Deref, DerefMut};
use super::*;
pub(crate) struct XSmartPointer<'a, T> {
pub struct XSmartPointer<'a, T> {
xconn: &'a XConnection,
pub ptr: *mut T,
}
@@ -12,7 +12,10 @@ impl<'a, T> XSmartPointer<'a, T> {
// Returns None if ptr is null.
pub fn new(xconn: &'a XConnection, ptr: *mut T) -> Option<Self> {
if !ptr.is_null() {
Some(XSmartPointer { xconn, ptr })
Some(XSmartPointer {
xconn,
ptr,
})
} else {
None
}
@@ -40,3 +43,20 @@ impl<'a, T> Drop for XSmartPointer<'a, T> {
}
}
}
impl XConnection {
pub fn alloc_class_hint(&self) -> XSmartPointer<ffi::XClassHint> {
XSmartPointer::new(self, unsafe { (self.xlib.XAllocClassHint)() })
.expect("`XAllocClassHint` returned null; out of memory")
}
pub fn alloc_size_hints(&self) -> XSmartPointer<ffi::XSizeHints> {
XSmartPointer::new(self, unsafe { (self.xlib.XAllocSizeHints)() })
.expect("`XAllocSizeHints` returned null; out of memory")
}
pub fn alloc_wm_hints(&self) -> XSmartPointer<ffi::XWMHints> {
XSmartPointer::new(self, unsafe { (self.xlib.XAllocWMHints)() })
.expect("`XAllocWMHints` returned null; out of memory")
}
}

View File

@@ -1,28 +1,41 @@
// Welcome to the util module, where we try to keep you from shooting yourself in the foot.
// *results may vary
mod atom;
mod client_msg;
mod cursor;
mod format;
mod geometry;
mod hint;
mod icon;
mod input;
pub mod keys;
pub(crate) mod memory;
mod memory;
mod randr;
mod window_property;
mod wm;
pub use self::{cursor::*, geometry::*, hint::*, input::*, window_property::*, wm::*};
pub use self::atom::*;
pub use self::client_msg::*;
pub use self::format::*;
pub use self::geometry::*;
pub use self::hint::*;
pub use self::icon::*;
pub use self::input::*;
pub use self::memory::*;
pub use self::randr::*;
pub use self::window_property::*;
pub use self::wm::*;
use std::{
mem::{self, MaybeUninit},
ops::BitAnd,
os::raw::*,
};
use std::mem;
use std::ptr;
use std::ops::BitAnd;
use std::os::raw::*;
use super::{atoms::*, ffi, VoidCookie, X11Error, XConnection, XError};
use x11rb::protocol::xproto::{self, ConnectionExt as _};
use super::{ffi, XConnection, XError};
pub fn reinterpret<'a, A, B>(a: &'a A) -> &'a B {
let b_ptr = a as *const _ as *const B;
unsafe { &*b_ptr }
}
pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
let wrapped = Some(value);
@@ -35,12 +48,36 @@ pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
}
pub fn has_flag<T>(bitset: T, flag: T) -> bool
where
T: Copy + PartialEq + BitAnd<T, Output = T>,
where T:
Copy + PartialEq + BitAnd<T, Output = T>
{
bitset & flag == flag
}
#[must_use = "This request was made asynchronously, and is still in the output buffer. You must explicitly choose to either `.flush()` (empty the output buffer, sending the request now) or `.queue()` (wait to send the request, allowing you to continue to add more requests without additional round-trips). For more information, see the documentation for `util::flush_requests`."]
pub struct Flusher<'a> {
xconn: &'a XConnection,
}
impl<'a> Flusher<'a> {
pub fn new(xconn: &'a XConnection) -> Self {
Flusher { xconn }
}
// "I want this request sent now!"
pub fn flush(self) -> Result<(), XError> {
self.xconn.flush_requests()
}
// "I want the response now too!"
pub fn sync(self) -> Result<(), XError> {
self.xconn.sync_with_server()
}
// "I'm aware that this request hasn't been sent, and I'm okay with waiting."
pub fn queue(self) {}
}
impl XConnection {
// This is impoartant, so pay attention!
// Xlib has an output buffer, and tries to hide the async nature of X from you.

View File

@@ -0,0 +1,134 @@
use std::{env, slice};
use std::str::FromStr;
use validate_hidpi_factor;
use super::*;
pub fn calc_dpi_factor(
(width_px, height_px): (u32, u32),
(width_mm, height_mm): (u64, u64),
) -> f64 {
// Override DPI if `WINIT_HIDPI_FACTOR` variable is set
let dpi_override = env::var("WINIT_HIDPI_FACTOR")
.ok()
.and_then(|var| f64::from_str(&var).ok());
if let Some(dpi_override) = dpi_override {
if !validate_hidpi_factor(dpi_override) {
panic!(
"`WINIT_HIDPI_FACTOR` invalid; DPI factors must be normal floats greater than 0. Got `{}`",
dpi_override,
);
}
return dpi_override;
}
// See http://xpra.org/trac/ticket/728 for more information.
if width_mm == 0 || height_mm == 0 {
warn!("XRandR reported that the display's 0mm in size, which is certifiably insane");
return 1.0;
}
let ppmm = (
(width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64)
).sqrt();
// Quantize 1/12 step size
let dpi_factor = ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0);
assert!(validate_hidpi_factor(dpi_factor));
dpi_factor
}
pub enum MonitorRepr {
Monitor(*mut ffi::XRRMonitorInfo),
Crtc(*mut ffi::XRRCrtcInfo),
}
impl MonitorRepr {
pub unsafe fn get_output(&self) -> ffi::RROutput {
match *self {
// Same member names, but different locations within the struct...
MonitorRepr::Monitor(monitor) => *((*monitor).outputs.offset(0)),
MonitorRepr::Crtc(crtc) => *((*crtc).outputs.offset(0)),
}
}
pub unsafe fn get_dimensions(&self) -> (u32, u32) {
match *self {
MonitorRepr::Monitor(monitor) => ((*monitor).width as u32, (*monitor).height as u32),
MonitorRepr::Crtc(crtc) => ((*crtc).width as u32, (*crtc).height as u32),
}
}
pub unsafe fn get_position(&self) -> (i32, i32) {
match *self {
MonitorRepr::Monitor(monitor) => ((*monitor).x as i32, (*monitor).y as i32),
MonitorRepr::Crtc(crtc) => ((*crtc).x as i32, (*crtc).y as i32),
}
}
}
impl From<*mut ffi::XRRMonitorInfo> for MonitorRepr {
fn from(monitor: *mut ffi::XRRMonitorInfo) -> Self {
MonitorRepr::Monitor(monitor)
}
}
impl From<*mut ffi::XRRCrtcInfo> for MonitorRepr {
fn from(crtc: *mut ffi::XRRCrtcInfo) -> Self {
MonitorRepr::Crtc(crtc)
}
}
impl XConnection {
// Retrieve DPI from Xft.dpi property
pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
(self.xlib.XrmInitialize)();
let resource_manager_str = (self.xlib.XResourceManagerString)(self.display);
if resource_manager_str == ptr::null_mut() {
return None;
}
if let Ok(res) = ::std::ffi::CStr::from_ptr(resource_manager_str).to_str() {
let name : &str = "Xft.dpi:\t";
for pair in res.split("\n") {
if pair.starts_with(&name) {
let res = &pair[name.len()..];
return f64::from_str(&res).ok();
}
}
}
None
}
pub unsafe fn get_output_info(
&self,
resources: *mut ffi::XRRScreenResources,
repr: &MonitorRepr,
) -> Option<(String, f64)> {
let output_info = (self.xrandr.XRRGetOutputInfo)(
self.display,
resources,
repr.get_output(),
);
if output_info.is_null() {
// When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display)
// it's possible for it to return null.
// https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=816596
let _ = self.check_errors(); // discard `BadRROutput` error
return None;
}
let name_slice = slice::from_raw_parts(
(*output_info).name as *mut u8,
(*output_info).nameLen as usize,
);
let name = String::from_utf8_lossy(name_slice).into();
let hidpi_factor = if let Some(dpi) = self.get_xft_dpi() {
dpi / 96.
} else {
calc_dpi_factor(
repr.get_dimensions(),
((*output_info).mm_width as u64, (*output_info).mm_height as u64),
)
};
(self.xrandr.XRRFreeOutputInfo)(output_info);
Some((name, hidpi_factor))
}
}

View File

@@ -0,0 +1,144 @@
use std;
use super::*;
pub type Cardinal = c_long;
pub const CARDINAL_SIZE: usize = mem::size_of::<c_long>();
#[derive(Debug, Clone)]
pub enum GetPropertyError {
XError(XError),
TypeMismatch(ffi::Atom),
FormatMismatch(c_int),
NothingAllocated,
}
impl GetPropertyError {
pub fn is_actual_property_type(&self, t: ffi::Atom) -> bool {
if let GetPropertyError::TypeMismatch(actual_type) = *self {
actual_type == t
} else {
false
}
}
}
// Number of 32-bit chunks to retrieve per iteration of get_property's inner loop.
// To test if `get_property` works correctly, set this to 1.
const PROPERTY_BUFFER_SIZE: c_long = 1024; // 4k of RAM ought to be enough for anyone!
#[derive(Debug)]
pub enum PropMode {
Replace = ffi::PropModeReplace as isize,
Prepend = ffi::PropModePrepend as isize,
Append = ffi::PropModeAppend as isize,
}
impl XConnection {
pub fn get_property<T: Formattable>(
&self,
window: c_ulong,
property: ffi::Atom,
property_type: ffi::Atom,
) -> Result<Vec<T>, GetPropertyError> {
let mut data = Vec::new();
let mut offset = 0;
let mut done = false;
while !done {
unsafe {
let mut actual_type: ffi::Atom = mem::uninitialized();
let mut actual_format: c_int = mem::uninitialized();
let mut quantity_returned: c_ulong = mem::uninitialized();
let mut bytes_after: c_ulong = mem::uninitialized();
let mut buf: *mut c_uchar = ptr::null_mut();
(self.xlib.XGetWindowProperty)(
self.display,
window,
property,
// This offset is in terms of 32-bit chunks.
offset,
// This is the quanity of 32-bit chunks to receive at once.
PROPERTY_BUFFER_SIZE,
ffi::False,
property_type,
&mut actual_type,
&mut actual_format,
// This is the quantity of items we retrieved in our format, NOT of 32-bit chunks!
&mut quantity_returned,
// ...and this is a quantity of bytes. So, this function deals in 3 different units.
&mut bytes_after,
&mut buf,
);
if let Err(e) = self.check_errors() {
return Err(GetPropertyError::XError(e));
}
if actual_type != property_type {
return Err(GetPropertyError::TypeMismatch(actual_type));
}
let format_mismatch = Format::from_format(actual_format as _) != Some(T::FORMAT);
if format_mismatch {
return Err(GetPropertyError::FormatMismatch(actual_format));
}
if !buf.is_null() {
offset += PROPERTY_BUFFER_SIZE;
let new_data = std::slice::from_raw_parts(
buf as *mut T,
quantity_returned as usize,
);
/*println!(
"XGetWindowProperty prop:{:?} fmt:{:02} len:{:02} off:{:02} out:{:02}, buf:{:?}",
property,
mem::size_of::<T>() * 8,
data.len(),
offset,
quantity_returned,
new_data,
);*/
data.extend_from_slice(&new_data);
// Fun fact: XGetWindowProperty allocates one extra byte at the end.
(self.xlib.XFree)(buf as _); // Don't try to access new_data after this.
} else {
return Err(GetPropertyError::NothingAllocated);
}
done = bytes_after == 0;
}
}
Ok(data)
}
pub fn change_property<'a, T: Formattable>(
&'a self,
window: c_ulong,
property: ffi::Atom,
property_type: ffi::Atom,
mode: PropMode,
new_value: &[T],
) -> Flusher<'a> {
debug_assert_eq!(mem::size_of::<T>(), T::FORMAT.get_actual_size());
unsafe {
(self.xlib.XChangeProperty)(
self.display,
window,
property,
property_type,
T::FORMAT as c_int,
mode as c_int,
new_value.as_ptr() as *const c_uchar,
new_value.len() as c_int,
);
}
/*println!(
"XChangeProperty prop:{:?} val:{:?}",
property,
new_value,
);*/
Flusher::new(self)
}
}

View File

@@ -1,31 +1,19 @@
use std::sync::Mutex;
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use super::*;
// https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html#idm46075117309248
pub const MOVERESIZE_TOPLEFT: isize = 0;
pub const MOVERESIZE_TOP: isize = 1;
pub const MOVERESIZE_TOPRIGHT: isize = 2;
pub const MOVERESIZE_RIGHT: isize = 3;
pub const MOVERESIZE_BOTTOMRIGHT: isize = 4;
pub const MOVERESIZE_BOTTOM: isize = 5;
pub const MOVERESIZE_BOTTOMLEFT: isize = 6;
pub const MOVERESIZE_LEFT: isize = 7;
pub const MOVERESIZE_MOVE: isize = 8;
// This info is global to the window manager.
static SUPPORTED_HINTS: Lazy<Mutex<Vec<xproto::Atom>>> =
Lazy::new(|| Mutex::new(Vec::with_capacity(0)));
static WM_NAME: Lazy<Mutex<Option<String>>> = Lazy::new(|| Mutex::new(None));
lazy_static! {
static ref SUPPORTED_HINTS: Mutex<Vec<ffi::Atom>> = Mutex::new(Vec::with_capacity(0));
static ref WM_NAME: Mutex<Option<String>> = Mutex::new(None);
}
pub fn hint_is_supported(hint: xproto::Atom) -> bool {
(*SUPPORTED_HINTS.lock().unwrap()).contains(&hint)
pub fn hint_is_supported(hint: ffi::Atom) -> bool {
(*SUPPORTED_HINTS.lock()).contains(&hint)
}
pub fn wm_name_is_one_of(names: &[&str]) -> bool {
if let Some(ref name) = *WM_NAME.lock().unwrap() {
if let Some(ref name) = *WM_NAME.lock() {
names.contains(&name.as_str())
} else {
false
@@ -33,27 +21,23 @@ pub fn wm_name_is_one_of(names: &[&str]) -> bool {
}
impl XConnection {
pub fn update_cached_wm_info(&self, root: xproto::Window) {
*SUPPORTED_HINTS.lock().unwrap() = self.get_supported_hints(root);
*WM_NAME.lock().unwrap() = self.get_wm_name(root);
pub fn update_cached_wm_info(&self, root: ffi::Window) {
*SUPPORTED_HINTS.lock() = self.get_supported_hints(root);
*WM_NAME.lock() = self.get_wm_name(root);
}
fn get_supported_hints(&self, root: xproto::Window) -> Vec<xproto::Atom> {
let atoms = self.atoms();
let supported_atom = atoms[_NET_SUPPORTED];
fn get_supported_hints(&self, root: ffi::Window) -> Vec<ffi::Atom> {
let supported_atom = unsafe { self.get_atom_unchecked(b"_NET_SUPPORTED\0") };
self.get_property(
root,
supported_atom,
xproto::Atom::from(xproto::AtomEnum::ATOM),
)
.unwrap_or_else(|_| Vec::with_capacity(0))
ffi::XA_ATOM,
).unwrap_or_else(|_| Vec::with_capacity(0))
}
#[allow(clippy::useless_conversion)]
fn get_wm_name(&self, root: xproto::Window) -> Option<String> {
let atoms = self.atoms();
let check_atom = atoms[_NET_SUPPORTING_WM_CHECK];
let wm_name_atom = atoms[_NET_WM_NAME];
fn get_wm_name(&self, root: ffi::Window) -> Option<String> {
let check_atom = unsafe { self.get_atom_unchecked(b"_NET_SUPPORTING_WM_CHECK\0") };
let wm_name_atom = unsafe { self.get_atom_unchecked(b"_NET_WM_NAME\0") };
// Mutter/Muffin/Budgie doesn't have _NET_SUPPORTING_WM_CHECK in its _NET_SUPPORTED, despite
// it working and being supported. This has been reported upstream, but due to the
@@ -77,29 +61,41 @@ impl XConnection {
// Querying this property on the root window will give us the ID of a child window created by
// the WM.
let root_window_wm_check = {
let result = self.get_property::<xproto::Window>(
let result = self.get_property(
root,
check_atom,
xproto::Atom::from(xproto::AtomEnum::WINDOW),
ffi::XA_WINDOW,
);
let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned());
let wm_check = result
.ok()
.and_then(|wm_check| wm_check.get(0).cloned());
wm_check?
if let Some(wm_check) = wm_check {
wm_check
} else {
return None;
}
};
// Querying the same property on the child window we were given, we should get this child
// window's ID again.
let child_window_wm_check = {
let result = self.get_property::<xproto::Window>(
root_window_wm_check.into(),
let result = self.get_property(
root_window_wm_check,
check_atom,
xproto::Atom::from(xproto::AtomEnum::WINDOW),
ffi::XA_WINDOW,
);
let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned());
let wm_check = result
.ok()
.and_then(|wm_check| wm_check.get(0).cloned());
wm_check?
if let Some(wm_check) = wm_check {
wm_check
} else {
return None;
}
};
// These values should be the same.
@@ -109,11 +105,13 @@ impl XConnection {
// All of that work gives us a window ID that we can get the WM name from.
let wm_name = {
let atoms = self.atoms();
let utf8_string_atom = atoms[UTF8_STRING];
let utf8_string_atom = unsafe { self.get_atom_unchecked(b"UTF8_STRING\0") };
let result =
self.get_property(root_window_wm_check.into(), wm_name_atom, utf8_string_atom);
let result = self.get_property(
root_window_wm_check,
wm_name_atom,
utf8_string_atom,
);
// IceWM requires this. IceWM was also the only WM tested that returns a null-terminated
// string. For more fun trivia, IceWM is also unique in including version and uname
@@ -122,22 +120,21 @@ impl XConnection {
// The unofficial 1.4 fork of IceWM still includes the extra details, but properly
// returns a UTF8 string that isn't null-terminated.
let no_utf8 = if let Err(ref err) = result {
err.is_actual_property_type(xproto::Atom::from(xproto::AtomEnum::STRING))
err.is_actual_property_type(ffi::XA_STRING)
} else {
false
};
if no_utf8 {
self.get_property(
root_window_wm_check.into(),
root_window_wm_check,
wm_name_atom,
xproto::Atom::from(xproto::AtomEnum::STRING),
ffi::XA_STRING,
)
} else {
result
}
}
.ok();
}.ok();
wm_name.and_then(|wm_name| String::from_utf8(wm_name).ok())
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,163 @@
use std::ptr;
use std::fmt;
use std::error::Error;
use std::os::raw::c_int;
use libc;
use parking_lot::Mutex;
use super::ffi;
/// A connection to an X server.
pub struct XConnection {
pub xlib: ffi::Xlib,
/// Exposes XRandR functions from version < 1.5
pub xrandr: ffi::Xrandr_2_2_0,
/// Exposes XRandR functions from version = 1.5
pub xrandr_1_5: Option<ffi::Xrandr>,
pub xcursor: ffi::Xcursor,
pub xinput2: ffi::XInput2,
pub xlib_xcb: ffi::Xlib_xcb,
pub display: *mut ffi::Display,
pub x11_fd: c_int,
pub latest_error: Mutex<Option<XError>>,
}
unsafe impl Send for XConnection {}
unsafe impl Sync for XConnection {}
pub type XErrorHandler = Option<unsafe extern fn(*mut ffi::Display, *mut ffi::XErrorEvent) -> libc::c_int>;
impl XConnection {
pub fn new(error_handler: XErrorHandler) -> Result<XConnection, XNotSupported> {
// opening the libraries
let xlib = ffi::Xlib::open()?;
let xcursor = ffi::Xcursor::open()?;
let xrandr = ffi::Xrandr_2_2_0::open()?;
let xrandr_1_5 = ffi::Xrandr::open().ok();
let xinput2 = ffi::XInput2::open()?;
let xlib_xcb = ffi::Xlib_xcb::open()?;
unsafe { (xlib.XInitThreads)() };
unsafe { (xlib.XSetErrorHandler)(error_handler) };
// calling XOpenDisplay
let display = unsafe {
let display = (xlib.XOpenDisplay)(ptr::null());
if display.is_null() {
return Err(XNotSupported::XOpenDisplayFailed);
}
display
};
// Get X11 socket file descriptor
let fd = unsafe {
(xlib.XConnectionNumber)(display)
};
Ok(XConnection {
xlib,
xrandr,
xrandr_1_5,
xcursor,
xinput2,
xlib_xcb,
display,
x11_fd: fd,
latest_error: Mutex::new(None),
})
}
/// Checks whether an error has been triggered by the previous function calls.
#[inline]
pub fn check_errors(&self) -> Result<(), XError> {
let error = self.latest_error.lock().take();
if let Some(error) = error {
Err(error)
} else {
Ok(())
}
}
/// Ignores any previous error.
#[inline]
pub fn ignore_error(&self) {
*self.latest_error.lock() = None;
}
}
impl fmt::Debug for XConnection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.display.fmt(f)
}
}
impl Drop for XConnection {
#[inline]
fn drop(&mut self) {
unsafe { (self.xlib.XCloseDisplay)(self.display) };
}
}
/// Error triggered by xlib.
#[derive(Debug, Clone)]
pub struct XError {
pub description: String,
pub error_code: u8,
pub request_code: u8,
pub minor_code: u8,
}
impl Error for XError {
#[inline]
fn description(&self) -> &str {
&self.description
}
}
impl fmt::Display for XError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(formatter, "X error: {} (code: {}, request code: {}, minor code: {})",
self.description, self.error_code, self.request_code, self.minor_code)
}
}
/// Error returned if this system doesn't have XLib or can't create an X connection.
#[derive(Clone, Debug)]
pub enum XNotSupported {
/// Failed to load one or several shared libraries.
LibraryOpenError(ffi::OpenError),
/// Connecting to the X server with `XOpenDisplay` failed.
XOpenDisplayFailed, // TODO: add better message
}
impl From<ffi::OpenError> for XNotSupported {
#[inline]
fn from(err: ffi::OpenError) -> XNotSupported {
XNotSupported::LibraryOpenError(err)
}
}
impl Error for XNotSupported {
#[inline]
fn description(&self) -> &str {
match *self {
XNotSupported::LibraryOpenError(_) => "Failed to load one of xlib's shared libraries",
XNotSupported::XOpenDisplayFailed => "Failed to open connection to X server",
}
}
#[inline]
fn cause(&self) -> Option<&Error> {
match *self {
XNotSupported::LibraryOpenError(ref err) => Some(err),
_ => None
}
}
}
impl fmt::Display for XNotSupported {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
formatter.write_str(self.description())
}
}

View File

@@ -0,0 +1,812 @@
use {ControlFlow, EventsLoopClosed};
use cocoa::{self, appkit, foundation};
use cocoa::appkit::{NSApplication, NSEvent, NSEventMask, NSEventModifierFlags, NSEventPhase, NSView, NSWindow};
use events::{self, ElementState, Event, TouchPhase, WindowEvent, DeviceEvent, ModifiersState, KeyboardInput};
use std::collections::VecDeque;
use std::sync::{Arc, Mutex, Weak};
use super::window::Window2;
use std;
use std::os::raw::*;
use super::DeviceId;
pub struct EventsLoop {
modifiers: Modifiers,
pub shared: Arc<Shared>,
}
// State shared between the `EventsLoop` and its registered windows.
pub struct Shared {
pub windows: Mutex<Vec<Weak<Window2>>>,
pub pending_events: Mutex<VecDeque<Event>>,
// The user event callback given via either of the `poll_events` or `run_forever` methods.
//
// We store the user's callback here so that it may be accessed by each of the window delegate
// callbacks (e.g. resize, close, etc) for the duration of a call to either of the
// `poll_events` or `run_forever` methods.
//
// This is *only* `Some` for the duration of a call to either of these methods and will be
// `None` otherwise.
user_callback: UserCallback,
}
#[derive(Clone)]
pub struct Proxy {}
struct Modifiers {
shift_pressed: bool,
ctrl_pressed: bool,
win_pressed: bool,
alt_pressed: bool,
}
// Wrapping the user callback in a type allows us to:
//
// - ensure the callback pointer is never accidentally cloned
// - ensure that only the `EventsLoop` can `store` and `drop` the callback pointer
// - Share access to the user callback with the NSWindow callbacks.
pub struct UserCallback {
mutex: Mutex<Option<*mut FnMut(Event)>>,
}
impl Shared {
pub fn new() -> Self {
Shared {
windows: Mutex::new(Vec::new()),
pending_events: Mutex::new(VecDeque::new()),
user_callback: UserCallback { mutex: Mutex::new(None) },
}
}
fn call_user_callback_with_pending_events(&self) {
loop {
let event = match self.pending_events.lock().unwrap().pop_front() {
Some(event) => event,
None => return,
};
unsafe {
self.user_callback.call_with_event(event);
}
}
}
// Calls the user callback if one exists.
//
// Otherwise, stores the event in the `pending_events` queue.
//
// This is necessary for the case when `WindowDelegate` callbacks are triggered during a call
// to the user's callback.
pub fn call_user_callback_with_event_or_store_in_pending(&self, event: Event) {
if self.user_callback.mutex.lock().unwrap().is_some() {
unsafe {
self.user_callback.call_with_event(event);
}
} else {
self.pending_events.lock().unwrap().push_back(event);
}
}
// Removes the window with the given `Id` from the `windows` list.
//
// This is called in response to `windowWillClose`.
pub fn find_and_remove_window(&self, id: super::window::Id) {
if let Ok(mut windows) = self.windows.lock() {
windows.retain(|w| match w.upgrade() {
Some(w) => w.id() != id,
None => false,
});
}
}
}
impl Modifiers {
pub fn new() -> Self {
Modifiers {
shift_pressed: false,
ctrl_pressed: false,
win_pressed: false,
alt_pressed: false,
}
}
}
impl UserCallback {
// Here we store user's `callback` behind the mutex so that they may be safely shared between
// each of the window delegates.
//
// In order to make sure that the pointer is always valid, we must manually guarantee that it
// is dropped before the callback itself is dropped. Thus, this should *only* be called at the
// beginning of a call to `poll_events` and `run_forever`, both of which *must* drop the
// callback at the end of their scope using the `drop` method.
fn store<F>(&self, callback: &mut F)
where F: FnMut(Event)
{
let trait_object = callback as &mut FnMut(Event);
let trait_object_ptr = trait_object as *const FnMut(Event) as *mut FnMut(Event);
*self.mutex.lock().unwrap() = Some(trait_object_ptr);
}
// Emits the given event via the user-given callback.
//
// This is unsafe as it requires dereferencing the pointer to the user-given callback. We
// guarantee this is safe by ensuring the `UserCallback` never lives longer than the user-given
// callback.
//
// Note that the callback may not always be `Some`. This is because some `NSWindowDelegate`
// callbacks can be triggered by means other than `NSApp().sendEvent`. For example, if a window
// is destroyed or created during a call to the user's callback, the `WindowDelegate` methods
// may be called with `windowShouldClose` or `windowDidResignKey`.
unsafe fn call_with_event(&self, event: Event) {
let callback = match self.mutex.lock().unwrap().take() {
Some(callback) => callback,
None => return,
};
(*callback)(event);
*self.mutex.lock().unwrap() = Some(callback);
}
// Used to drop the user callback pointer at the end of the `poll_events` and `run_forever`
// methods. This is done to enforce our guarantee that the top callback will never live longer
// than the call to either `poll_events` or `run_forever` to which it was given.
fn drop(&self) {
self.mutex.lock().unwrap().take();
}
}
impl EventsLoop {
pub fn new() -> Self {
// Mark this thread as the main thread of the Cocoa event system.
//
// This must be done before any worker threads get a chance to call it
// (e.g., via `EventsLoopProxy::wakeup()`), causing a wrong thread to be
// marked as the main thread.
unsafe { appkit::NSApp(); }
EventsLoop {
shared: Arc::new(Shared::new()),
modifiers: Modifiers::new(),
}
}
pub fn poll_events<F>(&mut self, mut callback: F)
where F: FnMut(Event),
{
unsafe {
if !msg_send![class!(NSThread), isMainThread] {
panic!("Events can only be polled from the main thread on macOS");
}
}
self.shared.user_callback.store(&mut callback);
// Loop as long as we have pending events to return.
loop {
unsafe {
// First, yield all pending events.
self.shared.call_user_callback_with_pending_events();
let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil);
// Poll for the next event, returning `nil` if there are none.
let ns_event = appkit::NSApp().nextEventMatchingMask_untilDate_inMode_dequeue_(
NSEventMask::NSAnyEventMask.bits() | NSEventMask::NSEventMaskPressure.bits(),
foundation::NSDate::distantPast(cocoa::base::nil),
foundation::NSDefaultRunLoopMode,
cocoa::base::YES);
let event = self.ns_event_to_event(ns_event);
let _: () = msg_send![pool, release];
match event {
// Call the user's callback.
Some(event) => self.shared.user_callback.call_with_event(event),
None => break,
}
}
}
self.shared.user_callback.drop();
}
pub fn run_forever<F>(&mut self, mut callback: F)
where F: FnMut(Event) -> ControlFlow
{
unsafe {
if !msg_send![class!(NSThread), isMainThread] {
panic!("Events can only be polled from the main thread on macOS");
}
}
// Track whether or not control flow has changed.
let control_flow = std::cell::Cell::new(ControlFlow::Continue);
let mut callback = |event| {
if let ControlFlow::Break = callback(event) {
control_flow.set(ControlFlow::Break);
}
};
self.shared.user_callback.store(&mut callback);
loop {
unsafe {
// First, yield all pending events.
self.shared.call_user_callback_with_pending_events();
if let ControlFlow::Break = control_flow.get() {
break;
}
let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil);
// Wait for the next event. Note that this function blocks during resize.
let ns_event = appkit::NSApp().nextEventMatchingMask_untilDate_inMode_dequeue_(
NSEventMask::NSAnyEventMask.bits() | NSEventMask::NSEventMaskPressure.bits(),
foundation::NSDate::distantFuture(cocoa::base::nil),
foundation::NSDefaultRunLoopMode,
cocoa::base::YES);
let maybe_event = self.ns_event_to_event(ns_event);
// Release the pool before calling the top callback in case the user calls either
// `run_forever` or `poll_events` within the callback.
let _: () = msg_send![pool, release];
if let Some(event) = maybe_event {
self.shared.user_callback.call_with_event(event);
if let ControlFlow::Break = control_flow.get() {
break;
}
}
}
}
self.shared.user_callback.drop();
}
// Convert some given `NSEvent` into a winit `Event`.
unsafe fn ns_event_to_event(&mut self, ns_event: cocoa::base::id) -> Option<Event> {
if ns_event == cocoa::base::nil {
return None;
}
// FIXME: Despite not being documented anywhere, an `NSEvent` is produced when a user opens
// Spotlight while the NSApplication is in focus. This `NSEvent` produces a `NSEventType`
// with value `21`. This causes a SEGFAULT as soon as we try to match on the `NSEventType`
// enum as there is no variant associated with the value. Thus, we return early if this
// sneaky event occurs. If someone does find some documentation on this, please fix this by
// adding an appropriate variant to the `NSEventType` enum in the cocoa-rs crate.
if ns_event.eventType() as u64 == 21 {
return None;
}
let event_type = ns_event.eventType();
let ns_window = ns_event.window();
let window_id = super::window::get_window_id(ns_window);
// FIXME: Document this. Why do we do this? Seems like it passes on events to window/app.
// If we don't do this, window does not become main for some reason.
appkit::NSApp().sendEvent_(ns_event);
let windows = self.shared.windows.lock().unwrap();
let maybe_window = windows.iter()
.filter_map(Weak::upgrade)
.find(|window| window_id == window.id());
let into_event = |window_event| Event::WindowEvent {
window_id: ::WindowId(window_id),
event: window_event,
};
// Returns `Some` window if one of our windows is the key window.
let maybe_key_window = || windows.iter()
.filter_map(Weak::upgrade)
.find(|window| {
let is_key_window: cocoa::base::BOOL = msg_send![*window.window, isKeyWindow];
is_key_window == cocoa::base::YES
});
match event_type {
// https://github.com/glfw/glfw/blob/50eccd298a2bbc272b4977bd162d3e4b55f15394/src/cocoa_window.m#L881
appkit::NSKeyUp => {
if let Some(key_window) = maybe_key_window() {
if event_mods(ns_event).logo {
let _: () = msg_send![*key_window.window, sendEvent:ns_event];
}
}
None
},
// similar to above, but for `<Cmd-.>`, the keyDown is suppressed instead of the
// KeyUp, and the above trick does not appear to work.
appkit::NSKeyDown => {
let modifiers = event_mods(ns_event);
let keycode = NSEvent::keyCode(ns_event);
if modifiers.logo && keycode == 47 {
modifier_event(ns_event, NSEventModifierFlags::NSCommandKeyMask, false)
.map(into_event)
} else {
None
}
},
appkit::NSFlagsChanged => {
let mut events = std::collections::VecDeque::new();
if let Some(window_event) = modifier_event(
ns_event,
NSEventModifierFlags::NSShiftKeyMask,
self.modifiers.shift_pressed,
) {
self.modifiers.shift_pressed = !self.modifiers.shift_pressed;
events.push_back(into_event(window_event));
}
if let Some(window_event) = modifier_event(
ns_event,
NSEventModifierFlags::NSControlKeyMask,
self.modifiers.ctrl_pressed,
) {
self.modifiers.ctrl_pressed = !self.modifiers.ctrl_pressed;
events.push_back(into_event(window_event));
}
if let Some(window_event) = modifier_event(
ns_event,
NSEventModifierFlags::NSCommandKeyMask,
self.modifiers.win_pressed,
) {
self.modifiers.win_pressed = !self.modifiers.win_pressed;
events.push_back(into_event(window_event));
}
if let Some(window_event) = modifier_event(
ns_event,
NSEventModifierFlags::NSAlternateKeyMask,
self.modifiers.alt_pressed,
) {
self.modifiers.alt_pressed = !self.modifiers.alt_pressed;
events.push_back(into_event(window_event));
}
let event = events.pop_front();
self.shared.pending_events
.lock()
.unwrap()
.extend(events.into_iter());
event
},
appkit::NSMouseEntered => {
let window = match maybe_window.or_else(maybe_key_window) {
Some(window) => window,
None => return None,
};
let window_point = ns_event.locationInWindow();
let view_point = if ns_window == cocoa::base::nil {
let ns_size = foundation::NSSize::new(0.0, 0.0);
let ns_rect = foundation::NSRect::new(window_point, ns_size);
let window_rect = window.window.convertRectFromScreen_(ns_rect);
window.view.convertPoint_fromView_(window_rect.origin, cocoa::base::nil)
} else {
window.view.convertPoint_fromView_(window_point, cocoa::base::nil)
};
let view_rect = NSView::frame(*window.view);
let x = view_point.x as f64;
let y = (view_rect.size.height - view_point.y) as f64;
let window_event = WindowEvent::CursorMoved {
device_id: DEVICE_ID,
position: (x, y).into(),
modifiers: event_mods(ns_event),
};
let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event };
self.shared.pending_events.lock().unwrap().push_back(event);
Some(into_event(WindowEvent::CursorEntered { device_id: DEVICE_ID }))
},
appkit::NSMouseExited => { Some(into_event(WindowEvent::CursorLeft { device_id: DEVICE_ID })) },
appkit::NSMouseMoved |
appkit::NSLeftMouseDragged |
appkit::NSOtherMouseDragged |
appkit::NSRightMouseDragged => {
// If the mouse movement was on one of our windows, use it.
// Otherwise, if one of our windows is the key window (receiving input), use it.
// Otherwise, return `None`.
match maybe_window.or_else(maybe_key_window) {
Some(_window) => (),
None => return None,
}
let mut events = std::collections::VecDeque::with_capacity(3);
let delta_x = ns_event.deltaX() as f64;
if delta_x != 0.0 {
let motion_event = DeviceEvent::Motion { axis: 0, value: delta_x };
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
events.push_back(event);
}
let delta_y = ns_event.deltaY() as f64;
if delta_y != 0.0 {
let motion_event = DeviceEvent::Motion { axis: 1, value: delta_y };
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
events.push_back(event);
}
if delta_x != 0.0 || delta_y != 0.0 {
let motion_event = DeviceEvent::MouseMotion { delta: (delta_x, delta_y) };
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
events.push_back(event);
}
let event = events.pop_front();
self.shared.pending_events.lock().unwrap().extend(events.into_iter());
event
},
appkit::NSScrollWheel => {
// If none of the windows received the scroll, return `None`.
if maybe_window.is_none() {
return None;
}
use events::MouseScrollDelta::{LineDelta, PixelDelta};
let delta = if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES {
PixelDelta((
ns_event.scrollingDeltaX() as f64,
ns_event.scrollingDeltaY() as f64,
).into())
} else {
// TODO: This is probably wrong
LineDelta(
ns_event.scrollingDeltaX() as f32,
ns_event.scrollingDeltaY() as f32,
)
};
let phase = match ns_event.phase() {
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => TouchPhase::Started,
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
_ => TouchPhase::Moved,
};
self.shared.pending_events.lock().unwrap().push_back(Event::DeviceEvent {
device_id: DEVICE_ID,
event: DeviceEvent::MouseWheel {
delta: if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES {
PixelDelta((
ns_event.scrollingDeltaX() as f64,
ns_event.scrollingDeltaY() as f64,
).into())
} else {
LineDelta(
ns_event.scrollingDeltaX() as f32,
ns_event.scrollingDeltaY() as f32,
)
},
}
});
let window_event = WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: delta, phase: phase, modifiers: event_mods(ns_event) };
Some(into_event(window_event))
},
appkit::NSEventTypePressure => {
let pressure = ns_event.pressure();
let stage = ns_event.stage();
let window_event = WindowEvent::TouchpadPressure { device_id: DEVICE_ID, pressure: pressure, stage: stage };
Some(into_event(window_event))
},
appkit::NSApplicationDefined => match ns_event.subtype() {
appkit::NSEventSubtype::NSApplicationActivatedEventType => {
Some(Event::Awakened)
},
_ => None,
},
_ => None,
}
}
pub fn create_proxy(&self) -> Proxy {
Proxy {}
}
}
impl Proxy {
pub fn wakeup(&self) -> Result<(), EventsLoopClosed> {
// Awaken the event loop by triggering `NSApplicationActivatedEventType`.
unsafe {
let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil);
let event =
NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_(
cocoa::base::nil,
appkit::NSApplicationDefined,
foundation::NSPoint::new(0.0, 0.0),
appkit::NSEventModifierFlags::empty(),
0.0,
0,
cocoa::base::nil,
appkit::NSEventSubtype::NSApplicationActivatedEventType,
0,
0);
appkit::NSApp().postEvent_atStart_(event, cocoa::base::NO);
foundation::NSAutoreleasePool::drain(pool);
}
Ok(())
}
}
pub fn char_to_keycode(c: char) -> Option<events::VirtualKeyCode> {
// We only translate keys that are affected by keyboard layout.
//
// Note that since keys are translated in a somewhat "dumb" way (reading character)
// there is a concern that some combination, i.e. Cmd+char, causes the wrong
// letter to be received, and so we receive the wrong key.
//
// Implementation reference: https://github.com/WebKit/webkit/blob/82bae82cf0f329dbe21059ef0986c4e92fea4ba6/Source/WebCore/platform/cocoa/KeyEventCocoa.mm#L626
Some(match c {
'a' | 'A' => events::VirtualKeyCode::A,
'b' | 'B' => events::VirtualKeyCode::B,
'c' | 'C' => events::VirtualKeyCode::C,
'd' | 'D' => events::VirtualKeyCode::D,
'e' | 'E' => events::VirtualKeyCode::E,
'f' | 'F' => events::VirtualKeyCode::F,
'g' | 'G' => events::VirtualKeyCode::G,
'h' | 'H' => events::VirtualKeyCode::H,
'i' | 'I' => events::VirtualKeyCode::I,
'j' | 'J' => events::VirtualKeyCode::J,
'k' | 'K' => events::VirtualKeyCode::K,
'l' | 'L' => events::VirtualKeyCode::L,
'm' | 'M' => events::VirtualKeyCode::M,
'n' | 'N' => events::VirtualKeyCode::N,
'o' | 'O' => events::VirtualKeyCode::O,
'p' | 'P' => events::VirtualKeyCode::P,
'q' | 'Q' => events::VirtualKeyCode::Q,
'r' | 'R' => events::VirtualKeyCode::R,
's' | 'S' => events::VirtualKeyCode::S,
't' | 'T' => events::VirtualKeyCode::T,
'u' | 'U' => events::VirtualKeyCode::U,
'v' | 'V' => events::VirtualKeyCode::V,
'w' | 'W' => events::VirtualKeyCode::W,
'x' | 'X' => events::VirtualKeyCode::X,
'y' | 'Y' => events::VirtualKeyCode::Y,
'z' | 'Z' => events::VirtualKeyCode::Z,
'1' | '!' => events::VirtualKeyCode::Key1,
'2' | '@' => events::VirtualKeyCode::Key2,
'3' | '#' => events::VirtualKeyCode::Key3,
'4' | '$' => events::VirtualKeyCode::Key4,
'5' | '%' => events::VirtualKeyCode::Key5,
'6' | '^' => events::VirtualKeyCode::Key6,
'7' | '&' => events::VirtualKeyCode::Key7,
'8' | '*' => events::VirtualKeyCode::Key8,
'9' | '(' => events::VirtualKeyCode::Key9,
'0' | ')' => events::VirtualKeyCode::Key0,
'=' | '+' => events::VirtualKeyCode::Equals,
'-' | '_' => events::VirtualKeyCode::Minus,
']' | '}' => events::VirtualKeyCode::RBracket,
'[' | '{' => events::VirtualKeyCode::LBracket,
'\''| '"' => events::VirtualKeyCode::Apostrophe,
';' | ':' => events::VirtualKeyCode::Semicolon,
'\\'| '|' => events::VirtualKeyCode::Backslash,
',' | '<' => events::VirtualKeyCode::Comma,
'/' | '?' => events::VirtualKeyCode::Slash,
'.' | '>' => events::VirtualKeyCode::Period,
'`' | '~' => events::VirtualKeyCode::Grave,
_ => return None,
})
}
pub fn scancode_to_keycode(code: c_ushort) -> Option<events::VirtualKeyCode> {
Some(match code {
0x00 => events::VirtualKeyCode::A,
0x01 => events::VirtualKeyCode::S,
0x02 => events::VirtualKeyCode::D,
0x03 => events::VirtualKeyCode::F,
0x04 => events::VirtualKeyCode::H,
0x05 => events::VirtualKeyCode::G,
0x06 => events::VirtualKeyCode::Z,
0x07 => events::VirtualKeyCode::X,
0x08 => events::VirtualKeyCode::C,
0x09 => events::VirtualKeyCode::V,
//0x0a => World 1,
0x0b => events::VirtualKeyCode::B,
0x0c => events::VirtualKeyCode::Q,
0x0d => events::VirtualKeyCode::W,
0x0e => events::VirtualKeyCode::E,
0x0f => events::VirtualKeyCode::R,
0x10 => events::VirtualKeyCode::Y,
0x11 => events::VirtualKeyCode::T,
0x12 => events::VirtualKeyCode::Key1,
0x13 => events::VirtualKeyCode::Key2,
0x14 => events::VirtualKeyCode::Key3,
0x15 => events::VirtualKeyCode::Key4,
0x16 => events::VirtualKeyCode::Key6,
0x17 => events::VirtualKeyCode::Key5,
0x18 => events::VirtualKeyCode::Equals,
0x19 => events::VirtualKeyCode::Key9,
0x1a => events::VirtualKeyCode::Key7,
0x1b => events::VirtualKeyCode::Minus,
0x1c => events::VirtualKeyCode::Key8,
0x1d => events::VirtualKeyCode::Key0,
0x1e => events::VirtualKeyCode::RBracket,
0x1f => events::VirtualKeyCode::O,
0x20 => events::VirtualKeyCode::U,
0x21 => events::VirtualKeyCode::LBracket,
0x22 => events::VirtualKeyCode::I,
0x23 => events::VirtualKeyCode::P,
0x24 => events::VirtualKeyCode::Return,
0x25 => events::VirtualKeyCode::L,
0x26 => events::VirtualKeyCode::J,
0x27 => events::VirtualKeyCode::Apostrophe,
0x28 => events::VirtualKeyCode::K,
0x29 => events::VirtualKeyCode::Semicolon,
0x2a => events::VirtualKeyCode::Backslash,
0x2b => events::VirtualKeyCode::Comma,
0x2c => events::VirtualKeyCode::Slash,
0x2d => events::VirtualKeyCode::N,
0x2e => events::VirtualKeyCode::M,
0x2f => events::VirtualKeyCode::Period,
0x30 => events::VirtualKeyCode::Tab,
0x31 => events::VirtualKeyCode::Space,
0x32 => events::VirtualKeyCode::Grave,
0x33 => events::VirtualKeyCode::Back,
//0x34 => unkown,
0x35 => events::VirtualKeyCode::Escape,
0x36 => events::VirtualKeyCode::RWin,
0x37 => events::VirtualKeyCode::LWin,
0x38 => events::VirtualKeyCode::LShift,
//0x39 => Caps lock,
0x3a => events::VirtualKeyCode::LAlt,
0x3b => events::VirtualKeyCode::LControl,
0x3c => events::VirtualKeyCode::RShift,
0x3d => events::VirtualKeyCode::RAlt,
0x3e => events::VirtualKeyCode::RControl,
//0x3f => Fn key,
0x40 => events::VirtualKeyCode::F17,
0x41 => events::VirtualKeyCode::Decimal,
//0x42 -> unkown,
0x43 => events::VirtualKeyCode::Multiply,
//0x44 => unkown,
0x45 => events::VirtualKeyCode::Add,
//0x46 => unkown,
0x47 => events::VirtualKeyCode::Numlock,
//0x48 => KeypadClear,
0x49 => events::VirtualKeyCode::VolumeUp,
0x4a => events::VirtualKeyCode::VolumeDown,
0x4b => events::VirtualKeyCode::Divide,
0x4c => events::VirtualKeyCode::NumpadEnter,
0x4e => events::VirtualKeyCode::Subtract,
//0x4d => unkown,
0x4e => events::VirtualKeyCode::Subtract,
0x4f => events::VirtualKeyCode::F18,
0x50 => events::VirtualKeyCode::F19,
0x51 => events::VirtualKeyCode::NumpadEquals,
0x52 => events::VirtualKeyCode::Numpad0,
0x53 => events::VirtualKeyCode::Numpad1,
0x54 => events::VirtualKeyCode::Numpad2,
0x55 => events::VirtualKeyCode::Numpad3,
0x56 => events::VirtualKeyCode::Numpad4,
0x57 => events::VirtualKeyCode::Numpad5,
0x58 => events::VirtualKeyCode::Numpad6,
0x59 => events::VirtualKeyCode::Numpad7,
0x5a => events::VirtualKeyCode::F20,
0x5b => events::VirtualKeyCode::Numpad8,
0x5c => events::VirtualKeyCode::Numpad9,
0x5d => events::VirtualKeyCode::Yen,
//0x5e => JIS Ro,
//0x5f => unkown,
0x60 => events::VirtualKeyCode::F5,
0x61 => events::VirtualKeyCode::F6,
0x62 => events::VirtualKeyCode::F7,
0x63 => events::VirtualKeyCode::F3,
0x64 => events::VirtualKeyCode::F8,
0x65 => events::VirtualKeyCode::F9,
//0x66 => JIS Eisuu (macOS),
0x67 => events::VirtualKeyCode::F11,
//0x68 => JIS Kana (macOS),
0x69 => events::VirtualKeyCode::F13,
0x6a => events::VirtualKeyCode::F16,
0x6b => events::VirtualKeyCode::F14,
//0x6c => unkown,
0x6d => events::VirtualKeyCode::F10,
//0x6e => unkown,
0x6f => events::VirtualKeyCode::F12,
//0x70 => unkown,
0x71 => events::VirtualKeyCode::F15,
0x72 => events::VirtualKeyCode::Insert,
0x73 => events::VirtualKeyCode::Home,
0x74 => events::VirtualKeyCode::PageUp,
0x75 => events::VirtualKeyCode::Delete,
0x76 => events::VirtualKeyCode::F4,
0x77 => events::VirtualKeyCode::End,
0x78 => events::VirtualKeyCode::F2,
0x79 => events::VirtualKeyCode::PageDown,
0x7a => events::VirtualKeyCode::F1,
0x7b => events::VirtualKeyCode::Left,
0x7c => events::VirtualKeyCode::Right,
0x7d => events::VirtualKeyCode::Down,
0x7e => events::VirtualKeyCode::Up,
//0x7f => unkown,
0xa => events::VirtualKeyCode::Caret,
_ => return None,
})
}
pub fn check_function_keys(
s: &String
) -> Option<events::VirtualKeyCode> {
if let Some(ch) = s.encode_utf16().next() {
return Some(match ch {
0xf718 => events::VirtualKeyCode::F21,
0xf719 => events::VirtualKeyCode::F22,
0xf71a => events::VirtualKeyCode::F23,
0xf71b => events::VirtualKeyCode::F24,
_ => return None,
})
}
None
}
pub fn event_mods(event: cocoa::base::id) -> ModifiersState {
let flags = unsafe {
NSEvent::modifierFlags(event)
};
ModifiersState {
shift: flags.contains(NSEventModifierFlags::NSShiftKeyMask),
ctrl: flags.contains(NSEventModifierFlags::NSControlKeyMask),
alt: flags.contains(NSEventModifierFlags::NSAlternateKeyMask),
logo: flags.contains(NSEventModifierFlags::NSCommandKeyMask),
}
}
pub fn get_scancode(event: cocoa::base::id) -> c_ushort {
// In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character,
// and there is no easy way to navtively retrieve the layout-dependent character.
// In winit, we use keycode to refer to the key's character, and so this function aligns
// AppKit's terminology with ours.
unsafe {
msg_send![event, keyCode]
}
}
unsafe fn modifier_event(
ns_event: cocoa::base::id,
keymask: NSEventModifierFlags,
was_key_pressed: bool,
) -> Option<WindowEvent> {
if !was_key_pressed && NSEvent::modifierFlags(ns_event).contains(keymask)
|| was_key_pressed && !NSEvent::modifierFlags(ns_event).contains(keymask) {
let state = if was_key_pressed {
ElementState::Released
} else {
ElementState::Pressed
};
let scancode = get_scancode(ns_event);
let virtual_keycode = scancode_to_keycode(scancode);
Some(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state,
scancode: scancode as u32,
virtual_keycode,
modifiers: event_mods(ns_event),
},
})
} else {
None
}
}
// Constant device ID, to be removed when this backend is updated to report real device IDs.
pub const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);

107
src/platform/macos/ffi.rs Normal file
View File

@@ -0,0 +1,107 @@
// TODO: Upstream these
#![allow(dead_code, non_snake_case, non_upper_case_globals)]
use cocoa::base::id;
use cocoa::foundation::{NSInteger, NSUInteger};
use objc;
pub const NSNotFound: NSInteger = NSInteger::max_value();
#[repr(C)]
pub struct NSRange {
pub location: NSUInteger,
pub length: NSUInteger,
}
impl NSRange {
#[inline]
pub fn new(location: NSUInteger, length: NSUInteger) -> NSRange {
NSRange { location, length }
}
}
unsafe impl objc::Encode for NSRange {
fn encode() -> objc::Encoding {
let encoding = format!(
// TODO: Verify that this is correct
"{{NSRange={}{}}}",
NSUInteger::encode().as_str(),
NSUInteger::encode().as_str(),
);
unsafe { objc::Encoding::from_str(&encoding) }
}
}
pub trait NSMutableAttributedString: Sized {
unsafe fn alloc(_: Self) -> id {
msg_send![class!(NSMutableAttributedString), alloc]
}
unsafe fn init(self) -> id; // *mut NSMutableAttributedString
unsafe fn initWithString(self, string: id) -> id;
unsafe fn initWithAttributedString(self, string: id) -> id;
unsafe fn string(self) -> id; // *mut NSString
unsafe fn mutableString(self) -> id; // *mut NSMutableString
unsafe fn length(self) -> NSUInteger;
}
impl NSMutableAttributedString for id {
unsafe fn init(self) -> id {
msg_send![self, init]
}
unsafe fn initWithString(self, string: id) -> id {
msg_send![self, initWithString:string]
}
unsafe fn initWithAttributedString(self, string: id) -> id {
msg_send![self, initWithAttributedString:string]
}
unsafe fn string(self) -> id {
msg_send![self, string]
}
unsafe fn mutableString(self) -> id {
msg_send![self, mutableString]
}
unsafe fn length(self) -> NSUInteger {
msg_send![self, length]
}
}
pub const kCGBaseWindowLevelKey: NSInteger = 0;
pub const kCGMinimumWindowLevelKey: NSInteger = 1;
pub const kCGDesktopWindowLevelKey: NSInteger = 2;
pub const kCGBackstopMenuLevelKey: NSInteger = 3;
pub const kCGNormalWindowLevelKey: NSInteger = 4;
pub const kCGFloatingWindowLevelKey: NSInteger = 5;
pub const kCGTornOffMenuWindowLevelKey: NSInteger = 6;
pub const kCGDockWindowLevelKey: NSInteger = 7;
pub const kCGMainMenuWindowLevelKey: NSInteger = 8;
pub const kCGStatusWindowLevelKey: NSInteger = 9;
pub const kCGModalPanelWindowLevelKey: NSInteger = 10;
pub const kCGPopUpMenuWindowLevelKey: NSInteger = 11;
pub const kCGDraggingWindowLevelKey: NSInteger = 12;
pub const kCGScreenSaverWindowLevelKey: NSInteger = 13;
pub const kCGMaximumWindowLevelKey: NSInteger = 14;
pub const kCGOverlayWindowLevelKey: NSInteger = 15;
pub const kCGHelpWindowLevelKey: NSInteger = 16;
pub const kCGUtilityWindowLevelKey: NSInteger = 17;
pub const kCGDesktopIconWindowLevelKey: NSInteger = 18;
pub const kCGCursorWindowLevelKey: NSInteger = 19;
pub const kCGNumberOfWindowLevelKeys: NSInteger = 20;
pub enum NSWindowLevel {
NSNormalWindowLevel = kCGBaseWindowLevelKey as _,
NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _,
NSTornOffMenuWindowLevel = kCGTornOffMenuWindowLevelKey as _,
NSModalPanelWindowLevel = kCGModalPanelWindowLevelKey as _,
NSMainMenuWindowLevel = kCGMainMenuWindowLevelKey as _,
NSStatusWindowLevel = kCGStatusWindowLevelKey as _,
NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _,
NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _,
}

51
src/platform/macos/mod.rs Normal file
View File

@@ -0,0 +1,51 @@
#![cfg(target_os = "macos")]
pub use self::events_loop::{EventsLoop, Proxy as EventsLoopProxy};
pub use self::monitor::MonitorId;
pub use self::window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, Window2};
use std::sync::Arc;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId
}
}
use {CreationError};
pub struct Window {
pub window: Arc<Window2>,
}
impl ::std::ops::Deref for Window {
type Target = Window2;
#[inline]
fn deref(&self) -> &Window2 {
&*self.window
}
}
impl Window {
pub fn new(events_loop: &EventsLoop,
attributes: ::WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes) -> Result<Self, CreationError>
{
let weak_shared = Arc::downgrade(&events_loop.shared);
let window = Arc::new(try!(Window2::new(weak_shared, attributes, pl_attribs)));
let weak_window = Arc::downgrade(&window);
events_loop.shared.windows.lock().unwrap().push(weak_window);
Ok(Window { window: window })
}
}
mod events_loop;
mod ffi;
mod monitor;
mod util;
mod view;
mod window;

View File

@@ -0,0 +1,147 @@
use std::collections::VecDeque;
use std::fmt;
use cocoa::appkit::NSScreen;
use cocoa::base::{id, nil};
use cocoa::foundation::{NSString, NSUInteger};
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
use {PhysicalPosition, PhysicalSize};
use super::EventsLoop;
use super::window::{IdRef, Window2};
#[derive(Clone, PartialEq)]
pub struct MonitorId(CGDirectDisplayID);
fn get_available_monitors() -> VecDeque<MonitorId> {
if let Ok(displays) = CGDisplay::active_displays() {
let mut monitors = VecDeque::with_capacity(displays.len());
for d in displays {
monitors.push_back(MonitorId(d));
}
monitors
} else {
VecDeque::with_capacity(0)
}
}
pub fn get_primary_monitor() -> MonitorId {
let id = MonitorId(CGDisplay::main().id);
id
}
impl EventsLoop {
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
get_available_monitors()
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
get_primary_monitor()
}
pub fn make_monitor_from_display(id: CGDirectDisplayID) -> MonitorId {
let id = MonitorId(id);
id
}
}
impl Window2 {
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
get_available_monitors()
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
get_primary_monitor()
}
}
impl fmt::Debug for MonitorId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct MonitorId {
name: Option<String>,
native_identifier: u32,
dimensions: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: f64,
}
let monitor_id_proxy = MonitorId {
name: self.get_name(),
native_identifier: self.get_native_identifier(),
dimensions: self.get_dimensions(),
position: self.get_position(),
hidpi_factor: self.get_hidpi_factor(),
};
monitor_id_proxy.fmt(f)
}
}
impl MonitorId {
pub fn get_name(&self) -> Option<String> {
let MonitorId(display_id) = *self;
let screen_num = CGDisplay::new(display_id).model_number();
Some(format!("Monitor #{}", screen_num))
}
#[inline]
pub fn get_native_identifier(&self) -> u32 {
self.0
}
pub fn get_dimensions(&self) -> PhysicalSize {
let MonitorId(display_id) = *self;
let display = CGDisplay::new(display_id);
let height = display.pixels_high();
let width = display.pixels_wide();
PhysicalSize::from_logical(
(width as f64, height as f64),
self.get_hidpi_factor(),
)
}
#[inline]
pub fn get_position(&self) -> PhysicalPosition {
let bounds = unsafe { CGDisplayBounds(self.get_native_identifier()) };
PhysicalPosition::from_logical(
(bounds.origin.x as f64, bounds.origin.y as f64),
self.get_hidpi_factor(),
)
}
pub fn get_hidpi_factor(&self) -> f64 {
let screen = match self.get_nsscreen() {
Some(screen) => screen,
None => return 1.0, // default to 1.0 when we can't find the screen
};
unsafe { NSScreen::backingScaleFactor(screen) as f64 }
}
pub(crate) fn get_nsscreen(&self) -> Option<id> {
unsafe {
let native_id = self.get_native_identifier();
let screens = NSScreen::screens(nil);
let count: NSUInteger = msg_send![screens, count];
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
let mut matching_screen: Option<id> = None;
for i in 0..count {
let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
let device_description = NSScreen::deviceDescription(screen);
let value: id = msg_send![device_description, objectForKey:*key];
if value != nil {
let screen_number: NSUInteger = msg_send![value, unsignedIntegerValue];
if screen_number as u32 == native_id {
matching_screen = Some(screen);
break;
}
}
}
matching_screen
}
}
}

View File

@@ -0,0 +1,149 @@
use cocoa::{
appkit::NSImage, base::{id, nil, YES},
foundation::{NSDictionary, NSPoint, NSString},
};
use objc::runtime::Sel;
use super::IntoOption;
use MouseCursor;
pub enum Cursor {
Native(&'static str),
Undocumented(&'static str),
WebKit(&'static str),
}
impl From<MouseCursor> for Cursor {
fn from(cursor: MouseCursor) -> Self {
match cursor {
MouseCursor::Arrow | MouseCursor::Default => Cursor::Native("arrowCursor"),
MouseCursor::Hand => Cursor::Native("pointingHandCursor"),
MouseCursor::Grabbing | MouseCursor::Grab => Cursor::Native("closedHandCursor"),
MouseCursor::Text => Cursor::Native("IBeamCursor"),
MouseCursor::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"),
MouseCursor::Copy => Cursor::Native("dragCopyCursor"),
MouseCursor::Alias => Cursor::Native("dragLinkCursor"),
MouseCursor::NotAllowed | MouseCursor::NoDrop => Cursor::Native("operationNotAllowedCursor"),
MouseCursor::ContextMenu => Cursor::Native("contextualMenuCursor"),
MouseCursor::Crosshair => Cursor::Native("crosshairCursor"),
MouseCursor::EResize => Cursor::Native("resizeRightCursor"),
MouseCursor::NResize => Cursor::Native("resizeUpCursor"),
MouseCursor::WResize => Cursor::Native("resizeLeftCursor"),
MouseCursor::SResize => Cursor::Native("resizeDownCursor"),
MouseCursor::EwResize | MouseCursor::ColResize => Cursor::Native("resizeLeftRightCursor"),
MouseCursor::NsResize | MouseCursor::RowResize => Cursor::Native("resizeUpDownCursor"),
// Undocumented cursors: https://stackoverflow.com/a/46635398/5435443
MouseCursor::Help => Cursor::Undocumented("_helpCursor"),
MouseCursor::ZoomIn => Cursor::Undocumented("_zoomInCursor"),
MouseCursor::ZoomOut => Cursor::Undocumented("_zoomOutCursor"),
MouseCursor::NeResize => Cursor::Undocumented("_windowResizeNorthEastCursor"),
MouseCursor::NwResize => Cursor::Undocumented("_windowResizeNorthWestCursor"),
MouseCursor::SeResize => Cursor::Undocumented("_windowResizeSouthEastCursor"),
MouseCursor::SwResize => Cursor::Undocumented("_windowResizeSouthWestCursor"),
MouseCursor::NeswResize => Cursor::Undocumented("_windowResizeNorthEastSouthWestCursor"),
MouseCursor::NwseResize => Cursor::Undocumented("_windowResizeNorthWestSouthEastCursor"),
// While these are available, the former just loads a white arrow,
// and the latter loads an ugly deflated beachball!
// MouseCursor::Move => Cursor::Undocumented("_moveCursor"),
// MouseCursor::Wait => Cursor::Undocumented("_waitCursor"),
// An even more undocumented cursor...
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349
// This is the wrong semantics for `Wait`, but it's the same as
// what's used in Safari and Chrome.
MouseCursor::Wait | MouseCursor::Progress => Cursor::Undocumented("busyButClickableCursor"),
// For the rest, we can just snatch the cursors from WebKit...
// They fit the style of the native cursors, and will seem
// completely standard to macOS users.
// https://stackoverflow.com/a/21786835/5435443
MouseCursor::Move | MouseCursor::AllScroll => Cursor::WebKit("move"),
MouseCursor::Cell => Cursor::WebKit("cell"),
}
}
}
impl Default for Cursor {
fn default() -> Self {
Cursor::Native("arrowCursor")
}
}
impl Cursor {
pub unsafe fn load(&self) -> id {
match self {
Cursor::Native(cursor_name) => {
let sel = Sel::register(cursor_name);
msg_send![class!(NSCursor), performSelector:sel]
},
Cursor::Undocumented(cursor_name) => {
let class = class!(NSCursor);
let sel = Sel::register(cursor_name);
let sel = if msg_send![class, respondsToSelector:sel] {
sel
} else {
warn!("Cursor `{}` appears to be invalid", cursor_name);
sel!(arrowCursor)
};
msg_send![class, performSelector:sel]
},
Cursor::WebKit(cursor_name) => load_webkit_cursor(cursor_name)
.unwrap_or_else(|message| {
warn!("{}", message);
Self::default().load()
}),
}
}
}
// Note that loading `busybutclickable` with this code won't animate the frames;
// instead you'll just get them all in a column.
unsafe fn load_webkit_cursor(cursor_name_str: &str) -> Result<id, String> {
static CURSOR_ROOT: &'static str = "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors";
let cursor_root = NSString::alloc(nil).init_str(CURSOR_ROOT);
let cursor_name = NSString::alloc(nil).init_str(cursor_name_str);
let cursor_pdf = NSString::alloc(nil).init_str("cursor.pdf");
let cursor_plist = NSString::alloc(nil).init_str("info.plist");
let key_x = NSString::alloc(nil).init_str("hotx");
let key_y = NSString::alloc(nil).init_str("hoty");
let cursor_path: id = msg_send![cursor_root,
stringByAppendingPathComponent:cursor_name
];
let pdf_path: id = msg_send![cursor_path,
stringByAppendingPathComponent:cursor_pdf
];
let info_path: id = msg_send![cursor_path,
stringByAppendingPathComponent:cursor_plist
];
let image = NSImage::alloc(nil)
.initByReferencingFile_(pdf_path)
// This will probably never be `None`, since images are loaded lazily...
.into_option()
// because of that, we need to check for validity.
.filter(|image| image.isValid() == YES)
.ok_or_else(||
format!("Failed to read image for `{}` cursor", cursor_name_str)
)?;
let info = NSDictionary::dictionaryWithContentsOfFile_(nil, info_path)
.into_option()
.ok_or_else(||
format!("Failed to read info for `{}` cursor", cursor_name_str)
)?;
let x = info.valueForKey_(key_x);
let y = info.valueForKey_(key_y);
let point = NSPoint::new(
msg_send![x, doubleValue],
msg_send![y, doubleValue],
);
let cursor: id = msg_send![class!(NSCursor), alloc];
let cursor: id = msg_send![cursor, initWithImage:image hotSpot:point];
cursor
.into_option()
.ok_or_else(||
format!("Failed to initialize `{}` cursor", cursor_name_str)
)
}

View File

@@ -0,0 +1,14 @@
use cocoa::base::{id, nil};
pub trait IntoOption: Sized {
fn into_option(self) -> Option<Self>;
}
impl IntoOption for id {
fn into_option(self) -> Option<Self> {
match self != nil {
true => Some(self),
false => None,
}
}
}

View File

@@ -0,0 +1,63 @@
mod cursor;
mod into_option;
pub use self::{cursor::Cursor, into_option::IntoOption};
use cocoa::appkit::NSWindowStyleMask;
use cocoa::base::{id, nil};
use cocoa::foundation::{NSRect, NSUInteger};
use core_graphics::display::CGDisplay;
use objc::runtime::{Class, Object};
use platform::platform::ffi;
use platform::platform::window::IdRef;
pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange {
location: ffi::NSNotFound as NSUInteger,
length: 0,
};
// For consistency with other platforms, this will...
// 1. translate the bottom-left window corner into the top-left window corner
// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one
pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)
}
pub unsafe fn set_style_mask(window: id, view: id, mask: NSWindowStyleMask) {
use cocoa::appkit::NSWindow;
window.setStyleMask_(mask);
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
window.makeFirstResponder_(view);
}
pub unsafe fn toggle_style_mask(window: id, view: id, mask: NSWindowStyleMask, on: bool) {
use cocoa::appkit::NSWindow;
let current_style_mask = window.styleMask();
if on {
window.setStyleMask_(current_style_mask | mask);
} else {
window.setStyleMask_(current_style_mask & (!mask));
}
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
window.makeFirstResponder_(view);
}
pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class {
let superclass: id = msg_send![this, superclass];
&*(superclass as *const _)
}
pub unsafe fn create_input_context(view: id) -> IdRef {
let input_context: id = msg_send![class!(NSTextInputContext), alloc];
let input_context: id = msg_send![input_context, initWithClient:view];
IdRef::new(input_context)
}
#[allow(dead_code)]
pub unsafe fn open_emoji_picker() {
let app: id = msg_send![class!(NSApplication), sharedApplication];
let _: () = msg_send![app, orderFrontCharacterPalette:nil];
}

663
src/platform/macos/view.rs Normal file
View File

@@ -0,0 +1,663 @@
// This is a pretty close port of the implementation in GLFW:
// https://github.com/glfw/glfw/blob/7ef34eb06de54dd9186d3d21a401b2ef819b59e7/src/cocoa_window.m
use std::{slice, str};
use std::boxed::Box;
use std::collections::VecDeque;
use std::os::raw::*;
use std::sync::{Arc, Mutex, Weak};
use cocoa::base::{id, nil};
use cocoa::appkit::{NSEvent, NSView, NSWindow};
use cocoa::foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger};
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Protocol, Sel, BOOL, YES};
use {ElementState, Event, KeyboardInput, MouseButton, WindowEvent, WindowId};
use platform::platform::events_loop::{DEVICE_ID, event_mods, Shared, scancode_to_keycode, char_to_keycode, check_function_keys, get_scancode};
use platform::platform::util;
use platform::platform::ffi::*;
use platform::platform::window::{get_window_id, IdRef};
use events;
struct ViewState {
window: id,
shared: Weak<Shared>,
cursor: Arc<Mutex<util::Cursor>>,
ime_spot: Option<(f64, f64)>,
raw_characters: Option<String>,
is_key_down: bool,
}
pub fn new_view(window: id, shared: Weak<Shared>) -> (IdRef, Weak<Mutex<util::Cursor>>) {
let cursor = Default::default();
let cursor_access = Arc::downgrade(&cursor);
let state = ViewState {
window,
shared,
cursor,
ime_spot: None,
raw_characters: None,
is_key_down: false,
};
unsafe {
// This is free'd in `dealloc`
let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void;
let view: id = msg_send![VIEW_CLASS.0, alloc];
(IdRef::new(msg_send![view, initWithWinit:state_ptr]), cursor_access)
}
}
pub fn set_ime_spot(view: id, input_context: id, x: f64, y: f64) {
unsafe {
let state_ptr: *mut c_void = *(*view).get_mut_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let content_rect = NSWindow::contentRectForFrameRect_(
state.window,
NSWindow::frame(state.window),
);
let base_x = content_rect.origin.x as f64;
let base_y = (content_rect.origin.y + content_rect.size.height) as f64;
state.ime_spot = Some((base_x + x, base_y - y));
let _: () = msg_send![input_context, invalidateCharacterCoordinates];
}
}
struct ViewClass(*const Class);
unsafe impl Send for ViewClass {}
unsafe impl Sync for ViewClass {}
lazy_static! {
static ref VIEW_CLASS: ViewClass = unsafe {
let superclass = class!(NSView);
let mut decl = ClassDecl::new("WinitView", superclass).unwrap();
decl.add_method(sel!(dealloc), dealloc as extern fn(&Object, Sel));
decl.add_method(
sel!(initWithWinit:),
init_with_winit as extern fn(&Object, Sel, *mut c_void) -> id,
);
decl.add_method(
sel!(drawRect:),
draw_rect as extern fn(&Object, Sel, NSRect),
);
decl.add_method(
sel!(resetCursorRects),
reset_cursor_rects as extern fn(&Object, Sel),
);
decl.add_method(sel!(hasMarkedText), has_marked_text as extern fn(&Object, Sel) -> BOOL);
decl.add_method(
sel!(markedRange),
marked_range as extern fn(&Object, Sel) -> NSRange,
);
decl.add_method(sel!(selectedRange), selected_range as extern fn(&Object, Sel) -> NSRange);
decl.add_method(
sel!(setMarkedText:selectedRange:replacementRange:),
set_marked_text as extern fn(&mut Object, Sel, id, NSRange, NSRange),
);
decl.add_method(sel!(unmarkText), unmark_text as extern fn(&Object, Sel));
decl.add_method(
sel!(validAttributesForMarkedText),
valid_attributes_for_marked_text as extern fn(&Object, Sel) -> id,
);
decl.add_method(
sel!(attributedSubstringForProposedRange:actualRange:),
attributed_substring_for_proposed_range
as extern fn(&Object, Sel, NSRange, *mut c_void) -> id,
);
decl.add_method(
sel!(insertText:replacementRange:),
insert_text as extern fn(&Object, Sel, id, NSRange),
);
decl.add_method(
sel!(characterIndexForPoint:),
character_index_for_point as extern fn(&Object, Sel, NSPoint) -> NSUInteger,
);
decl.add_method(
sel!(firstRectForCharacterRange:actualRange:),
first_rect_for_character_range
as extern fn(&Object, Sel, NSRange, *mut c_void) -> NSRect,
);
decl.add_method(
sel!(doCommandBySelector:),
do_command_by_selector as extern fn(&Object, Sel, Sel),
);
decl.add_method(sel!(keyDown:), key_down as extern fn(&Object, Sel, id));
decl.add_method(sel!(keyUp:), key_up as extern fn(&Object, Sel, id));
decl.add_method(sel!(insertTab:), insert_tab as extern fn(&Object, Sel, id));
decl.add_method(sel!(insertBackTab:), insert_back_tab as extern fn(&Object, Sel, id));
decl.add_method(sel!(mouseDown:), mouse_down as extern fn(&Object, Sel, id));
decl.add_method(sel!(mouseUp:), mouse_up as extern fn(&Object, Sel, id));
decl.add_method(sel!(rightMouseDown:), right_mouse_down as extern fn(&Object, Sel, id));
decl.add_method(sel!(rightMouseUp:), right_mouse_up as extern fn(&Object, Sel, id));
decl.add_method(sel!(otherMouseDown:), other_mouse_down as extern fn(&Object, Sel, id));
decl.add_method(sel!(otherMouseUp:), other_mouse_up as extern fn(&Object, Sel, id));
decl.add_method(sel!(mouseMoved:), mouse_moved as extern fn(&Object, Sel, id));
decl.add_method(sel!(mouseDragged:), mouse_dragged as extern fn(&Object, Sel, id));
decl.add_method(sel!(rightMouseDragged:), right_mouse_dragged as extern fn(&Object, Sel, id));
decl.add_method(sel!(otherMouseDragged:), other_mouse_dragged as extern fn(&Object, Sel, id));
decl.add_method(sel!(_wantsKeyDownForEvent:), wants_key_down_for_event as extern fn(&Object, Sel, id) -> BOOL);
decl.add_ivar::<*mut c_void>("winitState");
decl.add_ivar::<id>("markedText");
let protocol = Protocol::get("NSTextInputClient").unwrap();
decl.add_protocol(&protocol);
ViewClass(decl.register())
};
}
extern fn dealloc(this: &Object, _sel: Sel) {
unsafe {
let state: *mut c_void = *this.get_ivar("winitState");
let marked_text: id = *this.get_ivar("markedText");
let _: () = msg_send![marked_text, release];
Box::from_raw(state as *mut ViewState);
}
}
extern fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> id {
unsafe {
let this: id = msg_send![this, init];
if this != nil {
(*this).set_ivar("winitState", state);
let marked_text = <id as NSMutableAttributedString>::init(
NSMutableAttributedString::alloc(nil),
);
(*this).set_ivar("markedText", marked_text);
}
this
}
}
extern fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
if let Some(shared) = state.shared.upgrade() {
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::Refresh,
};
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
}
let superclass = util::superclass(this);
let () = msg_send![super(this, superclass), drawRect:rect];
}
}
extern fn reset_cursor_rects(this: &Object, _sel: Sel) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let bounds: NSRect = msg_send![this, bounds];
let cursor = state.cursor.lock().unwrap().load();
let _: () = msg_send![this,
addCursorRect:bounds
cursor:cursor
];
}
}
extern fn has_marked_text(this: &Object, _sel: Sel) -> BOOL {
//println!("hasMarkedText");
unsafe {
let marked_text: id = *this.get_ivar("markedText");
(marked_text.length() > 0) as i8
}
}
extern fn marked_range(this: &Object, _sel: Sel) -> NSRange {
//println!("markedRange");
unsafe {
let marked_text: id = *this.get_ivar("markedText");
let length = marked_text.length();
if length > 0 {
NSRange::new(0, length - 1)
} else {
util::EMPTY_RANGE
}
}
}
extern fn selected_range(_this: &Object, _sel: Sel) -> NSRange {
//println!("selectedRange");
util::EMPTY_RANGE
}
extern fn set_marked_text(
this: &mut Object,
_sel: Sel,
string: id,
_selected_range: NSRange,
_replacement_range: NSRange,
) {
//println!("setMarkedText");
unsafe {
let marked_text_ref: &mut id = this.get_mut_ivar("markedText");
let _: () = msg_send![(*marked_text_ref), release];
let marked_text = NSMutableAttributedString::alloc(nil);
let has_attr = msg_send![string, isKindOfClass:class!(NSAttributedString)];
if has_attr {
marked_text.initWithAttributedString(string);
} else {
marked_text.initWithString(string);
};
*marked_text_ref = marked_text;
}
}
extern fn unmark_text(this: &Object, _sel: Sel) {
//println!("unmarkText");
unsafe {
let marked_text: id = *this.get_ivar("markedText");
let mutable_string = marked_text.mutableString();
let _: () = msg_send![mutable_string, setString:""];
let input_context: id = msg_send![this, inputContext];
let _: () = msg_send![input_context, discardMarkedText];
}
}
extern fn valid_attributes_for_marked_text(_this: &Object, _sel: Sel) -> id {
//println!("validAttributesForMarkedText");
unsafe { msg_send![class!(NSArray), array] }
}
extern fn attributed_substring_for_proposed_range(
_this: &Object,
_sel: Sel,
_range: NSRange,
_actual_range: *mut c_void, // *mut NSRange
) -> id {
//println!("attributedSubstringForProposedRange");
nil
}
extern fn character_index_for_point(_this: &Object, _sel: Sel, _point: NSPoint) -> NSUInteger {
//println!("characterIndexForPoint");
0
}
extern fn first_rect_for_character_range(
this: &Object,
_sel: Sel,
_range: NSRange,
_actual_range: *mut c_void, // *mut NSRange
) -> NSRect {
//println!("firstRectForCharacterRange");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let (x, y) = state.ime_spot.unwrap_or_else(|| {
let content_rect = NSWindow::contentRectForFrameRect_(
state.window,
NSWindow::frame(state.window),
);
let x = content_rect.origin.x;
let y = util::bottom_left_to_top_left(content_rect);
(x, y)
});
NSRect::new(
NSPoint::new(x as _, y as _),
NSSize::new(0.0, 0.0),
)
}
}
extern fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_range: NSRange) {
//println!("insertText");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let has_attr = msg_send![string, isKindOfClass:class!(NSAttributedString)];
let characters = if has_attr {
// This is a *mut NSAttributedString
msg_send![string, string]
} else {
// This is already a *mut NSString
string
};
let slice = slice::from_raw_parts(
characters.UTF8String() as *const c_uchar,
characters.len(),
);
let string = str::from_utf8_unchecked(slice);
state.is_key_down = true;
// We don't need this now, but it's here if that changes.
//let event: id = msg_send![class!(NSApp), currentEvent];
let mut events = VecDeque::with_capacity(characters.len());
for character in string.chars() {
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::ReceivedCharacter(character),
});
}
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.append(&mut events);
}
}
}
extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) {
//println!("doCommandBySelector");
// Basically, we're sent this message whenever a keyboard event that doesn't generate a "human readable" character
// happens, i.e. newlines, tabs, and Ctrl+C.
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let shared = if let Some(shared) = state.shared.upgrade() {
shared
} else {
return;
};
let mut events = VecDeque::with_capacity(1);
if command == sel!(insertNewline:) {
// The `else` condition would emit the same character, but I'm keeping this here both...
// 1) as a reminder for how `doCommandBySelector` works
// 2) to make our use of carriage return explicit
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::ReceivedCharacter('\r'),
});
} else {
let raw_characters = state.raw_characters.take();
if let Some(raw_characters) = raw_characters {
for character in raw_characters.chars() {
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::ReceivedCharacter(character),
});
}
}
};
shared.pending_events
.lock()
.unwrap()
.append(&mut events);
}
}
fn get_characters(event: id, ignore_modifiers: bool) -> String {
unsafe {
let characters: id = if ignore_modifiers {
msg_send![event, charactersIgnoringModifiers]
} else {
msg_send![event, characters]
};
assert_ne!(characters, nil);
let slice = slice::from_raw_parts(
characters.UTF8String() as *const c_uchar,
characters.len(),
);
let string = str::from_utf8_unchecked(slice);
string.to_owned()
}
}
// Retrieves a layout-independent keycode given an event.
fn retrieve_keycode(event: id) -> Option<events::VirtualKeyCode> {
#[inline]
fn get_code(ev: id, raw: bool) -> Option<events::VirtualKeyCode> {
let characters = get_characters(ev, raw);
characters.chars().next().map_or(None, |c| char_to_keycode(c))
}
// Cmd switches Roman letters for Dvorak-QWERTY layout, so we try modified characters first.
// If we don't get a match, then we fall back to unmodified characters.
let code = get_code(event, false)
.or_else(|| {
get_code(event, true)
});
// We've checked all layout related keys, so fall through to scancode.
// Reaching this code means that the key is layout-independent (e.g. Backspace, Return).
//
// We're additionally checking here for F21-F24 keys, since their keycode
// can vary, but we know that they are encoded
// in characters property.
code.or_else(|| {
let scancode = get_scancode(event);
scancode_to_keycode(scancode)
.or_else(|| {
check_function_keys(&get_characters(event, true))
})
})
}
extern fn key_down(this: &Object, _sel: Sel, event: id) {
//println!("keyDown");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let window_id = WindowId(get_window_id(state.window));
let characters = get_characters(event, false);
state.raw_characters = Some(characters.clone());
let scancode = get_scancode(event) as u32;
let virtual_keycode = retrieve_keycode(event);
let is_repeat = msg_send![event, isARepeat];
let window_event = Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: ElementState::Pressed,
scancode,
virtual_keycode,
modifiers: event_mods(event),
},
},
};
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
// Emit `ReceivedCharacter` for key repeats
if is_repeat && state.is_key_down{
for character in characters.chars() {
let window_event = Event::WindowEvent {
window_id,
event: WindowEvent::ReceivedCharacter(character),
};
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
}
} else {
// Some keys (and only *some*, with no known reason) don't trigger `insertText`, while others do...
// So, we don't give repeats the opportunity to trigger that, since otherwise our hack will cause some
// keys to generate twice as many characters.
let array: id = msg_send![class!(NSArray), arrayWithObject:event];
let (): _ = msg_send![this, interpretKeyEvents:array];
}
}
}
}
extern fn key_up(this: &Object, _sel: Sel, event: id) {
//println!("keyUp");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
state.is_key_down = false;
let scancode = get_scancode(event) as u32;
let virtual_keycode = retrieve_keycode(event);
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: ElementState::Released,
scancode,
virtual_keycode,
modifiers: event_mods(event),
},
},
};
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
}
}
}
extern fn insert_tab(this: &Object, _sel: Sel, _sender: id) {
unsafe {
let window: id = msg_send![this, window];
let first_responder: id = msg_send![window, firstResponder];
let this_ptr = this as *const _ as *mut _;
if first_responder == this_ptr {
let (): _ = msg_send![window, selectNextKeyView:this];
}
}
}
extern fn insert_back_tab(this: &Object, _sel: Sel, _sender: id) {
unsafe {
let window: id = msg_send![this, window];
let first_responder: id = msg_send![window, firstResponder];
let this_ptr = this as *const _ as *mut _;
if first_responder == this_ptr {
let (): _ = msg_send![window, selectPreviousKeyView:this];
}
}
}
fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: ElementState) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::MouseInput {
device_id: DEVICE_ID,
state: button_state,
button,
modifiers: event_mods(event),
},
};
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
}
}
}
extern fn mouse_down(this: &Object, _sel: Sel, event: id) {
mouse_click(this, event, MouseButton::Left, ElementState::Pressed);
}
extern fn mouse_up(this: &Object, _sel: Sel, event: id) {
mouse_click(this, event, MouseButton::Left, ElementState::Released);
}
extern fn right_mouse_down(this: &Object, _sel: Sel, event: id) {
mouse_click(this, event, MouseButton::Right, ElementState::Pressed);
}
extern fn right_mouse_up(this: &Object, _sel: Sel, event: id) {
mouse_click(this, event, MouseButton::Right, ElementState::Released);
}
extern fn other_mouse_down(this: &Object, _sel: Sel, event: id) {
mouse_click(this, event, MouseButton::Middle, ElementState::Pressed);
}
extern fn other_mouse_up(this: &Object, _sel: Sel, event: id) {
mouse_click(this, event, MouseButton::Middle, ElementState::Released);
}
fn mouse_motion(this: &Object, event: id) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
// We have to do this to have access to the `NSView` trait...
let view: id = this as *const _ as *mut _;
let window_point = event.locationInWindow();
let view_point = view.convertPoint_fromView_(window_point, nil);
let view_rect = NSView::frame(view);
if view_point.x.is_sign_negative()
|| view_point.y.is_sign_negative()
|| view_point.x > view_rect.size.width
|| view_point.y > view_rect.size.height {
// Point is outside of the client area (view)
return;
}
let x = view_point.x as f64;
let y = view_rect.size.height as f64 - view_point.y as f64;
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::CursorMoved {
device_id: DEVICE_ID,
position: (x, y).into(),
modifiers: event_mods(event),
},
};
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
}
}
}
extern fn mouse_moved(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
}
extern fn mouse_dragged(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
}
extern fn right_mouse_dragged(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
}
extern fn other_mouse_dragged(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
}
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
extern fn wants_key_down_for_event(_this: &Object, _se: Sel, _event: id) -> BOOL {
YES
}

1375
src/platform/macos/window.rs Normal file

File diff suppressed because it is too large Load Diff

26
src/platform/mod.rs Normal file
View File

@@ -0,0 +1,26 @@
pub use self::platform::*;
#[cfg(target_os = "windows")]
#[path="windows/mod.rs"]
mod platform;
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
#[path="linux/mod.rs"]
mod platform;
#[cfg(target_os = "macos")]
#[path="macos/mod.rs"]
mod platform;
#[cfg(target_os = "android")]
#[path="android/mod.rs"]
mod platform;
#[cfg(target_os = "ios")]
#[path="ios/mod.rs"]
mod platform;
#[cfg(target_os = "emscripten")]
#[path="emscripten/mod.rs"]
mod platform;
#[cfg(all(not(target_os = "ios"), not(target_os = "windows"), not(target_os = "linux"),
not(target_os = "macos"), not(target_os = "android"), not(target_os = "dragonfly"),
not(target_os = "freebsd"), not(target_os = "netbsd"), not(target_os = "openbsd"),
not(target_os = "emscripten")))]
compile_error!("The platform you're compiling for is not supported by winit");

189
src/platform/windows/dpi.rs Normal file
View File

@@ -0,0 +1,189 @@
#![allow(non_snake_case, unused_unsafe)]
use std::mem;
use std::os::raw::c_void;
use std::sync::{Once, ONCE_INIT};
use winapi::shared::minwindef::{BOOL, UINT, FALSE};
use winapi::shared::windef::{
DPI_AWARENESS_CONTEXT,
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE,
HMONITOR,
HWND,
};
use winapi::shared::winerror::S_OK;
use winapi::um::libloaderapi::{GetProcAddress, LoadLibraryA};
use winapi::um::shellscalingapi::{
MDT_EFFECTIVE_DPI,
MONITOR_DPI_TYPE,
PROCESS_DPI_AWARENESS,
PROCESS_PER_MONITOR_DPI_AWARE,
};
use winapi::um::wingdi::{GetDeviceCaps, LOGPIXELSX};
use winapi::um::winnt::{HRESULT, LPCSTR};
use winapi::um::winuser::{self, MONITOR_DEFAULTTONEAREST};
const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2: DPI_AWARENESS_CONTEXT = -4isize as _;
type SetProcessDPIAware = unsafe extern "system" fn () -> BOOL;
type SetProcessDpiAwareness = unsafe extern "system" fn (
value: PROCESS_DPI_AWARENESS,
) -> HRESULT;
type SetProcessDpiAwarenessContext = unsafe extern "system" fn (
value: DPI_AWARENESS_CONTEXT,
) -> BOOL;
type GetDpiForWindow = unsafe extern "system" fn (hwnd: HWND) -> UINT;
type GetDpiForMonitor = unsafe extern "system" fn (
hmonitor: HMONITOR,
dpi_type: MONITOR_DPI_TYPE,
dpi_x: *mut UINT,
dpi_y: *mut UINT,
) -> HRESULT;
type EnableNonClientDpiScaling = unsafe extern "system" fn (hwnd: HWND) -> BOOL;
// Helper function to dynamically load function pointer.
// `library` and `function` must be zero-terminated.
fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
assert_eq!(library.chars().last(), Some('\0'));
assert_eq!(function.chars().last(), Some('\0'));
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
let module = unsafe { LoadLibraryA(library.as_ptr() as LPCSTR) };
if module.is_null() {
return None;
}
let function_ptr = unsafe { GetProcAddress(module, function.as_ptr() as LPCSTR) };
if function_ptr.is_null() {
return None;
}
Some(function_ptr as _)
}
macro_rules! get_function {
($lib:expr, $func:ident) => {
get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0'))
.map(|f| unsafe { mem::transmute::<*const _, $func>(f) })
}
}
lazy_static! {
static ref GET_DPI_FOR_WINDOW: Option<GetDpiForWindow> = get_function!(
"user32.dll",
GetDpiForWindow
);
static ref GET_DPI_FOR_MONITOR: Option<GetDpiForMonitor> = get_function!(
"shcore.dll",
GetDpiForMonitor
);
static ref ENABLE_NON_CLIENT_DPI_SCALING: Option<EnableNonClientDpiScaling> = get_function!(
"user32.dll",
EnableNonClientDpiScaling
);
}
pub fn become_dpi_aware(enable: bool) {
if !enable { return; }
static ENABLE_DPI_AWARENESS: Once = ONCE_INIT;
ENABLE_DPI_AWARENESS.call_once(|| { unsafe {
if let Some(SetProcessDpiAwarenessContext) = get_function!(
"user32.dll",
SetProcessDpiAwarenessContext
) {
// We are on Windows 10 Anniversary Update (1607) or later.
if SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
== FALSE {
// V2 only works with Windows 10 Creators Update (1703). Try using the older
// V1 if we can't set V2.
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
}
} else if let Some(SetProcessDpiAwareness) = get_function!(
"shcore.dll",
SetProcessDpiAwareness
) {
// We are on Windows 8.1 or later.
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
} else if let Some(SetProcessDPIAware) = get_function!(
"user32.dll",
SetProcessDPIAware
) {
// We are on Vista or later.
SetProcessDPIAware();
}
} });
}
pub fn enable_non_client_dpi_scaling(hwnd: HWND) {
unsafe {
if let Some(EnableNonClientDpiScaling) = *ENABLE_NON_CLIENT_DPI_SCALING {
EnableNonClientDpiScaling(hwnd);
}
}
}
pub fn get_monitor_dpi(hmonitor: HMONITOR) -> Option<u32> {
unsafe {
if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
// We are on Windows 8.1 or later.
let mut dpi_x = 0;
let mut dpi_y = 0;
if GetDpiForMonitor(hmonitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK {
// MSDN says that "the values of *dpiX and *dpiY are identical. You only need to
// record one of the values to determine the DPI and respond appropriately".
// https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx
return Some(dpi_x as u32)
}
}
}
None
}
pub const BASE_DPI: u32 = 96;
pub fn dpi_to_scale_factor(dpi: u32) -> f64 {
dpi as f64 / BASE_DPI as f64
}
pub unsafe fn get_hwnd_dpi(hwnd: HWND) -> u32 {
let hdc = winuser::GetDC(hwnd);
if hdc.is_null() {
panic!("[winit] `GetDC` returned null!");
}
if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW {
// We are on Windows 10 Anniversary Update (1607) or later.
match GetDpiForWindow(hwnd) {
0 => BASE_DPI, // 0 is returned if hwnd is invalid
dpi => dpi as u32,
}
} else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
// We are on Windows 8.1 or later.
let monitor = winuser::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if monitor.is_null() {
return BASE_DPI;
}
let mut dpi_x = 0;
let mut dpi_y = 0;
if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK {
dpi_x as u32
} else {
BASE_DPI
}
} else {
// We are on Vista or later.
if winuser::IsProcessDPIAware() != FALSE {
// If the process is DPI aware, then scaling must be handled by the application using
// this DPI value.
GetDeviceCaps(hdc, LOGPIXELSX) as u32
} else {
// If the process is DPI unaware, then scaling is performed by the OS; we thus return
// 96 (scale factor 1.0) to prevent the window from being re-scaled by both the
// application and the WM.
BASE_DPI
}
}
}
pub fn get_hwnd_scale_factor(hwnd: HWND) -> f64 {
dpi_to_scale_factor(unsafe { get_hwnd_dpi(hwnd) })
}

View File

@@ -0,0 +1,226 @@
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{mem, ptr};
use winapi::ctypes::c_void;
use winapi::shared::guiddef::REFIID;
use winapi::shared::minwindef::{DWORD, MAX_PATH, UINT, ULONG};
use winapi::shared::windef::{HWND, POINTL};
use winapi::shared::winerror::S_OK;
use winapi::um::objidl::IDataObject;
use winapi::um::oleidl::{DROPEFFECT_COPY, DROPEFFECT_NONE, IDropTarget, IDropTargetVtbl};
use winapi::um::winnt::HRESULT;
use winapi::um::{shellapi, unknwnbase};
use platform::platform::events_loop::send_event;
use platform::platform::WindowId;
use {Event, WindowId as SuperWindowId};
#[repr(C)]
pub struct FileDropHandlerData {
pub interface: IDropTarget,
refcount: AtomicUsize,
window: HWND,
cursor_effect: DWORD,
hovered_is_valid: bool, // If the currently hovered item is not valid there must not be any `HoveredFileCancelled` emitted
}
pub struct FileDropHandler {
pub data: *mut FileDropHandlerData,
}
#[allow(non_snake_case)]
impl FileDropHandler {
pub fn new(window: HWND) -> FileDropHandler {
let data = Box::new(FileDropHandlerData {
interface: IDropTarget {
lpVtbl: &DROP_TARGET_VTBL as *const IDropTargetVtbl,
},
refcount: AtomicUsize::new(1),
window,
cursor_effect: DROPEFFECT_NONE,
hovered_is_valid: false,
});
FileDropHandler {
data: Box::into_raw(data),
}
}
// Implement IUnknown
pub unsafe extern "system" fn QueryInterface(
_this: *mut unknwnbase::IUnknown,
_riid: REFIID,
_ppvObject: *mut *mut c_void,
) -> HRESULT {
// This function doesn't appear to be required for an `IDropTarget`.
// An implementation would be nice however.
unimplemented!();
}
pub unsafe extern "system" fn AddRef(this: *mut unknwnbase::IUnknown) -> ULONG {
let drop_handler_data = Self::from_interface(this);
let count = drop_handler_data.refcount.fetch_add(1, Ordering::Release) + 1;
count as ULONG
}
pub unsafe extern "system" fn Release(this: *mut unknwnbase::IUnknown) -> ULONG {
let drop_handler = Self::from_interface(this);
let count = drop_handler.refcount.fetch_sub(1, Ordering::Release) - 1;
if count == 0 {
// Destroy the underlying data
Box::from_raw(drop_handler as *mut FileDropHandlerData);
}
count as ULONG
}
pub unsafe extern "system" fn DragEnter(
this: *mut IDropTarget,
pDataObj: *const IDataObject,
_grfKeyState: DWORD,
_pt: *const POINTL,
pdwEffect: *mut DWORD,
) -> HRESULT {
use events::WindowEvent::HoveredFile;
let drop_handler = Self::from_interface(this);
let hdrop = Self::iterate_filenames(pDataObj, |filename| {
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(drop_handler.window)),
event: HoveredFile(filename),
});
});
drop_handler.hovered_is_valid = hdrop.is_some();
drop_handler.cursor_effect = if drop_handler.hovered_is_valid {
DROPEFFECT_COPY
} else {
DROPEFFECT_NONE
};
*pdwEffect = drop_handler.cursor_effect;
S_OK
}
pub unsafe extern "system" fn DragOver(
this: *mut IDropTarget,
_grfKeyState: DWORD,
_pt: *const POINTL,
pdwEffect: *mut DWORD,
) -> HRESULT {
let drop_handler = Self::from_interface(this);
*pdwEffect = drop_handler.cursor_effect;
S_OK
}
pub unsafe extern "system" fn DragLeave(this: *mut IDropTarget) -> HRESULT {
use events::WindowEvent::HoveredFileCancelled;
let drop_handler = Self::from_interface(this);
if drop_handler.hovered_is_valid {
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(drop_handler.window)),
event: HoveredFileCancelled,
});
}
S_OK
}
pub unsafe extern "system" fn Drop(
this: *mut IDropTarget,
pDataObj: *const IDataObject,
_grfKeyState: DWORD,
_pt: *const POINTL,
_pdwEffect: *mut DWORD,
) -> HRESULT {
use events::WindowEvent::DroppedFile;
let drop_handler = Self::from_interface(this);
let hdrop = Self::iterate_filenames(pDataObj, |filename| {
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(drop_handler.window)),
event: DroppedFile(filename),
});
});
if let Some(hdrop) = hdrop {
shellapi::DragFinish(hdrop);
}
S_OK
}
unsafe fn from_interface<'a, InterfaceT>(this: *mut InterfaceT) -> &'a mut FileDropHandlerData {
&mut *(this as *mut _)
}
unsafe fn iterate_filenames<F>(data_obj: *const IDataObject, callback: F) -> Option<shellapi::HDROP>
where
F: Fn(PathBuf),
{
use winapi::ctypes::wchar_t;
use winapi::shared::winerror::{SUCCEEDED, DV_E_FORMATETC};
use winapi::shared::wtypes::{CLIPFORMAT, DVASPECT_CONTENT};
use winapi::um::objidl::{FORMATETC, TYMED_HGLOBAL};
use winapi::um::shellapi::DragQueryFileW;
use winapi::um::winuser::CF_HDROP;
let mut drop_format = FORMATETC {
cfFormat: CF_HDROP as CLIPFORMAT,
ptd: ptr::null(),
dwAspect: DVASPECT_CONTENT,
lindex: -1,
tymed: TYMED_HGLOBAL,
};
let mut medium = mem::uninitialized();
let get_data_result = (*data_obj).GetData(&mut drop_format, &mut medium);
if SUCCEEDED(get_data_result) {
let hglobal = (*medium.u).hGlobal();
let hdrop = (*hglobal) as shellapi::HDROP;
// The second parameter (0xFFFFFFFF) instructs the function to return the item count
let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, ptr::null_mut(), 0);
let mut pathbuf: [wchar_t; MAX_PATH] = mem::uninitialized();
for i in 0..item_count {
let character_count =
DragQueryFileW(hdrop, i, pathbuf.as_mut_ptr(), MAX_PATH as UINT) as usize;
if character_count > 0 {
callback(OsString::from_wide(&pathbuf[0..character_count]).into());
}
}
return Some(hdrop);
} else if get_data_result == DV_E_FORMATETC {
// If the dropped item is not a file this error will occur.
// In this case it is OK to return without taking further action.
debug!("Error occured while processing dropped/hovered item: item is not a file.");
return None;
} else {
debug!("Unexpected error occured while processing dropped/hovered item.");
return None;
}
}
}
impl Drop for FileDropHandler {
fn drop(&mut self) {
unsafe {
FileDropHandler::Release(self.data as *mut unknwnbase::IUnknown);
}
}
}
static DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl {
parent: unknwnbase::IUnknownVtbl {
QueryInterface: FileDropHandler::QueryInterface,
AddRef: FileDropHandler::AddRef,
Release: FileDropHandler::Release,
},
DragEnter: FileDropHandler::DragEnter,
DragOver: FileDropHandler::DragOver,
DragLeave: FileDropHandler::DragLeave,
Drop: FileDropHandler::Drop,
};

View File

@@ -0,0 +1,321 @@
use std::{char, ptr};
use std::os::raw::c_int;
use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use events::VirtualKeyCode;
use events::ModifiersState;
use winapi::shared::minwindef::{WPARAM, LPARAM, UINT, HKL, HKL__};
use winapi::um::winuser;
use ScanCode;
fn key_pressed(vkey: c_int) -> bool {
unsafe {
(winuser::GetKeyState(vkey) & (1 << 15)) == (1 << 15)
}
}
pub fn get_key_mods() -> ModifiersState {
let mut mods = ModifiersState::default();
let filter_out_altgr = layout_uses_altgr() && key_pressed(winuser::VK_RMENU);
mods.shift = key_pressed(winuser::VK_SHIFT);
mods.ctrl = key_pressed(winuser::VK_CONTROL) && !filter_out_altgr;
mods.alt = key_pressed(winuser::VK_MENU) && !filter_out_altgr;
mods.logo = key_pressed(winuser::VK_LWIN) || key_pressed(winuser::VK_RWIN);
mods
}
unsafe fn get_char(keyboard_state: &[u8; 256], v_key: u32, hkl: HKL) -> Option<char> {
let mut unicode_bytes = [0u16; 5];
let len = winuser::ToUnicodeEx(v_key, 0, keyboard_state.as_ptr(), unicode_bytes.as_mut_ptr(), unicode_bytes.len() as _, 0, hkl);
if len >= 1 {
char::decode_utf16(unicode_bytes.into_iter().cloned()).next().and_then(|c| c.ok())
} else {
None
}
}
/// Figures out if the keyboard layout has an AltGr key instead of an Alt key.
///
/// Unfortunately, the Windows API doesn't give a way for us to conveniently figure that out. So,
/// we use a technique blatantly stolen from [the Firefox source code][source]: iterate over every
/// possible virtual key and compare the `char` output when AltGr is pressed vs when it isn't. If
/// pressing AltGr outputs characters that are different from the standard characters, the layout
/// uses AltGr. Otherwise, it doesn't.
///
/// [source]: https://github.com/mozilla/gecko-dev/blob/265e6721798a455604328ed5262f430cfcc37c2f/widget/windows/KeyboardLayout.cpp#L4356-L4416
fn layout_uses_altgr() -> bool {
unsafe {
static ACTIVE_LAYOUT: AtomicPtr<HKL__> = AtomicPtr::new(ptr::null_mut());
static USES_ALTGR: AtomicBool = AtomicBool::new(false);
let hkl = winuser::GetKeyboardLayout(0);
let old_hkl = ACTIVE_LAYOUT.swap(hkl, Ordering::SeqCst);
if hkl == old_hkl {
return USES_ALTGR.load(Ordering::SeqCst);
}
let mut keyboard_state_altgr = [0u8; 256];
// AltGr is an alias for Ctrl+Alt for... some reason. Whatever it is, those are the keypresses
// we have to emulate to do an AltGr test.
keyboard_state_altgr[winuser::VK_MENU as usize] = 0x80;
keyboard_state_altgr[winuser::VK_CONTROL as usize] = 0x80;
let keyboard_state_empty = [0u8; 256];
for v_key in 0..=255 {
let key_noaltgr = get_char(&keyboard_state_empty, v_key, hkl);
let key_altgr = get_char(&keyboard_state_altgr, v_key, hkl);
if let (Some(noaltgr), Some(altgr)) = (key_noaltgr, key_altgr) {
if noaltgr != altgr {
USES_ALTGR.store(true, Ordering::SeqCst);
return true;
}
}
}
USES_ALTGR.store(false, Ordering::SeqCst);
false
}
}
pub fn vkey_to_winit_vkey(vkey: c_int) -> Option<VirtualKeyCode> {
// VK_* codes are documented here https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
match vkey {
//winuser::VK_LBUTTON => Some(VirtualKeyCode::Lbutton),
//winuser::VK_RBUTTON => Some(VirtualKeyCode::Rbutton),
//winuser::VK_CANCEL => Some(VirtualKeyCode::Cancel),
//winuser::VK_MBUTTON => Some(VirtualKeyCode::Mbutton),
//winuser::VK_XBUTTON1 => Some(VirtualKeyCode::Xbutton1),
//winuser::VK_XBUTTON2 => Some(VirtualKeyCode::Xbutton2),
winuser::VK_BACK => Some(VirtualKeyCode::Back),
winuser::VK_TAB => Some(VirtualKeyCode::Tab),
//winuser::VK_CLEAR => Some(VirtualKeyCode::Clear),
winuser::VK_RETURN => Some(VirtualKeyCode::Return),
winuser::VK_LSHIFT => Some(VirtualKeyCode::LShift),
winuser::VK_RSHIFT => Some(VirtualKeyCode::RShift),
winuser::VK_LCONTROL => Some(VirtualKeyCode::LControl),
winuser::VK_RCONTROL => Some(VirtualKeyCode::RControl),
winuser::VK_LMENU => Some(VirtualKeyCode::LAlt),
winuser::VK_RMENU => Some(VirtualKeyCode::RAlt),
winuser::VK_PAUSE => Some(VirtualKeyCode::Pause),
winuser::VK_CAPITAL => Some(VirtualKeyCode::Capital),
winuser::VK_KANA => Some(VirtualKeyCode::Kana),
//winuser::VK_HANGUEL => Some(VirtualKeyCode::Hanguel),
//winuser::VK_HANGUL => Some(VirtualKeyCode::Hangul),
//winuser::VK_JUNJA => Some(VirtualKeyCode::Junja),
//winuser::VK_FINAL => Some(VirtualKeyCode::Final),
//winuser::VK_HANJA => Some(VirtualKeyCode::Hanja),
winuser::VK_KANJI => Some(VirtualKeyCode::Kanji),
winuser::VK_ESCAPE => Some(VirtualKeyCode::Escape),
winuser::VK_CONVERT => Some(VirtualKeyCode::Convert),
winuser::VK_NONCONVERT => Some(VirtualKeyCode::NoConvert),
//winuser::VK_ACCEPT => Some(VirtualKeyCode::Accept),
//winuser::VK_MODECHANGE => Some(VirtualKeyCode::Modechange),
winuser::VK_SPACE => Some(VirtualKeyCode::Space),
winuser::VK_PRIOR => Some(VirtualKeyCode::PageUp),
winuser::VK_NEXT => Some(VirtualKeyCode::PageDown),
winuser::VK_END => Some(VirtualKeyCode::End),
winuser::VK_HOME => Some(VirtualKeyCode::Home),
winuser::VK_LEFT => Some(VirtualKeyCode::Left),
winuser::VK_UP => Some(VirtualKeyCode::Up),
winuser::VK_RIGHT => Some(VirtualKeyCode::Right),
winuser::VK_DOWN => Some(VirtualKeyCode::Down),
//winuser::VK_SELECT => Some(VirtualKeyCode::Select),
//winuser::VK_PRINT => Some(VirtualKeyCode::Print),
//winuser::VK_EXECUTE => Some(VirtualKeyCode::Execute),
winuser::VK_SNAPSHOT => Some(VirtualKeyCode::Snapshot),
winuser::VK_INSERT => Some(VirtualKeyCode::Insert),
winuser::VK_DELETE => Some(VirtualKeyCode::Delete),
//winuser::VK_HELP => Some(VirtualKeyCode::Help),
0x30 => Some(VirtualKeyCode::Key0),
0x31 => Some(VirtualKeyCode::Key1),
0x32 => Some(VirtualKeyCode::Key2),
0x33 => Some(VirtualKeyCode::Key3),
0x34 => Some(VirtualKeyCode::Key4),
0x35 => Some(VirtualKeyCode::Key5),
0x36 => Some(VirtualKeyCode::Key6),
0x37 => Some(VirtualKeyCode::Key7),
0x38 => Some(VirtualKeyCode::Key8),
0x39 => Some(VirtualKeyCode::Key9),
0x41 => Some(VirtualKeyCode::A),
0x42 => Some(VirtualKeyCode::B),
0x43 => Some(VirtualKeyCode::C),
0x44 => Some(VirtualKeyCode::D),
0x45 => Some(VirtualKeyCode::E),
0x46 => Some(VirtualKeyCode::F),
0x47 => Some(VirtualKeyCode::G),
0x48 => Some(VirtualKeyCode::H),
0x49 => Some(VirtualKeyCode::I),
0x4A => Some(VirtualKeyCode::J),
0x4B => Some(VirtualKeyCode::K),
0x4C => Some(VirtualKeyCode::L),
0x4D => Some(VirtualKeyCode::M),
0x4E => Some(VirtualKeyCode::N),
0x4F => Some(VirtualKeyCode::O),
0x50 => Some(VirtualKeyCode::P),
0x51 => Some(VirtualKeyCode::Q),
0x52 => Some(VirtualKeyCode::R),
0x53 => Some(VirtualKeyCode::S),
0x54 => Some(VirtualKeyCode::T),
0x55 => Some(VirtualKeyCode::U),
0x56 => Some(VirtualKeyCode::V),
0x57 => Some(VirtualKeyCode::W),
0x58 => Some(VirtualKeyCode::X),
0x59 => Some(VirtualKeyCode::Y),
0x5A => Some(VirtualKeyCode::Z),
//winuser::VK_LWIN => Some(VirtualKeyCode::Lwin),
//winuser::VK_RWIN => Some(VirtualKeyCode::Rwin),
winuser::VK_APPS => Some(VirtualKeyCode::Apps),
winuser::VK_SLEEP => Some(VirtualKeyCode::Sleep),
winuser::VK_NUMPAD0 => Some(VirtualKeyCode::Numpad0),
winuser::VK_NUMPAD1 => Some(VirtualKeyCode::Numpad1),
winuser::VK_NUMPAD2 => Some(VirtualKeyCode::Numpad2),
winuser::VK_NUMPAD3 => Some(VirtualKeyCode::Numpad3),
winuser::VK_NUMPAD4 => Some(VirtualKeyCode::Numpad4),
winuser::VK_NUMPAD5 => Some(VirtualKeyCode::Numpad5),
winuser::VK_NUMPAD6 => Some(VirtualKeyCode::Numpad6),
winuser::VK_NUMPAD7 => Some(VirtualKeyCode::Numpad7),
winuser::VK_NUMPAD8 => Some(VirtualKeyCode::Numpad8),
winuser::VK_NUMPAD9 => Some(VirtualKeyCode::Numpad9),
winuser::VK_MULTIPLY => Some(VirtualKeyCode::Multiply),
winuser::VK_ADD => Some(VirtualKeyCode::Add),
//winuser::VK_SEPARATOR => Some(VirtualKeyCode::Separator),
winuser::VK_SUBTRACT => Some(VirtualKeyCode::Subtract),
winuser::VK_DECIMAL => Some(VirtualKeyCode::Decimal),
winuser::VK_DIVIDE => Some(VirtualKeyCode::Divide),
winuser::VK_F1 => Some(VirtualKeyCode::F1),
winuser::VK_F2 => Some(VirtualKeyCode::F2),
winuser::VK_F3 => Some(VirtualKeyCode::F3),
winuser::VK_F4 => Some(VirtualKeyCode::F4),
winuser::VK_F5 => Some(VirtualKeyCode::F5),
winuser::VK_F6 => Some(VirtualKeyCode::F6),
winuser::VK_F7 => Some(VirtualKeyCode::F7),
winuser::VK_F8 => Some(VirtualKeyCode::F8),
winuser::VK_F9 => Some(VirtualKeyCode::F9),
winuser::VK_F10 => Some(VirtualKeyCode::F10),
winuser::VK_F11 => Some(VirtualKeyCode::F11),
winuser::VK_F12 => Some(VirtualKeyCode::F12),
winuser::VK_F13 => Some(VirtualKeyCode::F13),
winuser::VK_F14 => Some(VirtualKeyCode::F14),
winuser::VK_F15 => Some(VirtualKeyCode::F15),
winuser::VK_F16 => Some(VirtualKeyCode::F16),
winuser::VK_F17 => Some(VirtualKeyCode::F17),
winuser::VK_F18 => Some(VirtualKeyCode::F18),
winuser::VK_F19 => Some(VirtualKeyCode::F19),
winuser::VK_F20 => Some(VirtualKeyCode::F20),
winuser::VK_F21 => Some(VirtualKeyCode::F21),
winuser::VK_F22 => Some(VirtualKeyCode::F22),
winuser::VK_F23 => Some(VirtualKeyCode::F23),
winuser::VK_F24 => Some(VirtualKeyCode::F24),
winuser::VK_NUMLOCK => Some(VirtualKeyCode::Numlock),
winuser::VK_SCROLL => Some(VirtualKeyCode::Scroll),
winuser::VK_BROWSER_BACK => Some(VirtualKeyCode::NavigateBackward),
winuser::VK_BROWSER_FORWARD => Some(VirtualKeyCode::NavigateForward),
winuser::VK_BROWSER_REFRESH => Some(VirtualKeyCode::WebRefresh),
winuser::VK_BROWSER_STOP => Some(VirtualKeyCode::WebStop),
winuser::VK_BROWSER_SEARCH => Some(VirtualKeyCode::WebSearch),
winuser::VK_BROWSER_FAVORITES => Some(VirtualKeyCode::WebFavorites),
winuser::VK_BROWSER_HOME => Some(VirtualKeyCode::WebHome),
winuser::VK_VOLUME_MUTE => Some(VirtualKeyCode::Mute),
winuser::VK_VOLUME_DOWN => Some(VirtualKeyCode::VolumeDown),
winuser::VK_VOLUME_UP => Some(VirtualKeyCode::VolumeUp),
winuser::VK_MEDIA_NEXT_TRACK => Some(VirtualKeyCode::NextTrack),
winuser::VK_MEDIA_PREV_TRACK => Some(VirtualKeyCode::PrevTrack),
winuser::VK_MEDIA_STOP => Some(VirtualKeyCode::MediaStop),
winuser::VK_MEDIA_PLAY_PAUSE => Some(VirtualKeyCode::PlayPause),
winuser::VK_LAUNCH_MAIL => Some(VirtualKeyCode::Mail),
winuser::VK_LAUNCH_MEDIA_SELECT => Some(VirtualKeyCode::MediaSelect),
/*winuser::VK_LAUNCH_APP1 => Some(VirtualKeyCode::Launch_app1),
winuser::VK_LAUNCH_APP2 => Some(VirtualKeyCode::Launch_app2),*/
winuser::VK_OEM_PLUS => Some(VirtualKeyCode::Equals),
winuser::VK_OEM_COMMA => Some(VirtualKeyCode::Comma),
winuser::VK_OEM_MINUS => Some(VirtualKeyCode::Minus),
winuser::VK_OEM_PERIOD => Some(VirtualKeyCode::Period),
winuser::VK_OEM_1 => map_text_keys(vkey),
winuser::VK_OEM_2 => map_text_keys(vkey),
winuser::VK_OEM_3 => map_text_keys(vkey),
winuser::VK_OEM_4 => map_text_keys(vkey),
winuser::VK_OEM_5 => map_text_keys(vkey),
winuser::VK_OEM_6 => map_text_keys(vkey),
winuser::VK_OEM_7 => map_text_keys(vkey),
/*winuser::VK_OEM_8 => Some(VirtualKeyCode::Oem_8), */
winuser::VK_OEM_102 => Some(VirtualKeyCode::OEM102),
/*winuser::VK_PROCESSKEY => Some(VirtualKeyCode::Processkey),
winuser::VK_PACKET => Some(VirtualKeyCode::Packet),
winuser::VK_ATTN => Some(VirtualKeyCode::Attn),
winuser::VK_CRSEL => Some(VirtualKeyCode::Crsel),
winuser::VK_EXSEL => Some(VirtualKeyCode::Exsel),
winuser::VK_EREOF => Some(VirtualKeyCode::Ereof),
winuser::VK_PLAY => Some(VirtualKeyCode::Play),
winuser::VK_ZOOM => Some(VirtualKeyCode::Zoom),
winuser::VK_NONAME => Some(VirtualKeyCode::Noname),
winuser::VK_PA1 => Some(VirtualKeyCode::Pa1),
winuser::VK_OEM_CLEAR => Some(VirtualKeyCode::Oem_clear),*/
_ => None
}
}
pub fn handle_extended_keys(vkey: c_int, mut scancode: UINT, extended: bool) -> Option<(c_int, UINT)> {
// Welcome to hell https://blog.molecular-matters.com/2011/09/05/properly-handling-keyboard-input/
let vkey = match vkey {
winuser::VK_SHIFT => unsafe { winuser::MapVirtualKeyA(
scancode,
winuser::MAPVK_VSC_TO_VK_EX,
) as _ },
winuser::VK_CONTROL => if extended {
winuser::VK_RCONTROL
} else {
winuser::VK_LCONTROL
},
winuser::VK_MENU => if extended {
winuser::VK_RMENU
} else {
winuser::VK_LMENU
},
_ => match scancode {
// This is only triggered when using raw input. Without this check, we get two events whenever VK_PAUSE is
// pressed, the first one having scancode 0x1D but vkey VK_PAUSE...
0x1D if vkey == winuser::VK_PAUSE => return None,
// ...and the second having scancode 0x45 but an unmatched vkey!
0x45 => winuser::VK_PAUSE,
// VK_PAUSE and VK_SCROLL have the same scancode when using modifiers, alongside incorrect vkey values.
0x46 => {
if extended {
scancode = 0x45;
winuser::VK_PAUSE
} else {
winuser::VK_SCROLL
}
},
_ => vkey,
},
};
Some((vkey, scancode))
}
pub fn process_key_params(wparam: WPARAM, lparam: LPARAM) -> Option<(ScanCode, Option<VirtualKeyCode>)> {
let scancode = ((lparam >> 16) & 0xff) as UINT;
let extended = (lparam & 0x01000000) != 0;
handle_extended_keys(wparam as _, scancode, extended)
.map(|(vkey, scancode)| (scancode, vkey_to_winit_vkey(vkey)))
}
// This is needed as windows doesn't properly distinguish
// some virtual key codes for different keyboard layouts
fn map_text_keys(win_virtual_key: i32) -> Option<VirtualKeyCode> {
let char_key = unsafe { winuser::MapVirtualKeyA(win_virtual_key as u32, winuser::MAPVK_VK_TO_CHAR) } & 0x7FFF;
match char::from_u32(char_key) {
Some(';') => Some(VirtualKeyCode::Semicolon),
Some('/') => Some(VirtualKeyCode::Slash),
Some('`') => Some(VirtualKeyCode::Grave),
Some('[') => Some(VirtualKeyCode::LBracket),
Some(']') => Some(VirtualKeyCode::RBracket),
Some('\'') => Some(VirtualKeyCode::Apostrophe),
Some('\\') => Some(VirtualKeyCode::Backslash),
_ => None
}
}

Some files were not shown because too many files have changed in this diff Show More