winit/src/platform_impl/android/mod.rs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1057 lines
34 KiB
Rust
Raw Normal View History

2022-06-08 11:50:26 -07:00
use std::cell::Cell;
use std::collections::VecDeque;
use std::hash::Hash;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
2022-06-08 11:50:26 -07:00
use std::time::{Duration, Instant};
Android: rework keycode handling (#2890) The recent overhaul of the keyboard API broke keyboard input on Android. The recent keyboard changes also broke building against the game-activity backend of android-activity because it was assumed that the backend is based on the NDK input API which isn't the case with with game-activity since it doesn't use the InputQueue API from the NDK. Any alphanumeric keycodes were being mapped to `Unidentified` Keys which meant even crude keyboard input support was broken. We do need to expose `getUnicodeChar` (or the ability to look up characters based on the current character map and modifiers) but for now we should at least map alphanumeric keycodes to `Key::Character` for basic interim support of virtual keyboards. This moves all the keycode mapping into a separate `keycodes.rs` file to reduce clutter. This adds back the mapping from Android key codes to Winit key codes that we had before the keyboard API overhaul. Android activity does expose scan codes but key codes currently seem like the more appropriate mapping to Winit physical key codes. This removes the gnarly, unsafe cfg() guarded digging into 'native-activity' and 'game-activity' specific implementation details. I never intended to expose these details in the public API and really hope to avoid there being a release of Winit that would depend on this. I'm also hoping/considering if I can get away with sealing this without necessarily requiring a semver breaking release of android_activity since this absolutely should never have been possible, and can probably safely assume this was the only code in the wild that has briefly done this. I'm also a bit unclear as to what led to doing this. There is a `.key_code()` and `.scan_code()` getter and we even already accessed the keycode in the Android backend so I'm not sure how those APIs were missed.
2023-06-21 18:49:44 +01:00
use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction};
use android_activity::{
AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect,
};
use tracing::{debug, trace, warn};
2022-06-08 11:50:26 -07:00
use crate::application::ApplicationHandler;
use crate::cursor::Cursor;
2022-06-08 11:50:26 -07:00
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use crate::error;
2022-06-08 11:50:26 -07:00
use crate::error::EventLoopError;
2023-09-22 21:44:39 +02:00
use crate::event::{self, Force, InnerSizeWriter, StartCause};
use crate::event_loop::{self, ControlFlow, DeviceEvents};
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::Fullscreen;
use crate::window::{
self, CursorGrabMode, CustomCursor, CustomCursorSource, ImePurpose, ResizeDirection, Theme,
WindowButtons, WindowLevel,
};
Android: rework keycode handling (#2890) The recent overhaul of the keyboard API broke keyboard input on Android. The recent keyboard changes also broke building against the game-activity backend of android-activity because it was assumed that the backend is based on the NDK input API which isn't the case with with game-activity since it doesn't use the InputQueue API from the NDK. Any alphanumeric keycodes were being mapped to `Unidentified` Keys which meant even crude keyboard input support was broken. We do need to expose `getUnicodeChar` (or the ability to look up characters based on the current character map and modifiers) but for now we should at least map alphanumeric keycodes to `Key::Character` for basic interim support of virtual keyboards. This moves all the keycode mapping into a separate `keycodes.rs` file to reduce clutter. This adds back the mapping from Android key codes to Winit key codes that we had before the keyboard API overhaul. Android activity does expose scan codes but key codes currently seem like the more appropriate mapping to Winit physical key codes. This removes the gnarly, unsafe cfg() guarded digging into 'native-activity' and 'game-activity' specific implementation details. I never intended to expose these details in the public API and really hope to avoid there being a release of Winit that would depend on this. I'm also hoping/considering if I can get away with sealing this without necessarily requiring a semver breaking release of android_activity since this absolutely should never have been possible, and can probably safely assume this was the only code in the wild that has briefly done this. I'm also a bit unclear as to what led to doing this. There is a `.key_code()` and `.scan_code()` getter and we even already accessed the keycode in the Android backend so I'm not sure how those APIs were missed.
2023-06-21 18:49:44 +01:00
mod keycodes;
pub(crate) use crate::cursor::{
NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource,
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;
static HAS_FOCUS: AtomicBool = AtomicBool::new(true);
/// Returns the minimum `Option<Duration>`, taking into account that `None`
/// equates to an infinite timeout, not a zero timeout (so can't just use
/// `Option::min`)
fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
}
#[derive(Clone)]
struct SharedFlagSetter {
flag: Arc<AtomicBool>,
}
impl SharedFlagSetter {
pub fn set(&self) -> bool {
self.flag.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed).is_ok()
}
}
struct SharedFlag {
flag: Arc<AtomicBool>,
}
// Used for queuing redraws from arbitrary threads. We don't care how many
// times a redraw is requested (so don't actually need to queue any data,
// we just need to know at the start of a main loop iteration if a redraw
// was queued and be able to read and clear the state atomically)
impl SharedFlag {
pub fn new() -> Self {
Self { flag: Arc::new(AtomicBool::new(false)) }
}
pub fn setter(&self) -> SharedFlagSetter {
SharedFlagSetter { flag: self.flag.clone() }
}
pub fn get_and_reset(&self) -> bool {
self.flag.swap(false, std::sync::atomic::Ordering::AcqRel)
}
}
#[derive(Clone)]
pub struct RedrawRequester {
flag: SharedFlagSetter,
waker: AndroidAppWaker,
}
impl RedrawRequester {
fn new(flag: &SharedFlag, waker: AndroidAppWaker) -> Self {
RedrawRequester { flag: flag.setter(), waker }
}
pub fn request_redraw(&self) {
if self.flag.set() {
// Only explicitly try to wake up the main loop when the flag
// value changes
self.waker.wake();
}
}
}
2016-10-31 17:08:55 +01:00
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {}
pub struct EventLoop {
android_app: AndroidApp,
window_target: event_loop::ActiveEventLoop,
redraw_flag: SharedFlag,
loop_running: bool, // Dispatched `NewEvents<Init>`
running: bool,
pending_redraw: bool,
cause: StartCause,
ignore_volume_keys: bool,
Android: Support unicode character mapping + dead keys Up until now the Android backend has been directly mapping key codes which essentially just represent the "physical" cap of the key (quoted since this also related to virtual keyboards). Since we didn't account for any meta keys either it meant the backend only supported a 1:1 mapping from key codes, which only covers a tiny subset of characters. For example you couldn't type a colon since there's no keycode for that and we didn't try and map Shift+Semicolon into a colon character. This has been tricky to support because the `NativeActivity` class doesn't have direct access to the Java `KeyEvent` object which exposes a more convenient `getUnicodeChar` API. It is now possible to query a `KeyCharcterMap` for the device associated with a `KeyEvent` via the `AndroidApp::device_key_character_map` API which provides a binding to the SDK `KeyCharacterMap` API in Java: https://developer.android.com/reference/android/view/KeyCharacterMap This is effectively what `getUnicodeChar` is implemented based on and is a bit more general purpose. `KeyCharacterMap` lets us map a key_code + meta_state from a `KeyEvent` into either a unicode character or dead key accent that can be combined with the following key. This mapping is done based on the user's chosen layout for the keyboard. To enable support for key character maps the `AndroidApp::input_events()` API was replaced by `AndroidApp::input_events_iter()` which returns a (lending) iterator for events. This was changed because the previous design made it difficult to allow other AndroidApp APIs to be used while iterating events (mainly because AndroidApp held a lock over the backend during iteration)
2023-08-07 23:56:42 +01:00
combining_accent: Option<char>,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct PlatformSpecificEventLoopAttributes {
pub(crate) android_app: Option<AndroidApp>,
pub(crate) ignore_volume_keys: bool,
}
impl Default for PlatformSpecificEventLoopAttributes {
fn default() -> Self {
Self { android_app: Default::default(), ignore_volume_keys: true }
}
}
impl EventLoop {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
let proxy_wake_up = Arc::new(AtomicBool::new(false));
let android_app = attributes.android_app.as_ref().expect(
"An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on \
Android",
);
let redraw_flag = SharedFlag::new();
Ok(Self {
android_app: android_app.clone(),
window_target: event_loop::ActiveEventLoop {
p: ActiveEventLoop {
app: android_app.clone(),
control_flow: Cell::new(ControlFlow::default()),
exit: Cell::new(false),
redraw_requester: RedrawRequester::new(
&redraw_flag,
android_app.create_waker(),
),
proxy_wake_up,
},
_marker: PhantomData,
},
redraw_flag,
loop_running: false,
running: false,
pending_redraw: false,
cause: StartCause::Init,
ignore_volume_keys: attributes.ignore_volume_keys,
Android: Support unicode character mapping + dead keys Up until now the Android backend has been directly mapping key codes which essentially just represent the "physical" cap of the key (quoted since this also related to virtual keyboards). Since we didn't account for any meta keys either it meant the backend only supported a 1:1 mapping from key codes, which only covers a tiny subset of characters. For example you couldn't type a colon since there's no keycode for that and we didn't try and map Shift+Semicolon into a colon character. This has been tricky to support because the `NativeActivity` class doesn't have direct access to the Java `KeyEvent` object which exposes a more convenient `getUnicodeChar` API. It is now possible to query a `KeyCharcterMap` for the device associated with a `KeyEvent` via the `AndroidApp::device_key_character_map` API which provides a binding to the SDK `KeyCharacterMap` API in Java: https://developer.android.com/reference/android/view/KeyCharacterMap This is effectively what `getUnicodeChar` is implemented based on and is a bit more general purpose. `KeyCharacterMap` lets us map a key_code + meta_state from a `KeyEvent` into either a unicode character or dead key accent that can be combined with the following key. This mapping is done based on the user's chosen layout for the keyboard. To enable support for key character maps the `AndroidApp::input_events()` API was replaced by `AndroidApp::input_events_iter()` which returns a (lending) iterator for events. This was changed because the previous design made it difficult to allow other AndroidApp APIs to be used while iterating events (mainly because AndroidApp held a lock over the backend during iteration)
2023-08-07 23:56:42 +01:00
combining_accent: None,
})
2016-10-31 17:08:55 +01:00
}
fn single_iteration<A: ApplicationHandler>(
&mut self,
main_event: Option<MainEvent<'_>>,
app: &mut A,
) {
trace!("Mainloop iteration");
let cause = self.cause;
let mut pending_redraw = self.pending_redraw;
let mut resized = false;
app.new_events(self.window_target(), cause);
if let Some(event) = main_event {
trace!("Handling main event {:?}", event);
match event {
MainEvent::InitWindow { .. } => {
app.can_create_surfaces(self.window_target());
},
MainEvent::TerminateWindow { .. } => {
app.destroy_surfaces(self.window_target());
},
MainEvent::WindowResized { .. } => resized = true,
MainEvent::RedrawNeeded { .. } => pending_redraw = true,
MainEvent::ContentRectChanged { .. } => {
warn!("TODO: find a way to notify application of content rect change");
},
MainEvent::GainedFocus => {
HAS_FOCUS.store(true, Ordering::Relaxed);
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Focused(true);
app.window_event(self.window_target(), window_id, event);
},
MainEvent::LostFocus => {
HAS_FOCUS.store(false, Ordering::Relaxed);
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Focused(false);
app.window_event(self.window_target(), window_id, event);
},
MainEvent::ConfigChanged { .. } => {
let monitor = MonitorHandle::new(self.android_app.clone());
let old_scale_factor = monitor.scale_factor();
let scale_factor = monitor.scale_factor();
if (scale_factor - old_scale_factor).abs() < f64::EPSILON {
let new_inner_size = Arc::new(Mutex::new(
MonitorHandle::new(self.android_app.clone()).size(),
));
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::ScaleFactorChanged {
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
scale_factor,
};
app.window_event(self.window_target(), window_id, event);
}
},
MainEvent::LowMemory => {
app.memory_warning(self.window_target());
},
MainEvent::Start => {
// XXX: how to forward this state to applications?
warn!("TODO: forward onStart notification to application");
},
MainEvent::Resume { .. } => {
debug!("App Resumed - is running");
self.running = true;
},
MainEvent::SaveState { .. } => {
// XXX: how to forward this state to applications?
// XXX: also how do we expose state restoration to apps?
warn!("TODO: forward saveState notification to application");
},
MainEvent::Pause => {
debug!("App Paused - stopped running");
self.running = false;
},
MainEvent::Stop => {
// XXX: how to forward this state to applications?
warn!("TODO: forward onStop notification to application");
},
MainEvent::Destroy => {
// XXX: maybe exit mainloop to drop things before being
// killed by the OS?
warn!("TODO: forward onDestroy notification to application");
},
MainEvent::InsetsChanged { .. } => {
// XXX: how to forward this state to applications?
warn!("TODO: handle Android InsetsChanged notification");
},
unknown => {
trace!("Unknown MainEvent {unknown:?} (ignored)");
},
}
} else {
trace!("No main event to handle");
}
Android: Support unicode character mapping + dead keys Up until now the Android backend has been directly mapping key codes which essentially just represent the "physical" cap of the key (quoted since this also related to virtual keyboards). Since we didn't account for any meta keys either it meant the backend only supported a 1:1 mapping from key codes, which only covers a tiny subset of characters. For example you couldn't type a colon since there's no keycode for that and we didn't try and map Shift+Semicolon into a colon character. This has been tricky to support because the `NativeActivity` class doesn't have direct access to the Java `KeyEvent` object which exposes a more convenient `getUnicodeChar` API. It is now possible to query a `KeyCharcterMap` for the device associated with a `KeyEvent` via the `AndroidApp::device_key_character_map` API which provides a binding to the SDK `KeyCharacterMap` API in Java: https://developer.android.com/reference/android/view/KeyCharacterMap This is effectively what `getUnicodeChar` is implemented based on and is a bit more general purpose. `KeyCharacterMap` lets us map a key_code + meta_state from a `KeyEvent` into either a unicode character or dead key accent that can be combined with the following key. This mapping is done based on the user's chosen layout for the keyboard. To enable support for key character maps the `AndroidApp::input_events()` API was replaced by `AndroidApp::input_events_iter()` which returns a (lending) iterator for events. This was changed because the previous design made it difficult to allow other AndroidApp APIs to be used while iterating events (mainly because AndroidApp held a lock over the backend during iteration)
2023-08-07 23:56:42 +01:00
// temporarily decouple `android_app` from `self` so we aren't holding
// a borrow of `self` while iterating
let android_app = self.android_app.clone();
Android: Support unicode character mapping + dead keys Up until now the Android backend has been directly mapping key codes which essentially just represent the "physical" cap of the key (quoted since this also related to virtual keyboards). Since we didn't account for any meta keys either it meant the backend only supported a 1:1 mapping from key codes, which only covers a tiny subset of characters. For example you couldn't type a colon since there's no keycode for that and we didn't try and map Shift+Semicolon into a colon character. This has been tricky to support because the `NativeActivity` class doesn't have direct access to the Java `KeyEvent` object which exposes a more convenient `getUnicodeChar` API. It is now possible to query a `KeyCharcterMap` for the device associated with a `KeyEvent` via the `AndroidApp::device_key_character_map` API which provides a binding to the SDK `KeyCharacterMap` API in Java: https://developer.android.com/reference/android/view/KeyCharacterMap This is effectively what `getUnicodeChar` is implemented based on and is a bit more general purpose. `KeyCharacterMap` lets us map a key_code + meta_state from a `KeyEvent` into either a unicode character or dead key accent that can be combined with the following key. This mapping is done based on the user's chosen layout for the keyboard. To enable support for key character maps the `AndroidApp::input_events()` API was replaced by `AndroidApp::input_events_iter()` which returns a (lending) iterator for events. This was changed because the previous design made it difficult to allow other AndroidApp APIs to be used while iterating events (mainly because AndroidApp held a lock over the backend during iteration)
2023-08-07 23:56:42 +01:00
// Process input events
match android_app.input_events_iter() {
Ok(mut input_iter) => loop {
let read_event =
input_iter.next(|event| self.handle_input_event(&android_app, event, app));
Android: Support unicode character mapping + dead keys Up until now the Android backend has been directly mapping key codes which essentially just represent the "physical" cap of the key (quoted since this also related to virtual keyboards). Since we didn't account for any meta keys either it meant the backend only supported a 1:1 mapping from key codes, which only covers a tiny subset of characters. For example you couldn't type a colon since there's no keycode for that and we didn't try and map Shift+Semicolon into a colon character. This has been tricky to support because the `NativeActivity` class doesn't have direct access to the Java `KeyEvent` object which exposes a more convenient `getUnicodeChar` API. It is now possible to query a `KeyCharcterMap` for the device associated with a `KeyEvent` via the `AndroidApp::device_key_character_map` API which provides a binding to the SDK `KeyCharacterMap` API in Java: https://developer.android.com/reference/android/view/KeyCharacterMap This is effectively what `getUnicodeChar` is implemented based on and is a bit more general purpose. `KeyCharacterMap` lets us map a key_code + meta_state from a `KeyEvent` into either a unicode character or dead key accent that can be combined with the following key. This mapping is done based on the user's chosen layout for the keyboard. To enable support for key character maps the `AndroidApp::input_events()` API was replaced by `AndroidApp::input_events_iter()` which returns a (lending) iterator for events. This was changed because the previous design made it difficult to allow other AndroidApp APIs to be used while iterating events (mainly because AndroidApp held a lock over the backend during iteration)
2023-08-07 23:56:42 +01:00
if !read_event {
break;
2019-06-24 12:14:55 -04:00
}
Android: Support unicode character mapping + dead keys Up until now the Android backend has been directly mapping key codes which essentially just represent the "physical" cap of the key (quoted since this also related to virtual keyboards). Since we didn't account for any meta keys either it meant the backend only supported a 1:1 mapping from key codes, which only covers a tiny subset of characters. For example you couldn't type a colon since there's no keycode for that and we didn't try and map Shift+Semicolon into a colon character. This has been tricky to support because the `NativeActivity` class doesn't have direct access to the Java `KeyEvent` object which exposes a more convenient `getUnicodeChar` API. It is now possible to query a `KeyCharcterMap` for the device associated with a `KeyEvent` via the `AndroidApp::device_key_character_map` API which provides a binding to the SDK `KeyCharacterMap` API in Java: https://developer.android.com/reference/android/view/KeyCharacterMap This is effectively what `getUnicodeChar` is implemented based on and is a bit more general purpose. `KeyCharacterMap` lets us map a key_code + meta_state from a `KeyEvent` into either a unicode character or dead key accent that can be combined with the following key. This mapping is done based on the user's chosen layout for the keyboard. To enable support for key character maps the `AndroidApp::input_events()` API was replaced by `AndroidApp::input_events_iter()` which returns a (lending) iterator for events. This was changed because the previous design made it difficult to allow other AndroidApp APIs to be used while iterating events (mainly because AndroidApp held a lock over the backend during iteration)
2023-08-07 23:56:42 +01:00
},
Err(err) => {
tracing::warn!("Failed to get input events iterator: {err:?}");
},
Android: Support unicode character mapping + dead keys Up until now the Android backend has been directly mapping key codes which essentially just represent the "physical" cap of the key (quoted since this also related to virtual keyboards). Since we didn't account for any meta keys either it meant the backend only supported a 1:1 mapping from key codes, which only covers a tiny subset of characters. For example you couldn't type a colon since there's no keycode for that and we didn't try and map Shift+Semicolon into a colon character. This has been tricky to support because the `NativeActivity` class doesn't have direct access to the Java `KeyEvent` object which exposes a more convenient `getUnicodeChar` API. It is now possible to query a `KeyCharcterMap` for the device associated with a `KeyEvent` via the `AndroidApp::device_key_character_map` API which provides a binding to the SDK `KeyCharacterMap` API in Java: https://developer.android.com/reference/android/view/KeyCharacterMap This is effectively what `getUnicodeChar` is implemented based on and is a bit more general purpose. `KeyCharacterMap` lets us map a key_code + meta_state from a `KeyEvent` into either a unicode character or dead key accent that can be combined with the following key. This mapping is done based on the user's chosen layout for the keyboard. To enable support for key character maps the `AndroidApp::input_events()` API was replaced by `AndroidApp::input_events_iter()` which returns a (lending) iterator for events. This was changed because the previous design made it difficult to allow other AndroidApp APIs to be used while iterating events (mainly because AndroidApp held a lock over the backend during iteration)
2023-08-07 23:56:42 +01:00
}
if self.window_target.p.proxy_wake_up.swap(false, Ordering::Relaxed) {
app.proxy_wake_up(self.window_target());
}
if self.running {
if resized {
let size = if let Some(native_window) = self.android_app.native_window().as_ref() {
let width = native_window.width() as _;
let height = native_window.height() as _;
PhysicalSize::new(width, height)
} else {
PhysicalSize::new(0, 0)
};
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Resized(size);
app.window_event(self.window_target(), window_id, event);
}
pending_redraw |= self.redraw_flag.get_and_reset();
if pending_redraw {
pending_redraw = false;
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::RedrawRequested;
app.window_event(self.window_target(), window_id, event);
}
}
Remove RedrawEventsCleared + MainEventsCleared, and added AboutToWait The idea that redraw events are dispatched with a specific ordering that makes it possible to specifically report when we have finished dispatching redraw events isn't portable and the way in which we dispatched RedrawEventsCleared was inconsistent across backends. More generally speaking, there is no inherent relationship between redrawing and event loop iterations. An event loop may wake up at any frequency depending on what sources of input events are being listened to but redrawing is generally throttled and in some way synchronized with the display frequency. Similarly there's no inherent relationship between a single event loop iteration and the dispatching of any specific kind of "main" event. An event loop wakes up when there are events to read (e.g. input events or responses from a display server / compositor) and goes back to waiting when there's nothing else to read. There isn't really a special kind of "main" event that is dispatched in order with respect to other events. What we can do more portably is emit an event when the event loop is about to block and wait for new events. In practice this is very similar to how MainEventsCleared was implemented except it wasn't the very last event previously since redraw events could be dispatched afterwards. The main backend where we don't strictly know when we're going to wait for events is Web (since the real event loop is internal to the browser). For now we emulate AboutToWait on Web similar to how MainEventsCleared was dispatched. In practice most applications almost certainly shouldn't care about AboutToWait because the frequency of event loop iterations is essentially arbitrary and usually irrelevant.
2023-07-28 17:37:56 +01:00
// This is always the last event we dispatch before poll again
app.about_to_wait(self.window_target());
self.pending_redraw = pending_redraw;
}
fn handle_input_event<A: ApplicationHandler>(
Android: Support unicode character mapping + dead keys Up until now the Android backend has been directly mapping key codes which essentially just represent the "physical" cap of the key (quoted since this also related to virtual keyboards). Since we didn't account for any meta keys either it meant the backend only supported a 1:1 mapping from key codes, which only covers a tiny subset of characters. For example you couldn't type a colon since there's no keycode for that and we didn't try and map Shift+Semicolon into a colon character. This has been tricky to support because the `NativeActivity` class doesn't have direct access to the Java `KeyEvent` object which exposes a more convenient `getUnicodeChar` API. It is now possible to query a `KeyCharcterMap` for the device associated with a `KeyEvent` via the `AndroidApp::device_key_character_map` API which provides a binding to the SDK `KeyCharacterMap` API in Java: https://developer.android.com/reference/android/view/KeyCharacterMap This is effectively what `getUnicodeChar` is implemented based on and is a bit more general purpose. `KeyCharacterMap` lets us map a key_code + meta_state from a `KeyEvent` into either a unicode character or dead key accent that can be combined with the following key. This mapping is done based on the user's chosen layout for the keyboard. To enable support for key character maps the `AndroidApp::input_events()` API was replaced by `AndroidApp::input_events_iter()` which returns a (lending) iterator for events. This was changed because the previous design made it difficult to allow other AndroidApp APIs to be used while iterating events (mainly because AndroidApp held a lock over the backend during iteration)
2023-08-07 23:56:42 +01:00
&mut self,
android_app: &AndroidApp,
event: &InputEvent<'_>,
app: &mut A,
) -> InputStatus {
Android: Support unicode character mapping + dead keys Up until now the Android backend has been directly mapping key codes which essentially just represent the "physical" cap of the key (quoted since this also related to virtual keyboards). Since we didn't account for any meta keys either it meant the backend only supported a 1:1 mapping from key codes, which only covers a tiny subset of characters. For example you couldn't type a colon since there's no keycode for that and we didn't try and map Shift+Semicolon into a colon character. This has been tricky to support because the `NativeActivity` class doesn't have direct access to the Java `KeyEvent` object which exposes a more convenient `getUnicodeChar` API. It is now possible to query a `KeyCharcterMap` for the device associated with a `KeyEvent` via the `AndroidApp::device_key_character_map` API which provides a binding to the SDK `KeyCharacterMap` API in Java: https://developer.android.com/reference/android/view/KeyCharacterMap This is effectively what `getUnicodeChar` is implemented based on and is a bit more general purpose. `KeyCharacterMap` lets us map a key_code + meta_state from a `KeyEvent` into either a unicode character or dead key accent that can be combined with the following key. This mapping is done based on the user's chosen layout for the keyboard. To enable support for key character maps the `AndroidApp::input_events()` API was replaced by `AndroidApp::input_events_iter()` which returns a (lending) iterator for events. This was changed because the previous design made it difficult to allow other AndroidApp APIs to be used while iterating events (mainly because AndroidApp held a lock over the backend during iteration)
2023-08-07 23:56:42 +01:00
let mut input_status = InputStatus::Handled;
match event {
InputEvent::MotionEvent(motion_event) => {
let window_id = window::WindowId(WindowId);
let device_id = event::DeviceId(DeviceId(motion_event.device_id()));
Android: Support unicode character mapping + dead keys Up until now the Android backend has been directly mapping key codes which essentially just represent the "physical" cap of the key (quoted since this also related to virtual keyboards). Since we didn't account for any meta keys either it meant the backend only supported a 1:1 mapping from key codes, which only covers a tiny subset of characters. For example you couldn't type a colon since there's no keycode for that and we didn't try and map Shift+Semicolon into a colon character. This has been tricky to support because the `NativeActivity` class doesn't have direct access to the Java `KeyEvent` object which exposes a more convenient `getUnicodeChar` API. It is now possible to query a `KeyCharcterMap` for the device associated with a `KeyEvent` via the `AndroidApp::device_key_character_map` API which provides a binding to the SDK `KeyCharacterMap` API in Java: https://developer.android.com/reference/android/view/KeyCharacterMap This is effectively what `getUnicodeChar` is implemented based on and is a bit more general purpose. `KeyCharacterMap` lets us map a key_code + meta_state from a `KeyEvent` into either a unicode character or dead key accent that can be combined with the following key. This mapping is done based on the user's chosen layout for the keyboard. To enable support for key character maps the `AndroidApp::input_events()` API was replaced by `AndroidApp::input_events_iter()` which returns a (lending) iterator for events. This was changed because the previous design made it difficult to allow other AndroidApp APIs to be used while iterating events (mainly because AndroidApp held a lock over the backend during iteration)
2023-08-07 23:56:42 +01:00
let phase = match motion_event.action() {
MotionAction::Down | MotionAction::PointerDown => {
Some(event::TouchPhase::Started)
},
MotionAction::Up | MotionAction::PointerUp => Some(event::TouchPhase::Ended),
MotionAction::Move => Some(event::TouchPhase::Moved),
MotionAction::Cancel => Some(event::TouchPhase::Cancelled),
_ => {
None // TODO mouse events
},
};
if let Some(phase) = phase {
let pointers: Box<dyn Iterator<Item = android_activity::input::Pointer<'_>>> =
match phase {
event::TouchPhase::Started | event::TouchPhase::Ended => {
Box::new(std::iter::once(
motion_event.pointer_at_index(motion_event.pointer_index()),
))
},
event::TouchPhase::Moved | event::TouchPhase::Cancelled => {
Box::new(motion_event.pointers())
},
};
for pointer in pointers {
let location =
PhysicalPosition { x: pointer.x() as _, y: pointer.y() as _ };
trace!(
"Input event {device_id:?}, {phase:?}, loc={location:?}, \
pointer={pointer:?}"
);
let event = event::WindowEvent::Touch(event::Touch {
device_id,
phase,
location,
id: pointer.pointer_id() as u64,
force: Some(Force::Normalized(pointer.pressure() as f64)),
});
app.window_event(self.window_target(), window_id, event);
Android: Support unicode character mapping + dead keys Up until now the Android backend has been directly mapping key codes which essentially just represent the "physical" cap of the key (quoted since this also related to virtual keyboards). Since we didn't account for any meta keys either it meant the backend only supported a 1:1 mapping from key codes, which only covers a tiny subset of characters. For example you couldn't type a colon since there's no keycode for that and we didn't try and map Shift+Semicolon into a colon character. This has been tricky to support because the `NativeActivity` class doesn't have direct access to the Java `KeyEvent` object which exposes a more convenient `getUnicodeChar` API. It is now possible to query a `KeyCharcterMap` for the device associated with a `KeyEvent` via the `AndroidApp::device_key_character_map` API which provides a binding to the SDK `KeyCharacterMap` API in Java: https://developer.android.com/reference/android/view/KeyCharacterMap This is effectively what `getUnicodeChar` is implemented based on and is a bit more general purpose. `KeyCharacterMap` lets us map a key_code + meta_state from a `KeyEvent` into either a unicode character or dead key accent that can be combined with the following key. This mapping is done based on the user's chosen layout for the keyboard. To enable support for key character maps the `AndroidApp::input_events()` API was replaced by `AndroidApp::input_events_iter()` which returns a (lending) iterator for events. This was changed because the previous design made it difficult to allow other AndroidApp APIs to be used while iterating events (mainly because AndroidApp held a lock over the backend during iteration)
2023-08-07 23:56:42 +01:00
}
}
},
InputEvent::KeyEvent(key) => {
match key.key_code() {
// Flag keys related to volume as unhandled. While winit does not have a way for
// applications to configure what keys to flag as handled,
// this appears to be a good default until winit
// can be configured.
Keycode::VolumeUp | Keycode::VolumeDown | Keycode::VolumeMute
if self.ignore_volume_keys =>
{
input_status = InputStatus::Unhandled
Android: Support unicode character mapping + dead keys Up until now the Android backend has been directly mapping key codes which essentially just represent the "physical" cap of the key (quoted since this also related to virtual keyboards). Since we didn't account for any meta keys either it meant the backend only supported a 1:1 mapping from key codes, which only covers a tiny subset of characters. For example you couldn't type a colon since there's no keycode for that and we didn't try and map Shift+Semicolon into a colon character. This has been tricky to support because the `NativeActivity` class doesn't have direct access to the Java `KeyEvent` object which exposes a more convenient `getUnicodeChar` API. It is now possible to query a `KeyCharcterMap` for the device associated with a `KeyEvent` via the `AndroidApp::device_key_character_map` API which provides a binding to the SDK `KeyCharacterMap` API in Java: https://developer.android.com/reference/android/view/KeyCharacterMap This is effectively what `getUnicodeChar` is implemented based on and is a bit more general purpose. `KeyCharacterMap` lets us map a key_code + meta_state from a `KeyEvent` into either a unicode character or dead key accent that can be combined with the following key. This mapping is done based on the user's chosen layout for the keyboard. To enable support for key character maps the `AndroidApp::input_events()` API was replaced by `AndroidApp::input_events_iter()` which returns a (lending) iterator for events. This was changed because the previous design made it difficult to allow other AndroidApp APIs to be used while iterating events (mainly because AndroidApp held a lock over the backend during iteration)
2023-08-07 23:56:42 +01:00
},
keycode => {
let state = match key.action() {
KeyAction::Down => event::ElementState::Pressed,
KeyAction::Up => event::ElementState::Released,
_ => event::ElementState::Released,
};
let key_char = keycodes::character_map_and_combine_key(
android_app,
key,
&mut self.combining_accent,
);
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId(key.device_id())),
event: event::KeyEvent {
state,
physical_key: keycodes::to_physical_key(keycode),
logical_key: keycodes::to_logical(key_char, keycode),
location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0,
text: None,
platform_specific: KeyEventExtra {},
Android: Support unicode character mapping + dead keys Up until now the Android backend has been directly mapping key codes which essentially just represent the "physical" cap of the key (quoted since this also related to virtual keyboards). Since we didn't account for any meta keys either it meant the backend only supported a 1:1 mapping from key codes, which only covers a tiny subset of characters. For example you couldn't type a colon since there's no keycode for that and we didn't try and map Shift+Semicolon into a colon character. This has been tricky to support because the `NativeActivity` class doesn't have direct access to the Java `KeyEvent` object which exposes a more convenient `getUnicodeChar` API. It is now possible to query a `KeyCharcterMap` for the device associated with a `KeyEvent` via the `AndroidApp::device_key_character_map` API which provides a binding to the SDK `KeyCharacterMap` API in Java: https://developer.android.com/reference/android/view/KeyCharacterMap This is effectively what `getUnicodeChar` is implemented based on and is a bit more general purpose. `KeyCharacterMap` lets us map a key_code + meta_state from a `KeyEvent` into either a unicode character or dead key accent that can be combined with the following key. This mapping is done based on the user's chosen layout for the keyboard. To enable support for key character maps the `AndroidApp::input_events()` API was replaced by `AndroidApp::input_events_iter()` which returns a (lending) iterator for events. This was changed because the previous design made it difficult to allow other AndroidApp APIs to be used while iterating events (mainly because AndroidApp held a lock over the backend during iteration)
2023-08-07 23:56:42 +01:00
},
is_synthetic: false,
Android: Support unicode character mapping + dead keys Up until now the Android backend has been directly mapping key codes which essentially just represent the "physical" cap of the key (quoted since this also related to virtual keyboards). Since we didn't account for any meta keys either it meant the backend only supported a 1:1 mapping from key codes, which only covers a tiny subset of characters. For example you couldn't type a colon since there's no keycode for that and we didn't try and map Shift+Semicolon into a colon character. This has been tricky to support because the `NativeActivity` class doesn't have direct access to the Java `KeyEvent` object which exposes a more convenient `getUnicodeChar` API. It is now possible to query a `KeyCharcterMap` for the device associated with a `KeyEvent` via the `AndroidApp::device_key_character_map` API which provides a binding to the SDK `KeyCharacterMap` API in Java: https://developer.android.com/reference/android/view/KeyCharacterMap This is effectively what `getUnicodeChar` is implemented based on and is a bit more general purpose. `KeyCharacterMap` lets us map a key_code + meta_state from a `KeyEvent` into either a unicode character or dead key accent that can be combined with the following key. This mapping is done based on the user's chosen layout for the keyboard. To enable support for key character maps the `AndroidApp::input_events()` API was replaced by `AndroidApp::input_events_iter()` which returns a (lending) iterator for events. This was changed because the previous design made it difficult to allow other AndroidApp APIs to be used while iterating events (mainly because AndroidApp held a lock over the backend during iteration)
2023-08-07 23:56:42 +01:00
};
app.window_event(self.window_target(), window_id, event);
Android: Support unicode character mapping + dead keys Up until now the Android backend has been directly mapping key codes which essentially just represent the "physical" cap of the key (quoted since this also related to virtual keyboards). Since we didn't account for any meta keys either it meant the backend only supported a 1:1 mapping from key codes, which only covers a tiny subset of characters. For example you couldn't type a colon since there's no keycode for that and we didn't try and map Shift+Semicolon into a colon character. This has been tricky to support because the `NativeActivity` class doesn't have direct access to the Java `KeyEvent` object which exposes a more convenient `getUnicodeChar` API. It is now possible to query a `KeyCharcterMap` for the device associated with a `KeyEvent` via the `AndroidApp::device_key_character_map` API which provides a binding to the SDK `KeyCharacterMap` API in Java: https://developer.android.com/reference/android/view/KeyCharacterMap This is effectively what `getUnicodeChar` is implemented based on and is a bit more general purpose. `KeyCharacterMap` lets us map a key_code + meta_state from a `KeyEvent` into either a unicode character or dead key accent that can be combined with the following key. This mapping is done based on the user's chosen layout for the keyboard. To enable support for key character maps the `AndroidApp::input_events()` API was replaced by `AndroidApp::input_events_iter()` which returns a (lending) iterator for events. This was changed because the previous design made it difficult to allow other AndroidApp APIs to be used while iterating events (mainly because AndroidApp held a lock over the backend during iteration)
2023-08-07 23:56:42 +01:00
},
}
},
_ => {
warn!("Unknown android_activity input event {event:?}")
},
}
input_status
}
pub fn run_app<A: ApplicationHandler>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
loop {
match self.pump_app_events(None, app) {
PumpStatus::Exit(0) => {
break Ok(());
},
PumpStatus::Exit(code) => {
break Err(EventLoopError::ExitFailure(code));
},
_ => {
continue;
},
}
}
}
pub fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
if !self.loop_running {
self.loop_running = true;
// Reset the internal state for the loop as we start running to
// ensure consistent behaviour in case the loop runs and exits more
// than once
self.pending_redraw = false;
self.cause = StartCause::Init;
// run the initial loop iteration
self.single_iteration(None, app);
}
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit
if !self.exiting() {
self.poll_events_with_timeout(timeout, app);
}
if self.exiting() {
self.loop_running = false;
app.exiting(self.window_target());
PumpStatus::Exit(0)
} else {
PumpStatus::Continue
}
}
fn poll_events_with_timeout<A: ApplicationHandler>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
) {
let start = Instant::now();
self.pending_redraw |= self.redraw_flag.get_and_reset();
timeout = if self.running
&& (self.pending_redraw || self.window_target.p.proxy_wake_up.load(Ordering::Relaxed))
{
// If we already have work to do then we don't want to block on the next poll
Some(Duration::ZERO)
} else {
let control_flow_timeout = match self.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
Some(wait_deadline.saturating_duration_since(start))
},
};
min_timeout(control_flow_timeout, timeout)
};
let android_app = self.android_app.clone(); // Don't borrow self as part of poll expression
android_app.poll_events(timeout, |poll_event| {
let mut main_event = None;
match poll_event {
android_activity::PollEvent::Wake => {
// In the X11 backend it's noted that too many false-positive wake ups
// would cause the event loop to run continuously. They handle this by
// re-checking for pending events (assuming they cover all
// valid reasons for a wake up).
//
// For now, user_events and redraw_requests are the only reasons to expect
// a wake up here so we can ignore the wake up if there are no events/requests.
// We also ignore wake ups while suspended.
self.pending_redraw |= self.redraw_flag.get_and_reset();
if !self.running
|| (!self.pending_redraw
&& !self.window_target.p.proxy_wake_up.load(Ordering::Relaxed))
{
return;
}
2019-06-24 12:14:55 -04:00
},
android_activity::PollEvent::Timeout => {},
android_activity::PollEvent::Main(event) => {
main_event = Some(event);
2019-06-24 12:14:55 -04:00
},
unknown_event => {
warn!("Unknown poll event {unknown_event:?} (ignored)");
},
}
self.cause = match self.control_flow() {
ControlFlow::Poll => StartCause::Poll,
ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None },
ControlFlow::WaitUntil(deadline) => {
if Instant::now() < deadline {
StartCause::WaitCancelled { start, requested_resume: Some(deadline) }
} else {
StartCause::ResumeTimeReached { start, requested_resume: deadline }
}
},
};
self.single_iteration(main_event, app);
});
}
pub fn window_target(&self) -> &event_loop::ActiveEventLoop {
&self.window_target
}
fn control_flow(&self) -> ControlFlow {
self.window_target.p.control_flow()
}
fn exiting(&self) -> bool {
self.window_target.p.exiting()
}
}
#[derive(Clone)]
pub struct EventLoopProxy {
proxy_wake_up: Arc<AtomicBool>,
waker: AndroidAppWaker,
}
impl EventLoopProxy {
pub fn wake_up(&self) {
self.proxy_wake_up.store(true, Ordering::Relaxed);
self.waker.wake();
}
2016-10-31 17:08:55 +01:00
}
pub struct ActiveEventLoop {
app: AndroidApp,
control_flow: Cell<ControlFlow>,
exit: Cell<bool>,
redraw_requester: RedrawRequester,
proxy_wake_up: Arc<AtomicBool>,
}
impl ActiveEventLoop {
pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy { proxy_wake_up: self.proxy_wake_up.clone(), waker: self.app.create_waker() }
}
2022-09-21 10:04:28 +02:00
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle::new(self.app.clone()))
}
pub fn create_custom_cursor(&self, source: CustomCursorSource) -> CustomCursor {
let _ = source.inner;
CustomCursor { inner: PlatformCustomCursor }
}
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
let mut v = VecDeque::with_capacity(1);
v.push_back(MonitorHandle::new(self.app.clone()));
v
}
#[inline]
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::empty())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::Android(rwh_06::AndroidDisplayHandle::new()))
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.control_flow.set(control_flow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow.get()
}
pub(crate) fn exit(&self) {
self.exit.set(true)
}
pub(crate) fn clear_exit(&self) {
self.exit.set(false)
}
pub(crate) fn exiting(&self) -> bool {
self.exit.get()
}
pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle
}
}
#[derive(Clone)]
pub(crate) struct OwnedDisplayHandle;
impl OwnedDisplayHandle {
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::AndroidDisplayHandle::empty().into()
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::AndroidDisplayHandle::new().into())
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(crate) struct WindowId;
impl WindowId {
pub const fn dummy() -> Self {
WindowId
}
}
impl From<WindowId> for u64 {
fn from(_: WindowId) -> Self {
0
}
}
impl From<u64> for WindowId {
fn from(_: u64) -> Self {
Self
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct DeviceId(i32);
impl DeviceId {
pub const fn dummy() -> Self {
DeviceId(0)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PlatformSpecificWindowAttributes;
pub(crate) struct Window {
app: AndroidApp,
redraw_requester: RedrawRequester,
}
2018-06-14 19:42:18 -04:00
impl Window {
pub(crate) fn new(
el: &ActiveEventLoop,
_window_attrs: window::WindowAttributes,
) -> Result<Self, error::OsError> {
// FIXME this ignores requested window attributes
Ok(Self { app: el.app.clone(), redraw_requester: el.redraw_requester.clone() })
}
2018-06-14 19:42:18 -04:00
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) {
f(self)
}
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Self) -> R + Send) -> R {
f(self)
}
pub fn id(&self) -> WindowId {
WindowId
2018-06-14 19:42:18 -04:00
}
2016-10-31 17:08:55 +01:00
2022-09-21 10:04:28 +02:00
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle::new(self.app.clone()))
}
2016-10-31 17:08:55 +01:00
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
let mut v = VecDeque::with_capacity(1);
v.push_back(MonitorHandle::new(self.app.clone()));
v
2016-10-31 17:08:55 +01:00
}
Move fullscreen modes to not touch physical resolutions (#270) * Fix X11 screen resolution change using XrandR The previous XF86 resolution switching was broken and everything seems to have moved on to xrandr. Use that instead while cleaning up the code a bit as well. * Use XRandR for actual multiscreen support in X11 * Use actual monitor names in X11 * Get rid of ptr::read usage in X11 * Use a bog standard Vec instead of VecDeque * Get rid of the XRandR mode switching stuff Wayland has made the decision that apps shouldn't change screen resolutions and just take the screens as they've been setup. In the modern world where GPU scaling is cheap and LCD panels are scaling anyway it makes no sense to make "physical" resolution changes when software should be taking care of it. This massively simplifies the code and makes it easier to extend to more niche setups like MST and videowalls. * Rename fullscreen options to match new semantics * Implement XRandR 1.5 support * Get rid of the FullScreen enum Moving to just having two states None and Some(MonitorId) and then being able to set full screen in the current monitor with something like: window.set_fullscreen(Some(window.current_monitor())); * Implement Window::get_current_monitor() Do it by iterating over the available monitors and finding which has the biggest overlap with the window. For this MonitorId needs a new get_position() that needs to be implemented for all platforms. * Add unimplemented get_position() to all MonitorId * Make get_current_monitor() platform specific * Add unimplemented get_current_monitor() to all * Implement proper primary monitor selection in X11 * Shut up some warnings * Remove libxxf86vm package from travis Since we're no longer using XF86 there's no need to keep the package around for CI. * Don't use new struct syntax * Fix indentation * Adjust Android/iOS fullscreen/maximized On Android and iOS we can assume single screen apps that are already fullscreen and maximized so there are a few methods that are implemented by just returning a fixed value or not doing anything. * Mark OSX/Win fullscreen/maximized unimplemented()! These would be safe as no-ops but we should make it explicit so there is more of an incentive to actually implement them.
2017-09-07 09:33:46 +01:00
2022-09-21 10:04:28 +02:00
pub fn current_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle::new(self.app.clone()))
Move fullscreen modes to not touch physical resolutions (#270) * Fix X11 screen resolution change using XrandR The previous XF86 resolution switching was broken and everything seems to have moved on to xrandr. Use that instead while cleaning up the code a bit as well. * Use XRandR for actual multiscreen support in X11 * Use actual monitor names in X11 * Get rid of ptr::read usage in X11 * Use a bog standard Vec instead of VecDeque * Get rid of the XRandR mode switching stuff Wayland has made the decision that apps shouldn't change screen resolutions and just take the screens as they've been setup. In the modern world where GPU scaling is cheap and LCD panels are scaling anyway it makes no sense to make "physical" resolution changes when software should be taking care of it. This massively simplifies the code and makes it easier to extend to more niche setups like MST and videowalls. * Rename fullscreen options to match new semantics * Implement XRandR 1.5 support * Get rid of the FullScreen enum Moving to just having two states None and Some(MonitorId) and then being able to set full screen in the current monitor with something like: window.set_fullscreen(Some(window.current_monitor())); * Implement Window::get_current_monitor() Do it by iterating over the available monitors and finding which has the biggest overlap with the window. For this MonitorId needs a new get_position() that needs to be implemented for all platforms. * Add unimplemented get_position() to all MonitorId * Make get_current_monitor() platform specific * Add unimplemented get_current_monitor() to all * Implement proper primary monitor selection in X11 * Shut up some warnings * Remove libxxf86vm package from travis Since we're no longer using XF86 there's no need to keep the package around for CI. * Don't use new struct syntax * Fix indentation * Adjust Android/iOS fullscreen/maximized On Android and iOS we can assume single screen apps that are already fullscreen and maximized so there are a few methods that are implemented by just returning a fixed value or not doing anything. * Mark OSX/Win fullscreen/maximized unimplemented()! These would be safe as no-ops but we should make it explicit so there is more of an incentive to actually implement them.
2017-09-07 09:33:46 +01:00
}
pub fn scale_factor(&self) -> f64 {
MonitorHandle::new(self.app.clone()).scale_factor()
}
2016-10-31 17:08:55 +01:00
pub fn request_redraw(&self) {
self.redraw_requester.request_redraw()
2016-10-31 17:08:55 +01:00
}
pub fn pre_present_notify(&self) {}
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
Err(error::NotSupportedError::new())
2016-10-31 17:08:55 +01:00
}
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
Err(error::NotSupportedError::new())
2016-10-31 17:08:55 +01:00
}
pub fn set_outer_position(&self, _position: Position) {
// no effect
2016-10-31 17:08:55 +01:00
}
pub fn inner_size(&self) -> PhysicalSize<u32> {
self.outer_size()
2016-10-31 17:08:55 +01:00
}
pub fn request_inner_size(&self, _size: Size) -> Option<PhysicalSize<u32>> {
Some(self.inner_size())
2016-10-31 17:08:55 +01:00
}
pub fn outer_size(&self) -> PhysicalSize<u32> {
MonitorHandle::new(self.app.clone()).size()
}
pub fn set_min_inner_size(&self, _: Option<Size>) {}
2016-10-31 17:08:55 +01:00
pub fn set_max_inner_size(&self, _: Option<Size>) {}
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
None
}
pub fn set_resize_increments(&self, _increments: Option<Size>) {}
pub fn set_title(&self, _title: &str) {}
pub fn set_transparent(&self, _transparent: bool) {}
pub fn set_blur(&self, _blur: bool) {}
pub fn set_visible(&self, _visibility: bool) {}
2018-06-14 19:42:18 -04:00
pub fn is_visible(&self) -> Option<bool> {
None
}
pub fn set_resizable(&self, _resizeable: bool) {}
2016-10-31 17:08:55 +01:00
pub fn is_resizable(&self) -> bool {
false
}
pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {}
pub fn enabled_buttons(&self) -> WindowButtons {
WindowButtons::all()
}
pub fn set_minimized(&self, _minimized: bool) {}
2016-10-31 17:08:55 +01:00
pub fn is_minimized(&self) -> Option<bool> {
None
}
pub fn set_maximized(&self, _maximized: bool) {}
2016-10-31 17:08:55 +01:00
pub fn is_maximized(&self) -> bool {
false
}
2022-09-21 10:04:28 +02:00
pub fn set_fullscreen(&self, _monitor: Option<Fullscreen>) {
warn!("Cannot set fullscreen on Android");
2016-10-31 17:08:55 +01:00
}
2022-09-21 10:04:28 +02:00
pub fn fullscreen(&self) -> Option<Fullscreen> {
None
2016-10-31 17:08:55 +01:00
}
pub fn set_decorations(&self, _decorations: bool) {}
pub fn is_decorated(&self) -> bool {
true
}
pub fn set_window_level(&self, _level: WindowLevel) {}
2016-10-31 17:08:55 +01:00
pub fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {}
pub fn set_ime_allowed(&self, _allowed: bool) {}
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {}
2021-05-19 18:39:53 +02:00
pub fn focus_window(&self) {}
pub fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
pub fn set_cursor(&self, _: Cursor) {}
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
Move fullscreen modes to not touch physical resolutions (#270) * Fix X11 screen resolution change using XrandR The previous XF86 resolution switching was broken and everything seems to have moved on to xrandr. Use that instead while cleaning up the code a bit as well. * Use XRandR for actual multiscreen support in X11 * Use actual monitor names in X11 * Get rid of ptr::read usage in X11 * Use a bog standard Vec instead of VecDeque * Get rid of the XRandR mode switching stuff Wayland has made the decision that apps shouldn't change screen resolutions and just take the screens as they've been setup. In the modern world where GPU scaling is cheap and LCD panels are scaling anyway it makes no sense to make "physical" resolution changes when software should be taking care of it. This massively simplifies the code and makes it easier to extend to more niche setups like MST and videowalls. * Rename fullscreen options to match new semantics * Implement XRandR 1.5 support * Get rid of the FullScreen enum Moving to just having two states None and Some(MonitorId) and then being able to set full screen in the current monitor with something like: window.set_fullscreen(Some(window.current_monitor())); * Implement Window::get_current_monitor() Do it by iterating over the available monitors and finding which has the biggest overlap with the window. For this MonitorId needs a new get_position() that needs to be implemented for all platforms. * Add unimplemented get_position() to all MonitorId * Make get_current_monitor() platform specific * Add unimplemented get_current_monitor() to all * Implement proper primary monitor selection in X11 * Shut up some warnings * Remove libxxf86vm package from travis Since we're no longer using XF86 there's no need to keep the package around for CI. * Don't use new struct syntax * Fix indentation * Adjust Android/iOS fullscreen/maximized On Android and iOS we can assume single screen apps that are already fullscreen and maximized so there are a few methods that are implemented by just returning a fixed value or not doing anything. * Mark OSX/Win fullscreen/maximized unimplemented()! These would be safe as no-ops but we should make it explicit so there is more of an incentive to actually implement them.
2017-09-07 09:33:46 +01:00
}
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
}
pub fn set_cursor_visible(&self, _: bool) {}
pub fn drag_window(&self) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
}
pub fn drag_resize_window(
&self,
_direction: ResizeDirection,
) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
}
#[inline]
pub fn show_window_menu(&self, _position: Position) {}
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
}
#[cfg(feature = "rwh_04")]
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
use rwh_04::HasRawWindowHandle;
if let Some(native_window) = self.app.native_window().as_ref() {
native_window.raw_window_handle()
} else {
panic!(
"Cannot get the native window, it's null and will always be null before \
Event::Resumed and after Event::Suspended. Make sure you only call this function \
between those events."
);
}
}
#[cfg(feature = "rwh_05")]
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
use rwh_05::HasRawWindowHandle;
if let Some(native_window) = self.app.native_window().as_ref() {
native_window.raw_window_handle()
} else {
panic!(
"Cannot get the native window, it's null and will always be null before \
Event::Resumed and after Event::Suspended. Make sure you only call this function \
between those events."
);
}
}
#[cfg(feature = "rwh_05")]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::empty())
}
#[cfg(feature = "rwh_06")]
// Allow the usage of HasRawWindowHandle inside this function
#[allow(deprecated)]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
use rwh_06::HasRawWindowHandle;
if let Some(native_window) = self.app.native_window().as_ref() {
native_window.raw_window_handle()
} else {
tracing::error!(
"Cannot get the native window, it's null and will always be null before \
Event::Resumed and after Event::Suspended. Make sure you only call this function \
between those events."
);
Err(rwh_06::HandleError::Unavailable)
}
}
#[cfg(feature = "rwh_06")]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::Android(rwh_06::AndroidDisplayHandle::new()))
}
pub fn config(&self) -> ConfigurationRef {
self.app.config()
}
pub fn content_rect(&self) -> Rect {
self.app.content_rect()
}
pub fn set_theme(&self, _theme: Option<Theme>) {}
pub fn theme(&self) -> Option<Theme> {
None
}
pub fn set_content_protected(&self, _protected: bool) {}
pub fn has_focus(&self) -> bool {
HAS_FOCUS.load(Ordering::Relaxed)
}
pub fn title(&self) -> String {
String::new()
}
pub fn reset_dead_keys(&self) {}
}
#[derive(Default, Clone, Debug)]
pub struct OsError;
use std::fmt::{self, Display, Formatter};
impl Display for OsError {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(fmt, "Android OS Error")
2018-05-07 17:36:21 -04:00
}
}
2018-05-07 17:36:21 -04:00
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct MonitorHandle {
app: AndroidApp,
}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MonitorHandle {
fn cmp(&self, _other: &Self) -> std::cmp::Ordering {
std::cmp::Ordering::Equal
}
}
impl MonitorHandle {
pub(crate) fn new(app: AndroidApp) -> Self {
Self { app }
}
pub fn name(&self) -> Option<String> {
Some("Android Device".to_owned())
}
pub fn size(&self) -> PhysicalSize<u32> {
if let Some(native_window) = self.app.native_window() {
PhysicalSize::new(native_window.width() as _, native_window.height() as _)
} else {
PhysicalSize::new(0, 0)
}
}
2016-10-31 17:08:55 +01:00
pub fn position(&self) -> PhysicalPosition<i32> {
(0, 0).into()
}
pub fn scale_factor(&self) -> f64 {
self.app.config().density().map(|dpi| dpi as f64 / 160.0).unwrap_or(1.0)
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
2022-07-15 14:02:12 -02:30
// FIXME no way to get real refresh rate for now.
None
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
let size = self.size().into();
// FIXME this is not the real refresh rate
2022-06-10 13:43:33 +03:00
// (it is guaranteed to support 32 bit color though)
std::iter::once(VideoModeHandle {
2022-09-21 10:04:28 +02:00
size,
bit_depth: 32,
refresh_rate_millihertz: 60000,
monitor: self.clone(),
2022-06-10 13:43:33 +03:00
})
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct VideoModeHandle {
size: (u32, u32),
bit_depth: u16,
refresh_rate_millihertz: u32,
monitor: MonitorHandle,
}
impl VideoModeHandle {
pub fn size(&self) -> PhysicalSize<u32> {
self.size.into()
2016-10-31 17:08:55 +01:00
}
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
2017-07-06 23:33:42 +02:00
pub fn refresh_rate_millihertz(&self) -> u32 {
self.refresh_rate_millihertz
}
2022-09-21 10:04:28 +02:00
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone()
}
}