Event Loop 2.0 API and Windows implementation (#638)
* Rename EventsLoop and associated types to EventLoop * Rename WindowEvent::Refresh to WindowEvent::Redraw * Remove second thread from win32 backend * Update run_forever to hijack thread * Replace windows Mutex with parking_lot Mutex * Implement new ControlFlow and associated events * Add StartCause::Init support, timer example * Add ability to send custom user events * Fully invert windows control flow so win32 calls into winit's callback * Add request_redraw * Rename platform to platform_impl * Rename os to platform, add Ext trait postfixes * Add platform::desktop module with EventLoopExt::run_return * Re-organize into module structure * Improve documentation * Small changes to examples * Improve docs for run and run_return * Change instances of "events_loop" to "event_loop" * Rename MonitorId to MonitorHandle * Add CHANGELOG entry * Improve WaitUntil timer precision * When SendEvent is called during event closure, buffer events * Fix resize lag when waiting in some situations * Update send test and errors that broke some examples/APIs * Improve clarity/fix typos in docs * Fix unreachable panic after setting ControlFlow to Poll during some RedrawRequested events. * Fix crash when running in release mode * Remove crossbeam dependency and make drop events work again * Remove serde implementations from ControlFlow * Fix 1.24.1 build * Fix freeze when setting decorations * Replace &EventLoop in callback with &EventLoopWindowTarget * Document and implement Debug for EventLoopWindowTarget * Fix some deadlocks that could occur when changing window state * Fix thread executor not executing closure when called from non-loop thread * Fix buffered events not getting dispatched * Fix crash with runner refcell not getting dropped * Address review feedback * Fix CHANGELOG typo * Catch panics in user callback
This commit is contained in:
parent
7be1d16263
commit
9602716ed2
92 changed files with 3467 additions and 1260 deletions
108
src/platform_impl/android/ffi.rs
Normal file
108
src/platform_impl/android/ffi.rs
Normal 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;
|
||||
424
src/platform_impl/android/mod.rs
Normal file
424
src/platform_impl/android/mod.rs
Normal file
|
|
@ -0,0 +1,424 @@
|
|||
#![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::MonitorHandle as RootMonitorHandle;
|
||||
|
||||
pub struct EventLoop {
|
||||
event_rx: Receiver<android_glue::Event>,
|
||||
suspend_callback: RefCell<Option<Box<Fn(bool) -> ()>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EventLoopProxy;
|
||||
|
||||
impl EventLoop {
|
||||
pub fn new() -> EventLoop {
|
||||
let (tx, rx) = channel();
|
||||
android_glue::add_sender(tx);
|
||||
EventLoop {
|
||||
event_rx: rx,
|
||||
suspend_callback: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
let mut rb = VecDeque::with_capacity(1);
|
||||
rb.push_back(MonitorHandle);
|
||||
rb
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorHandle {
|
||||
MonitorHandle
|
||||
}
|
||||
|
||||
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 = MonitorHandle.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 = MonitorHandle.get_hidpi_factor();
|
||||
let physical_size = MonitorHandle.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::Redraw,
|
||||
})
|
||||
}
|
||||
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) -> EventLoopProxy {
|
||||
EventLoopProxy
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopProxy {
|
||||
pub fn wakeup(&self) -> Result<(), ::EventLoopClosed> {
|
||||
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 MonitorHandle;
|
||||
|
||||
impl fmt::Debug for MonitorHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct MonitorHandle {
|
||||
name: Option<String>,
|
||||
dimensions: PhysicalSize,
|
||||
position: PhysicalPosition,
|
||||
hidpi_factor: f64,
|
||||
}
|
||||
|
||||
let monitor_id_proxy = MonitorHandle {
|
||||
name: self.get_name(),
|
||||
dimensions: self.get_dimensions(),
|
||||
position: self.get_position(),
|
||||
hidpi_factor: self.get_hidpi_factor(),
|
||||
};
|
||||
|
||||
monitor_id_proxy.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
#[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(_: &EventLoop, 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 set_fullscreen(&self, _monitor: Option<RootMonitorHandle>) {
|
||||
// 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) -> RootMonitorHandle {
|
||||
RootMonitorHandle { inner: MonitorHandle }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
let mut rb = VecDeque::with_capacity(1);
|
||||
rb.push_back(MonitorHandle);
|
||||
rb
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorHandle {
|
||||
MonitorHandle
|
||||
}
|
||||
|
||||
#[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);
|
||||
314
src/platform_impl/emscripten/ffi.rs
Normal file
314
src/platform_impl/emscripten/ffi.rs
Normal 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;
|
||||
}
|
||||
1135
src/platform_impl/emscripten/mod.rs
Normal file
1135
src/platform_impl/emscripten/mod.rs
Normal file
File diff suppressed because it is too large
Load diff
110
src/platform_impl/ios/ffi.rs
Normal file
110
src/platform_impl/ios/ffi.rs
Normal 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]
|
||||
}
|
||||
}
|
||||
702
src/platform_impl/ios/mod.rs
Normal file
702
src/platform_impl/ios/mod.rs
Normal file
|
|
@ -0,0 +1,702 @@
|
|||
//! 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::MonitorHandle as RootMonitorHandle;
|
||||
|
||||
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 MonitorHandle;
|
||||
|
||||
impl fmt::Debug for MonitorHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct MonitorHandle {
|
||||
name: Option<String>,
|
||||
dimensions: PhysicalSize,
|
||||
position: PhysicalPosition,
|
||||
hidpi_factor: f64,
|
||||
}
|
||||
|
||||
let monitor_id_proxy = MonitorHandle {
|
||||
name: self.get_name(),
|
||||
dimensions: self.get_dimensions(),
|
||||
position: self.get_position(),
|
||||
hidpi_factor: self.get_hidpi_factor(),
|
||||
};
|
||||
|
||||
monitor_id_proxy.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
#[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 EventLoop {
|
||||
events_queue: Arc<RefCell<VecDeque<Event>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EventLoopProxy;
|
||||
|
||||
impl EventLoop {
|
||||
pub fn new() -> EventLoop {
|
||||
unsafe {
|
||||
if !msg_send![class!(NSThread), isMainThread] {
|
||||
panic!("`EventLoop` can only be created on the main thread on iOS");
|
||||
}
|
||||
}
|
||||
EventLoop { events_queue: Default::default() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
let mut rb = VecDeque::with_capacity(1);
|
||||
rb.push_back(MonitorHandle);
|
||||
rb
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorHandle {
|
||||
MonitorHandle
|
||||
}
|
||||
|
||||
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(), "`EventLoop::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) -> EventLoopProxy {
|
||||
EventLoopProxy
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopProxy {
|
||||
pub fn wakeup(&self) -> Result<(), ::EventLoopClosed> {
|
||||
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: &EventLoop,
|
||||
_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![MonitorHandle.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 set_fullscreen(&self, _monitor: Option<RootMonitorHandle>) {
|
||||
// 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) -> RootMonitorHandle {
|
||||
RootMonitorHandle { inner: MonitorHandle }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
let mut rb = VecDeque::with_capacity(1);
|
||||
rb.push_back(MonitorHandle);
|
||||
rb
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorHandle {
|
||||
MonitorHandle
|
||||
}
|
||||
|
||||
#[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);
|
||||
15
src/platform_impl/linux/dlopen.rs
Normal file
15
src/platform_impl/linux/dlopen.rs
Normal 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="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;
|
||||
}
|
||||
542
src/platform_impl/linux/mod.rs
Normal file
542
src/platform_impl/linux/mod.rs
Normal file
|
|
@ -0,0 +1,542 @@
|
|||
#![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,
|
||||
EventLoopClosed,
|
||||
Icon,
|
||||
MouseCursor,
|
||||
ControlFlow,
|
||||
WindowAttributes,
|
||||
};
|
||||
use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
|
||||
use window::MonitorHandle as RootMonitorHandle;
|
||||
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 MonitorHandle {
|
||||
X(x11::MonitorHandle),
|
||||
Wayland(wayland::MonitorHandle),
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
#[inline]
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
match self {
|
||||
&MonitorHandle::X(ref m) => m.get_name(),
|
||||
&MonitorHandle::Wayland(ref m) => m.get_name(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_native_identifier(&self) -> u32 {
|
||||
match self {
|
||||
&MonitorHandle::X(ref m) => m.get_native_identifier(),
|
||||
&MonitorHandle::Wayland(ref m) => m.get_native_identifier(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_dimensions(&self) -> PhysicalSize {
|
||||
match self {
|
||||
&MonitorHandle::X(ref m) => m.get_dimensions(),
|
||||
&MonitorHandle::Wayland(ref m) => m.get_dimensions(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> PhysicalPosition {
|
||||
match self {
|
||||
&MonitorHandle::X(ref m) => m.get_position(),
|
||||
&MonitorHandle::Wayland(ref m) => m.get_position(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
match self {
|
||||
&MonitorHandle::X(ref m) => m.get_hidpi_factor(),
|
||||
&MonitorHandle::Wayland(ref m) => m.get_hidpi_factor() as f64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
#[inline]
|
||||
pub fn new(
|
||||
event_loop: &EventLoop,
|
||||
attribs: WindowAttributes,
|
||||
pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Self, CreationError> {
|
||||
match *event_loop {
|
||||
EventsLoop::Wayland(ref event_loop) => {
|
||||
wayland::Window::new(event_loop, attribs, pl_attribs).map(Window::Wayland)
|
||||
},
|
||||
EventLoop::X(ref event_loop) => {
|
||||
x11::Window::new(event_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 set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
||||
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) -> RootMonitorHandle {
|
||||
match self {
|
||||
&Window::X(ref window) => RootMonitorHandle { inner: MonitorHandle::X(window.get_current_monitor()) },
|
||||
&Window::Wayland(ref window) => RootMonitorHandle { inner: MonitorHandle::Wayland(window.get_current_monitor()) },
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
match self {
|
||||
&Window::X(ref window) => window.get_available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::X)
|
||||
.collect(),
|
||||
&Window::Wayland(ref window) => window.get_available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::Wayland)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorHandle {
|
||||
match self {
|
||||
&Window::X(ref window) => MonitorHandle::X(window.get_primary_monitor()),
|
||||
&Window::Wayland(ref window) => MonitorHandle::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 EventLoop {
|
||||
Wayland(wayland::EventLoop),
|
||||
X(x11::EventLoop)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum EventLoopProxy {
|
||||
X(x11::EventLoopProxy),
|
||||
Wayland(wayland::EventLoopProxy),
|
||||
}
|
||||
|
||||
impl EventLoop {
|
||||
pub fn new() -> EventLoop {
|
||||
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
|
||||
match env_var.as_str() {
|
||||
"x11" => {
|
||||
// TODO: propagate
|
||||
return EventLoop::new_x11().expect("Failed to initialize X11 backend");
|
||||
},
|
||||
"wayland" => {
|
||||
return EventLoop::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 EventLoop::new_wayland() {
|
||||
Ok(event_loop) => return event_loop,
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
let x11_err = match EventLoop::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<EventLoop, ConnectError> {
|
||||
wayland::EventLoop::new()
|
||||
.map(EventLoop::Wayland)
|
||||
}
|
||||
|
||||
pub fn new_x11() -> Result<EventLoop, XNotSupported> {
|
||||
X11_BACKEND
|
||||
.lock()
|
||||
.as_ref()
|
||||
.map(Arc::clone)
|
||||
.map(x11::EventLoop::new)
|
||||
.map(EventLoop::X)
|
||||
.map_err(|err| err.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
match *self {
|
||||
EventLoop::Wayland(ref evlp) => evlp
|
||||
.get_available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::Wayland)
|
||||
.collect(),
|
||||
EventLoop::X(ref evlp) => evlp
|
||||
.x_connection()
|
||||
.get_available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::X)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorHandle {
|
||||
match *self {
|
||||
EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.get_primary_monitor()),
|
||||
EventLoop::X(ref evlp) => MonitorHandle::X(evlp.x_connection().get_primary_monitor()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventLoopProxy {
|
||||
match *self {
|
||||
EventLoop::Wayland(ref evlp) => EventLoopProxy::Wayland(evlp.create_proxy()),
|
||||
EventLoop::X(ref evlp) => EventLoopProxy::X(evlp.create_proxy()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_events<F>(&mut self, callback: F)
|
||||
where F: FnMut(::Event)
|
||||
{
|
||||
match *self {
|
||||
EventLoop::Wayland(ref mut evlp) => evlp.poll_events(callback),
|
||||
EventLoop::X(ref mut evlp) => evlp.poll_events(callback)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_forever<F>(&mut self, callback: F)
|
||||
where F: FnMut(::Event) -> ControlFlow
|
||||
{
|
||||
match *self {
|
||||
EventLoop::Wayland(ref mut evlp) => evlp.run_forever(callback),
|
||||
EventLoop::X(ref mut evlp) => evlp.run_forever(callback)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_wayland(&self) -> bool {
|
||||
match *self {
|
||||
EventLoop::Wayland(_) => true,
|
||||
EventLoop::X(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn x_connection(&self) -> Option<&Arc<XConnection>> {
|
||||
match *self {
|
||||
EventLoop::Wayland(_) => None,
|
||||
EventLoop::X(ref ev) => Some(ev.x_connection()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopProxy {
|
||||
pub fn wakeup(&self) -> Result<(), EventLoopClosed> {
|
||||
match *self {
|
||||
EventLoopProxy::Wayland(ref proxy) => proxy.wakeup(),
|
||||
EventLoopProxy::X(ref proxy) => proxy.wakeup(),
|
||||
}
|
||||
}
|
||||
}
|
||||
594
src/platform_impl/linux/wayland/event_loop.rs
Normal file
594
src/platform_impl/linux/wayland/event_loop.rs
Normal file
|
|
@ -0,0 +1,594 @@
|
|||
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, EventLoopClosed, 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 EventLoopSink {
|
||||
buffer: VecDeque<::Event>,
|
||||
}
|
||||
|
||||
impl EventLoopSink {
|
||||
pub fn new() -> EventLoopSink {
|
||||
EventLoopSink {
|
||||
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 EventLoop {
|
||||
// The Event Queue
|
||||
pub evq: RefCell<EventQueue>,
|
||||
// our sink, shared with some handlers, buffering the events
|
||||
sink: Arc<Mutex<EventLoopSink>>,
|
||||
// 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 `EventLoop`.
|
||||
//
|
||||
// We should only try and wake up the `EventLoop` if it still exists, so we hold Weak ptrs.
|
||||
#[derive(Clone)]
|
||||
pub struct EventLoopProxy {
|
||||
display: Weak<Display>,
|
||||
pending_wakeup: Weak<AtomicBool>,
|
||||
}
|
||||
|
||||
impl EventLoopProxy {
|
||||
// Causes the `EventLoop` to stop blocking on `run_forever` and emit an `Awakened` event.
|
||||
//
|
||||
// Returns `Err` if the associated `EventLoop` no longer exists.
|
||||
pub fn wakeup(&self) -> Result<(), EventLoopClosed> {
|
||||
let display = self.display.upgrade();
|
||||
let wakeup = self.pending_wakeup.upgrade();
|
||||
match (display, wakeup) {
|
||||
(Some(display), Some(wakeup)) => {
|
||||
// Update the `EventLoop`'s `pending_wakeup` flag.
|
||||
wakeup.store(true, Ordering::Relaxed);
|
||||
// Cause the `EventLoop` to break from `dispatch` if it is currently blocked.
|
||||
let _ = display.sync(|callback| callback.implement(|_, _| {}, ()));
|
||||
display.flush().map_err(|_| EventLoopClosed)?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(EventLoopClosed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoop {
|
||||
pub fn new() -> Result<EventLoop, 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(EventLoopSink::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(EventLoop {
|
||||
display,
|
||||
evq: RefCell::new(event_queue),
|
||||
sink,
|
||||
pending_wakeup,
|
||||
store,
|
||||
env,
|
||||
cleanup_needed: Arc::new(Mutex::new(false)),
|
||||
seats,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventLoopProxy {
|
||||
EventLoopProxy {
|
||||
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) -> MonitorHandle {
|
||||
get_primary_monitor(&self.env.outputs)
|
||||
}
|
||||
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
get_available_monitors(&self.env.outputs)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Private EventLoop Internals
|
||||
*/
|
||||
|
||||
impl EventLoop {
|
||||
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::Redraw, wid);
|
||||
}
|
||||
if closed {
|
||||
sink.send_event(::WindowEvent::CloseRequested, wid);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Wayland protocol implementations
|
||||
*/
|
||||
|
||||
struct SeatManager {
|
||||
sink: Arc<Mutex<EventLoopSink>>,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
seats: Arc<Mutex<Vec<(u32, Proxy<wl_seat::WlSeat>)>>>,
|
||||
event_loop_proxy: EventLoopProxy,
|
||||
}
|
||||
|
||||
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;
|
||||
<<<<<<< HEAD:src/platform_impl/linux/wayland/event_loop.rs
|
||||
match evt {
|
||||
GlobalEvent::New {
|
||||
id,
|
||||
ref interface,
|
||||
version,
|
||||
} if interface == "wl_seat" =>
|
||||
{
|
||||
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
|
||||
<<<<<<< HEAD
|
||||
.bind(min(version, 5), id, move |seat| {
|
||||
seat.implement(move |event, seat| {
|
||||
seat_data.receive(event, seat)
|
||||
}, ())
|
||||
})
|
||||
.unwrap();
|
||||
=======
|
||||
.bind::<wl_seat::WlSeat>(min(version, 5), id)
|
||||
.unwrap()
|
||||
.implement(SeatData {
|
||||
sink: self.sink.clone(),
|
||||
store: self.store.clone(),
|
||||
pointer: None,
|
||||
keyboard: None,
|
||||
touch: None,
|
||||
event_loop_proxy: self.event_loop_proxy.clone(),
|
||||
});
|
||||
>>>>>>> Change instances of "events_loop" to "event_loop"
|
||||
self.store.lock().unwrap().new_seat(&seat);
|
||||
self.seats.lock().unwrap().push((id, seat));
|
||||
}
|
||||
GlobalEvent::Removed { id, ref interface } if interface == "wl_seat" => {
|
||||
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();
|
||||
}
|
||||
}
|
||||
=======
|
||||
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();
|
||||
>>>>>>> master:src/platform/linux/wayland/event_loop.rs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SeatData {
|
||||
sink: Arc<Mutex<EventLoopSink>>,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
pointer: Option<Proxy<wl_pointer::WlPointer>>,
|
||||
keyboard: Option<Proxy<wl_keyboard::WlKeyboard>>,
|
||||
touch: Option<Proxy<wl_touch::WlTouch>>,
|
||||
<<<<<<< HEAD
|
||||
events_loop_proxy: EventsLoopProxy,
|
||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
||||
=======
|
||||
event_loop_proxy: EventLoopProxy,
|
||||
>>>>>>> Change instances of "events_loop" to "event_loop"
|
||||
}
|
||||
|
||||
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(),
|
||||
<<<<<<< HEAD
|
||||
self.events_loop_proxy.clone(),
|
||||
self.modifiers_tracker.clone(),
|
||||
=======
|
||||
self.event_loop_proxy.clone(),
|
||||
>>>>>>> Change instances of "events_loop" to "event_loop"
|
||||
))
|
||||
}
|
||||
// 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 MonitorHandle {
|
||||
pub(crate) proxy: Proxy<wl_output::WlOutput>,
|
||||
pub(crate) mgr: OutputMgr,
|
||||
}
|
||||
|
||||
impl Clone for MonitorHandle {
|
||||
fn clone(&self) -> MonitorHandle {
|
||||
MonitorHandle {
|
||||
proxy: self.proxy.clone(),
|
||||
mgr: self.mgr.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MonitorHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct MonitorHandle {
|
||||
name: Option<String>,
|
||||
native_identifier: u32,
|
||||
dimensions: PhysicalSize,
|
||||
position: PhysicalPosition,
|
||||
hidpi_factor: i32,
|
||||
}
|
||||
|
||||
let monitor_id_proxy = MonitorHandle {
|
||||
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 MonitorHandle {
|
||||
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) -> MonitorHandle {
|
||||
outputs.with_all(|list| {
|
||||
if let Some(&(_, ref proxy, _)) = list.first() {
|
||||
MonitorHandle {
|
||||
proxy: proxy.clone(),
|
||||
mgr: outputs.clone(),
|
||||
}
|
||||
} else {
|
||||
panic!("No monitor is available.")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_available_monitors(outputs: &OutputMgr) -> VecDeque<MonitorHandle> {
|
||||
outputs.with_all(|list| {
|
||||
list.iter()
|
||||
.map(|&(_, ref proxy, _)| MonitorHandle {
|
||||
proxy: proxy.clone(),
|
||||
mgr: outputs.clone(),
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
363
src/platform_impl/linux/wayland/keyboard.rs
Normal file
363
src/platform_impl/linux/wayland/keyboard.rs
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::{make_wid, DeviceId, EventLoopProxy, EventLoopSink};
|
||||
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<EventLoopSink>>,
|
||||
event_loop_proxy: EventLoopProxy,
|
||||
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);
|
||||
}
|
||||
}
|
||||
event_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),
|
||||
// => 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/platform_impl/linux/wayland/mod.rs
Normal file
37
src/platform_impl/linux/wayland/mod.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#![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::{EventLoop, EventLoopProxy, EventLoopSink, MonitorHandle};
|
||||
|
||||
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;
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(usize);
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
WindowId(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn make_wid(s: &Proxy<wl_surface::WlSurface>) -> WindowId {
|
||||
WindowId(s.c_ptr() as usize)
|
||||
}
|
||||
189
src/platform_impl/linux/wayland/pointer.rs
Normal file
189
src/platform_impl/linux/wayland/pointer.rs
Normal 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::EventLoopSink;
|
||||
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<EventLoopSink>>,
|
||||
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()
|
||||
}
|
||||
97
src/platform_impl/linux/wayland/touch.rs
Normal file
97
src/platform_impl/linux/wayland/touch.rs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use {TouchPhase, WindowEvent};
|
||||
|
||||
use super::{DeviceId, WindowId};
|
||||
use super::event_loop::EventLoopSink;
|
||||
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<EventLoopSink>>,
|
||||
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()
|
||||
}
|
||||
451
src/platform_impl/linux/wayland/window.rs
Normal file
451
src/platform_impl/linux/wayland/window.rs
Normal file
|
|
@ -0,0 +1,451 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
|
||||
use {CreationError, MouseCursor, WindowAttributes};
|
||||
use dpi::{LogicalPosition, LogicalSize};
|
||||
<<<<<<< HEAD:src/platform_impl/linux/wayland/window.rs
|
||||
use platform_impl::MonitorHandle as PlatformMonitorHandle;
|
||||
use window::MonitorHandle as RootMonitorHandle;
|
||||
=======
|
||||
use platform::{MonitorId as PlatformMonitorId, PlatformSpecificWindowBuilderAttributes as PlAttributes};
|
||||
use window::MonitorId as RootMonitorId;
|
||||
>>>>>>> master:src/platform/linux/wayland/window.rs
|
||||
|
||||
use sctk::surface::{get_dpi_factor, get_outputs};
|
||||
use sctk::window::{ConceptFrame, Event as WEvent, 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, EventLoop, MonitorHandle, WindowId};
|
||||
use platform_impl::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>>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(evlp: &EventLoop, 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 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, .. } => {
|
||||
let mut store = window_store.lock().unwrap();
|
||||
for window in &mut store.windows {
|
||||
if window.surface.equals(&my_surface) {
|
||||
window.newsize = new_size;
|
||||
window.need_refresh = true;
|
||||
*(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);
|
||||
}
|
||||
|
||||
for &(_, ref seat) in evlp.seats.lock().unwrap().iter() {
|
||||
frame.new_seat(seat);
|
||||
}
|
||||
|
||||
// Check for fullscreen requirements
|
||||
if let Some(RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::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(),
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
#[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 set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
||||
if let Some(RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::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
|
||||
}
|
||||
|
||||
<<<<<<< HEAD:src/platform_impl/linux/wayland/window.rs
|
||||
pub fn get_current_monitor(&self) -> MonitorHandle {
|
||||
// we don't know how much each monitor sees us so...
|
||||
// just return the most recent one ?
|
||||
let guard = self.monitors.lock().unwrap();
|
||||
guard.monitors.last().unwrap().clone()
|
||||
=======
|
||||
pub fn get_current_monitor(&self) -> MonitorId {
|
||||
let output = get_outputs(&self.surface).last().unwrap().clone();
|
||||
MonitorId {
|
||||
proxy: output,
|
||||
mgr: self.outputs.clone(),
|
||||
}
|
||||
>>>>>>> master:src/platform/linux/wayland/window.rs
|
||||
}
|
||||
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
get_available_monitors(&self.outputs)
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor(&self) -> MonitorHandle {
|
||||
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)>>,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
<<<<<<< HEAD:src/platform_impl/linux/wayland/window.rs
|
||||
|
||||
/*
|
||||
* Monitor list with some covenience method to compute DPI
|
||||
*/
|
||||
|
||||
struct MonitorList {
|
||||
monitors: Vec<MonitorHandle>
|
||||
}
|
||||
|
||||
impl MonitorList {
|
||||
fn new() -> MonitorList {
|
||||
MonitorList {
|
||||
monitors: Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_hidpi_factor(&self) -> i32 {
|
||||
let mut factor = 1;
|
||||
for monitor_id in &self.monitors {
|
||||
let monitor_dpi = monitor_id.get_hidpi_factor();
|
||||
if monitor_dpi > factor { factor = monitor_dpi; }
|
||||
}
|
||||
factor
|
||||
}
|
||||
|
||||
fn add_output(&mut self, monitor: MonitorHandle) -> Option<i32> {
|
||||
let old_dpi = self.compute_hidpi_factor();
|
||||
let monitor_dpi = monitor.get_hidpi_factor();
|
||||
self.monitors.push(monitor);
|
||||
if monitor_dpi > old_dpi {
|
||||
Some(monitor_dpi)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn del_output(&mut self, output: &Proxy<wl_output::WlOutput>) -> Option<i32> {
|
||||
let old_dpi = self.compute_hidpi_factor();
|
||||
self.monitors.retain(|m| !m.proxy.equals(output));
|
||||
let new_dpi = self.compute_hidpi_factor();
|
||||
if new_dpi != old_dpi {
|
||||
Some(new_dpi)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
=======
|
||||
>>>>>>> master:src/platform/linux/wayland/window.rs
|
||||
219
src/platform_impl/linux/x11/dnd.rs
Normal file
219
src/platform_impl/linux/x11/dnd.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
1008
src/platform_impl/linux/x11/events.rs
Normal file
1008
src/platform_impl/linux/x11/events.rs
Normal file
File diff suppressed because it is too large
Load diff
8
src/platform_impl/linux/x11/ffi.rs
Normal file
8
src/platform_impl/linux/x11/ffi.rs
Normal 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::*;
|
||||
185
src/platform_impl/linux/x11/ime/callbacks.rs
Normal file
185
src/platform_impl/linux/x11/ime/callbacks.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
134
src/platform_impl/linux/x11/ime/context.rs
Normal file
134
src/platform_impl/linux/x11/ime/context.rs
Normal 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::<()>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/platform_impl/linux/x11/ime/inner.rs
Normal file
75
src/platform_impl/linux/x11/ime/inner.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::{ffi, XConnection, XError};
|
||||
|
||||
use super::input_method::PotentialInputMethods;
|
||||
use super::context::ImeContext;
|
||||
|
||||
pub unsafe fn close_im(xconn: &Arc<XConnection>, im: ffi::XIM) -> Result<(), XError> {
|
||||
(xconn.xlib.XCloseIM)(im);
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub unsafe fn destroy_ic(xconn: &Arc<XConnection>, ic: ffi::XIC) -> Result<(), XError> {
|
||||
(xconn.xlib.XDestroyIC)(ic);
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub struct ImeInner {
|
||||
pub xconn: Arc<XConnection>,
|
||||
// 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,
|
||||
// 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,
|
||||
pub is_fallback: bool,
|
||||
}
|
||||
|
||||
impl ImeInner {
|
||||
pub fn new(
|
||||
xconn: Arc<XConnection>,
|
||||
potential_input_methods: PotentialInputMethods,
|
||||
) -> Self {
|
||||
ImeInner {
|
||||
xconn,
|
||||
im: ptr::null_mut(),
|
||||
potential_input_methods,
|
||||
contexts: HashMap::new(),
|
||||
destroy_callback: unsafe { mem::zeroed() },
|
||||
is_destroyed: false,
|
||||
is_fallback: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn close_im_if_necessary(&self) -> Result<bool, XError> {
|
||||
if !self.is_destroyed {
|
||||
close_im(&self.xconn, self.im).map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn destroy_ic_if_necessary(&self, ic: ffi::XIC) -> Result<bool, XError> {
|
||||
if !self.is_destroyed {
|
||||
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() {
|
||||
if let &Some(ref context) = context {
|
||||
self.destroy_ic_if_necessary(context.ic)?;
|
||||
}
|
||||
}
|
||||
Ok(!self.is_destroyed)
|
||||
}
|
||||
}
|
||||
283
src/platform_impl/linux/x11/ime/input_method.rs
Normal file
283
src/platform_impl/linux/x11/ime/input_method.rs
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
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 parking_lot::Mutex;
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
|
||||
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.
|
||||
(xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr());
|
||||
|
||||
let im = (xconn.xlib.XOpenIM)(
|
||||
xconn.display,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
|
||||
if im.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(im)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InputMethod {
|
||||
pub im: ffi::XIM,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl InputMethod {
|
||||
fn new(im: ffi::XIM, name: String) -> Self {
|
||||
InputMethod { im, name }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InputMethodResult {
|
||||
/// Input method used locale modifier from `XMODIFIERS` environment variable.
|
||||
XModifiers(InputMethod),
|
||||
/// Input method used internal fallback locale modifier.
|
||||
Fallback(InputMethod),
|
||||
/// Input method could not be opened using any locale modifier tried.
|
||||
Failure,
|
||||
}
|
||||
|
||||
impl InputMethodResult {
|
||||
pub fn is_fallback(&self) -> bool {
|
||||
if let &InputMethodResult::Fallback(_) = self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ok(self) -> Option<InputMethod> {
|
||||
use self::InputMethodResult::*;
|
||||
match self {
|
||||
XModifiers(im) | Fallback(im) => Some(im),
|
||||
Failure => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum GetXimServersError {
|
||||
XError(XError),
|
||||
GetPropertyError(util::GetPropertyError),
|
||||
InvalidUtf8(IntoStringError),
|
||||
}
|
||||
|
||||
// The root window has a property named XIM_SERVERS, which contains a list of atoms represeting
|
||||
// the availabile XIM servers. For instance, if you're using ibus, it would contain an atom named
|
||||
// "@server=ibus". It's possible for this property to contain multiple atoms, though presumably
|
||||
// rare. Note that we replace "@server=" with "@im=" in order to match the format of locale
|
||||
// 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 servers_atom = xconn.get_atom_unchecked(b"XIM_SERVERS\0");
|
||||
|
||||
let root = (xconn.xlib.XDefaultRootWindow)(xconn.display);
|
||||
|
||||
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());
|
||||
(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 = CStr::from_ptr(name)
|
||||
.to_owned()
|
||||
.into_string()
|
||||
.map_err(GetXimServersError::InvalidUtf8)?;
|
||||
(xconn.xlib.XFree)(name as _);
|
||||
formatted_names.push(string.replace("@server=", "@im="));
|
||||
}
|
||||
xconn.check_errors().map_err(GetXimServersError::XError)?;
|
||||
Ok(formatted_names)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct InputMethodName {
|
||||
c_string: CString,
|
||||
string: String,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(string: &str) -> Self {
|
||||
let c_string = CString::new(string)
|
||||
.expect("String used to construct CString contained null byte");
|
||||
InputMethodName {
|
||||
c_string,
|
||||
string: string.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for InputMethodName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.string.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PotentialInputMethod {
|
||||
name: InputMethodName,
|
||||
successful: Option<bool>,
|
||||
}
|
||||
|
||||
impl PotentialInputMethod {
|
||||
pub fn from_string(string: String) -> Self {
|
||||
PotentialInputMethod {
|
||||
name: InputMethodName::from_string(string),
|
||||
successful: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(string: &str) -> Self {
|
||||
PotentialInputMethod {
|
||||
name: InputMethodName::from_str(string),
|
||||
successful: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.successful = None;
|
||||
}
|
||||
|
||||
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.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 succceeded.
|
||||
#[derive(Debug, Clone)]
|
||||
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
|
||||
// who only need compose sequences, this ensures that the program launches without a hitch
|
||||
// For users who need more sophisticated IME features, this is more or less a silent failure.
|
||||
// Logging features should be added in the future to allow both audiences to be effectively
|
||||
// served.
|
||||
fallbacks: [PotentialInputMethod; 2],
|
||||
// For diagnostic purposes, we include the list of XIM servers that the server reports as
|
||||
// being available.
|
||||
_xim_servers: Result<Vec<String>, GetXimServersError>,
|
||||
}
|
||||
|
||||
impl PotentialInputMethods {
|
||||
pub fn new(xconn: &Arc<XConnection>) -> Self {
|
||||
let xmodifiers = env::var("XMODIFIERS")
|
||||
.ok()
|
||||
.map(PotentialInputMethod::from_string);
|
||||
PotentialInputMethods {
|
||||
// Since passing "" to XSetLocaleModifiers results in it defaulting to the value of
|
||||
// XMODIFIERS, it's worth noting what happens if XMODIFIERS is also "". If simply
|
||||
// running the program with `XMODIFIERS="" cargo run`, then assuming XMODIFIERS is
|
||||
// defined in the profile (or parent environment) then that parent XMODIFIERS is used.
|
||||
// If that XMODIFIERS value is also "" (i.e. if you ran `export XMODIFIERS=""`), then
|
||||
// XSetLocaleModifiers uses the default local input method. Note that defining
|
||||
// XMODIFIERS as "" is different from XMODIFIERS not being defined at all, since in
|
||||
// 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 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
|
||||
// that seems to be equivalent to just using the local input method.
|
||||
PotentialInputMethod::from_str("@im="),
|
||||
],
|
||||
// The XIM_SERVERS property can have surprising values. For instance, when I exited
|
||||
// ibus to run fcitx, it retained the value denoting ibus. Even more surprising is
|
||||
// that the fcitx input method could only be successfully opened using "@im=ibus".
|
||||
// Presumably due to this quirk, it's actually possible to alternate between ibus and
|
||||
// fcitx in a running application.
|
||||
_xim_servers: unsafe { get_xim_servers(xconn) },
|
||||
}
|
||||
}
|
||||
|
||||
// This resets the `successful` field of every potential input method, ensuring we have
|
||||
// accurate information when this struct is re-used by the destruction/instantiation callbacks.
|
||||
fn reset(&mut self) {
|
||||
if let Some(ref mut input_method) = self.xmodifiers {
|
||||
input_method.reset();
|
||||
}
|
||||
|
||||
for input_method in &mut self.fallbacks {
|
||||
input_method.reset();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_im(
|
||||
&mut self,
|
||||
xconn: &Arc<XConnection>,
|
||||
callback: Option<&Fn() -> ()>,
|
||||
) -> InputMethodResult {
|
||||
use self::InputMethodResult::*;
|
||||
|
||||
self.reset();
|
||||
|
||||
if let Some(ref mut input_method) = self.xmodifiers {
|
||||
let im = input_method.open_im(xconn);
|
||||
if let Some(im) = im {
|
||||
return XModifiers(im);
|
||||
} else {
|
||||
if let Some(ref callback) = callback {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for input_method in &mut self.fallbacks {
|
||||
let im = input_method.open_im(xconn);
|
||||
if let Some(im) = im {
|
||||
return Fallback(im);
|
||||
}
|
||||
}
|
||||
|
||||
Failure
|
||||
}
|
||||
}
|
||||
165
src/platform_impl/linux/x11/ime/mod.rs
Normal file
165
src/platform_impl/linux/x11/ime/mod.rs
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
// Important: all XIM calls need to happen from the same thread!
|
||||
|
||||
mod inner;
|
||||
mod input_method;
|
||||
mod context;
|
||||
mod callbacks;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
|
||||
use self::inner::{close_im, ImeInner};
|
||||
use self::input_method::PotentialInputMethods;
|
||||
use self::context::{ImeContextCreationError, ImeContext};
|
||||
use self::callbacks::*;
|
||||
|
||||
pub type ImeReceiver = Receiver<(ffi::Window, i16, i16)>;
|
||||
pub type ImeSender = Sender<(ffi::Window, i16, i16)>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ImeCreationError {
|
||||
OpenFailure(PotentialInputMethods),
|
||||
SetDestroyCallbackFailed(XError),
|
||||
}
|
||||
|
||||
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.
|
||||
inner: Box<ImeInner>,
|
||||
}
|
||||
|
||||
impl Ime {
|
||||
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,
|
||||
));
|
||||
let inner_ptr = Box::into_raw(inner);
|
||||
let client_data = inner_ptr as _;
|
||||
let destroy_callback = ffi::XIMCallback {
|
||||
client_data,
|
||||
callback: Some(xim_destroy_callback),
|
||||
};
|
||||
inner = unsafe { Box::from_raw(inner_ptr) };
|
||||
inner.destroy_callback = destroy_callback;
|
||||
(inner, client_data)
|
||||
};
|
||||
|
||||
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 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)
|
||||
.map_err(ImeCreationError::SetDestroyCallbackFailed);
|
||||
if result.is_err() {
|
||||
let _ = close_im(&xconn, input_method.im);
|
||||
}
|
||||
result?;
|
||||
}
|
||||
Ok(Ime { xconn, inner })
|
||||
} else {
|
||||
Err(ImeCreationError::OpenFailure(inner.potential_input_methods))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_destroyed(&self) -> bool {
|
||||
self.inner.is_destroyed
|
||||
}
|
||||
|
||||
// This pattern is used for various methods here:
|
||||
// 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)
|
||||
-> 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 {
|
||||
Some(unsafe { ImeContext::new(
|
||||
&self.inner.xconn,
|
||||
self.inner.im,
|
||||
window,
|
||||
None,
|
||||
) }?)
|
||||
};
|
||||
self.inner.contexts.insert(window, context);
|
||||
Ok(!self.is_destroyed())
|
||||
}
|
||||
|
||||
pub fn get_context(&self, window: ffi::Window) -> Option<ffi::XIC> {
|
||||
if self.is_destroyed() {
|
||||
return None;
|
||||
}
|
||||
if let Some(&Some(ref context)) = self.inner.contexts.get(&window) {
|
||||
Some(context.ic)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_context(&mut self, window: ffi::Window) -> Result<bool, XError> {
|
||||
if let Some(Some(context)) = self.inner.contexts.remove(&window) {
|
||||
unsafe {
|
||||
self.inner.destroy_ic_if_necessary(context.ic)?;
|
||||
}
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus(&mut self, window: ffi::Window) -> Result<bool, XError> {
|
||||
if self.is_destroyed() {
|
||||
return Ok(false);
|
||||
}
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
context.focus(&self.xconn).map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unfocus(&mut self, window: ffi::Window) -> Result<bool, XError> {
|
||||
if self.is_destroyed() {
|
||||
return Ok(false);
|
||||
}
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
context.unfocus(&self.xconn).map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_xim_spot(&mut self, window: ffi::Window, x: i16, y: i16) {
|
||||
if self.is_destroyed() {
|
||||
return;
|
||||
}
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
context.set_spot(&self.xconn, x as _, y as _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Ime {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = self.inner.destroy_all_contexts_if_necessary();
|
||||
let _ = self.inner.close_im_if_necessary();
|
||||
}
|
||||
}
|
||||
}
|
||||
1447
src/platform_impl/linux/x11/mod.rs
Normal file
1447
src/platform_impl/linux/x11/mod.rs
Normal file
File diff suppressed because it is too large
Load diff
269
src/platform_impl/linux/x11/monitor.rs
Normal file
269
src/platform_impl/linux/x11/monitor.rs
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
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<MonitorHandle>>> = 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<MonitorHandle>> {
|
||||
// We update this lazily.
|
||||
(*MONITORS.lock()).take()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MonitorHandle {
|
||||
/// 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 MonitorHandle {
|
||||
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(MonitorHandle {
|
||||
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>) -> MonitorHandle {
|
||||
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<MonitorHandle> {
|
||||
unsafe {
|
||||
let root = (self.xlib.XDefaultRootWindow)(self.display);
|
||||
// WARNING: this function is supposedly very slow, on the order of hundreds of ms.
|
||||
// Upon failure, `resources` will be null.
|
||||
let resources = (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;
|
||||
MonitorHandle::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;
|
||||
MonitorHandle::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<MonitorHandle> {
|
||||
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) -> MonitorHandle {
|
||||
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)
|
||||
}
|
||||
}
|
||||
72
src/platform_impl/linux/x11/util/atom.rs
Normal file
72
src/platform_impl/linux/x11/util/atom.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
95
src/platform_impl/linux/x11/util/client_msg.rs
Normal file
95
src/platform_impl/linux/x11/util/client_msg.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
58
src/platform_impl/linux/x11/util/format.rs
Normal file
58
src/platform_impl/linux/x11/util/format.rs
Normal 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; }
|
||||
387
src/platform_impl/linux/x11/util/geometry.rs
Normal file
387
src/platform_impl/linux/x11/util/geometry.rs
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
use std::cmp;
|
||||
|
||||
use super::*;
|
||||
use {LogicalPosition, LogicalSize};
|
||||
|
||||
// Friendly neighborhood axis-aligned rectangle
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AaRect {
|
||||
x: i64,
|
||||
y: i64,
|
||||
width: i64,
|
||||
height: i64,
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
pub fn contains_point(&self, x: i64, y: i64) -> bool {
|
||||
x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
|
||||
}
|
||||
|
||||
pub fn get_overlapping_area(&self, other: &Self) -> i64 {
|
||||
let x_overlap = cmp::max(
|
||||
0,
|
||||
cmp::min(self.x + self.width, other.x + other.width) - cmp::max(self.x, other.x),
|
||||
);
|
||||
let y_overlap = cmp::max(
|
||||
0,
|
||||
cmp::min(self.y + self.height, other.y + other.height) - cmp::max(self.y, other.y),
|
||||
);
|
||||
x_overlap * y_overlap
|
||||
}
|
||||
}
|
||||
|
||||
#[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: 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
|
||||
// you call get_geometry with that window ID, then you'll get the position of that client area
|
||||
// window relative to the parent it's nested in (the frame), which isn't helpful if you want
|
||||
// to know the frame position.
|
||||
pub x_rel_parent: c_int,
|
||||
pub y_rel_parent: c_int,
|
||||
// In that same case, this will give you client area size.
|
||||
pub width: c_uint,
|
||||
pub height: c_uint,
|
||||
// xmonad and dwm were the only WMs tested that use the border return at all.
|
||||
// The majority of WMs seem to simply fill it with 0 unconditionally.
|
||||
pub border: c_uint,
|
||||
pub depth: c_uint,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FrameExtents {
|
||||
pub left: c_ulong,
|
||||
pub right: c_ulong,
|
||||
pub top: c_ulong,
|
||||
pub bottom: c_ulong,
|
||||
}
|
||||
|
||||
impl FrameExtents {
|
||||
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: 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)]
|
||||
pub struct LogicalFrameExtents {
|
||||
pub left: f64,
|
||||
pub right: f64,
|
||||
pub top: f64,
|
||||
pub bottom: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum FrameExtentsHeuristicPath {
|
||||
Supported,
|
||||
UnsupportedNested,
|
||||
UnsupportedBordered,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FrameExtentsHeuristic {
|
||||
pub frame_extents: FrameExtents,
|
||||
pub heuristic_path: FrameExtentsHeuristicPath,
|
||||
}
|
||||
|
||||
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)
|
||||
} 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 u32
|
||||
),
|
||||
height.saturating_add(
|
||||
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 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 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: 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;
|
||||
}
|
||||
|
||||
// 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<c_ulong>> = self.get_property(
|
||||
window,
|
||||
extents_atom,
|
||||
ffi::XA_CARDINAL,
|
||||
).ok();
|
||||
|
||||
extents.and_then(|extents| {
|
||||
if extents.len() >= 4 {
|
||||
Some(FrameExtents {
|
||||
left: extents[0],
|
||||
right: extents[1],
|
||||
top: extents[2],
|
||||
bottom: extents[3],
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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<ffi::Window>> = self.get_property(
|
||||
root,
|
||||
client_list_atom,
|
||||
ffi::XA_WINDOW,
|
||||
).ok();
|
||||
|
||||
client_list.map(|client_list| client_list.contains(&window))
|
||||
}
|
||||
|
||||
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: ffi::Window, root: ffi::Window) -> Result<ffi::Window, XError> {
|
||||
let mut outer_window = window;
|
||||
loop {
|
||||
let candidate = self.get_parent_window(outer_window)?;
|
||||
if candidate == root {
|
||||
break;
|
||||
}
|
||||
outer_window = candidate;
|
||||
}
|
||||
Ok(outer_window)
|
||||
}
|
||||
|
||||
pub fn get_frame_extents_heuristic(&self, window: ffi::Window, root: ffi::Window) -> FrameExtentsHeuristic {
|
||||
use self::FrameExtentsHeuristicPath::*;
|
||||
|
||||
// Position relative to root window.
|
||||
// With rare exceptions, this is the position of a nested window. Cases where the window
|
||||
// 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.y_rel_root,
|
||||
coords.child,
|
||||
)
|
||||
};
|
||||
|
||||
let (width, height, border) = {
|
||||
let inner_geometry = self.get_geometry(window).expect("Failed to get inner window geometry");
|
||||
(
|
||||
inner_geometry.width,
|
||||
inner_geometry.height,
|
||||
inner_geometry.border,
|
||||
)
|
||||
};
|
||||
|
||||
// The first condition is only false for un-nested windows, but isn't always false for
|
||||
// un-nested windows. Mutter/Muffin/Budgie and Marco present a mysterious discrepancy:
|
||||
// when y is on the range [0, 2] and if the window has been unfocused since being
|
||||
// undecorated (or was undecorated upon construction), the first condition is true,
|
||||
// requiring us to rely on the second condition.
|
||||
let nested = !(window == child || self.is_top_level(child, root) == Some(true));
|
||||
|
||||
// Hopefully the WM supports EWMH, allowing us to get exact info on the window frames.
|
||||
if let Some(mut frame_extents) = self.get_frame_extents(window) {
|
||||
// Mutter/Muffin/Budgie and Marco preserve their decorated frame extents when
|
||||
// decorations are disabled, but since the window becomes un-nested, it's easy to
|
||||
// catch.
|
||||
if !nested {
|
||||
frame_extents = FrameExtents::new(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
// The difference between the nested window's position and the outermost window's
|
||||
// position is equivalent to the frame size. In most scenarios, this is equivalent to
|
||||
// manually climbing the hierarchy as is done in the case below. Here's a list of
|
||||
// known discrepancies:
|
||||
// * Mutter/Muffin/Budgie gives decorated windows a margin of 9px (only 7px on top) in
|
||||
// addition to a 1px semi-transparent border. The margin can be easily observed by
|
||||
// using a screenshot tool to get a screenshot of a selected window, and is
|
||||
// presumably used for drawing drop shadows. Getting window geometry information
|
||||
// via hierarchy-climbing results in this margin being included in both the
|
||||
// position and outer size, so a window positioned at (0, 0) would be reported as
|
||||
// having a position (-10, -8).
|
||||
// * Compiz has a drop shadow margin just like Mutter/Muffin/Budgie, though it's 10px
|
||||
// on all sides, and there's no additional border.
|
||||
// * Enlightenment otherwise gets a y position equivalent to inner_y_rel_root.
|
||||
// Without decorations, there's no difference. This is presumably related to
|
||||
// Enlightenment's fairly unique concept of window position; it interprets
|
||||
// positions given to XMoveWindow as a client area position rather than a position
|
||||
// of the overall window.
|
||||
|
||||
FrameExtentsHeuristic {
|
||||
frame_extents,
|
||||
heuristic_path: Supported,
|
||||
}
|
||||
} else if nested {
|
||||
// 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_y, outer_width, outer_height) = {
|
||||
let outer_geometry = self.get_geometry(outer_window).expect("Failed to get outer window geometry");
|
||||
(
|
||||
outer_geometry.y_rel_parent,
|
||||
outer_geometry.width,
|
||||
outer_geometry.height,
|
||||
)
|
||||
};
|
||||
|
||||
// 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);
|
||||
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.into(),
|
||||
right.into(),
|
||||
top.into(),
|
||||
bottom.into(),
|
||||
);
|
||||
FrameExtentsHeuristic {
|
||||
frame_extents,
|
||||
heuristic_path: UnsupportedNested,
|
||||
}
|
||||
} else {
|
||||
// This is the case for xmonad and dwm, AKA the only WMs tested that supplied a
|
||||
// border value. This is convenient, since we can use it to get an accurate frame.
|
||||
let frame_extents = FrameExtents::from_border(border.into());
|
||||
FrameExtentsHeuristic {
|
||||
frame_extents,
|
||||
heuristic_path: UnsupportedBordered,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
236
src/platform_impl/linux/x11/util/hint.rs
Normal file
236
src/platform_impl/linux/x11/util/hint.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
34
src/platform_impl/linux/x11/util/icon.rs
Normal file
34
src/platform_impl/linux/x11/util/icon.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use {Icon, Pixel, PIXEL_SIZE};
|
||||
use super::*;
|
||||
|
||||
impl Pixel {
|
||||
pub fn to_packed_argb(&self) -> Cardinal {
|
||||
let mut cardinal = 0;
|
||||
assert!(CARDINAL_SIZE >= PIXEL_SIZE);
|
||||
let as_bytes = &mut cardinal as *mut _ as *mut u8;
|
||||
unsafe {
|
||||
*as_bytes.offset(0) = self.b;
|
||||
*as_bytes.offset(1) = self.g;
|
||||
*as_bytes.offset(2) = self.r;
|
||||
*as_bytes.offset(3) = self.a;
|
||||
}
|
||||
cardinal
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
assert_eq!(pixel_count, (self.width * self.height) as usize);
|
||||
let mut data = Vec::with_capacity(pixel_count);
|
||||
data.push(self.width as Cardinal);
|
||||
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.offset(pixel_index as isize) };
|
||||
data.push(pixel.to_packed_argb());
|
||||
}
|
||||
data
|
||||
}
|
||||
}
|
||||
159
src/platform_impl/linux/x11/util/input.rs
Normal file
159
src/platform_impl/linux/x11/util/input.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/platform_impl/linux/x11/util/memory.rs
Normal file
62
src/platform_impl/linux/x11/util/memory.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct XSmartPointer<'a, T> {
|
||||
xconn: &'a XConnection,
|
||||
pub ptr: *mut T,
|
||||
}
|
||||
|
||||
impl<'a, T> XSmartPointer<'a, T> {
|
||||
// You're responsible for only passing things to this that should be XFree'd.
|
||||
// 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,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for XSmartPointer<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { &*self.ptr }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> DerefMut for XSmartPointer<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
unsafe { &mut *self.ptr }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Drop for XSmartPointer<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.xconn.xlib.XFree)(self.ptr as *mut _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
104
src/platform_impl/linux/x11/util/mod.rs
Normal file
104
src/platform_impl/linux/x11/util/mod.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
// 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 format;
|
||||
mod geometry;
|
||||
mod hint;
|
||||
mod icon;
|
||||
mod input;
|
||||
mod memory;
|
||||
mod randr;
|
||||
mod window_property;
|
||||
mod 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;
|
||||
use std::ptr;
|
||||
use std::ops::BitAnd;
|
||||
use std::os::raw::*;
|
||||
|
||||
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);
|
||||
if *field != wrapped {
|
||||
*field = wrapped;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
||||
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.
|
||||
// This buffer contains the requests you make, and is flushed under various circumstances:
|
||||
// 1. `XPending`, `XNextEvent`, and `XWindowEvent` flush "as needed"
|
||||
// 2. `XFlush` explicitly flushes
|
||||
// 3. `XSync` flushes and blocks until all requests are responded to
|
||||
// 4. Calls that have a return dependent on a response (i.e. `XGetWindowProperty`) sync internally.
|
||||
// When in doubt, check the X11 source; if a function calls `_XReply`, it flushes and waits.
|
||||
// All util functions that abstract an async function will return a `Flusher`.
|
||||
pub fn flush_requests(&self) -> Result<(), XError> {
|
||||
unsafe { (self.xlib.XFlush)(self.display) };
|
||||
//println!("XFlush");
|
||||
// This isn't necessarily a useful time to check for errors (since our request hasn't
|
||||
// necessarily been processed yet)
|
||||
self.check_errors()
|
||||
}
|
||||
|
||||
pub fn sync_with_server(&self) -> Result<(), XError> {
|
||||
unsafe { (self.xlib.XSync)(self.display, ffi::False) };
|
||||
//println!("XSync");
|
||||
self.check_errors()
|
||||
}
|
||||
}
|
||||
111
src/platform_impl/linux/x11/util/randr.rs
Normal file
111
src/platform_impl/linux/x11/util/randr.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
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 || width_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 {
|
||||
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 = 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))
|
||||
}
|
||||
}
|
||||
144
src/platform_impl/linux/x11/util/window_property.rs
Normal file
144
src/platform_impl/linux/x11/util/window_property.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
141
src/platform_impl/linux/x11/util/wm.rs
Normal file
141
src/platform_impl/linux/x11/util/wm.rs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
use parking_lot::Mutex;
|
||||
|
||||
use super::*;
|
||||
|
||||
// This info is global to the window manager.
|
||||
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: 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() {
|
||||
names.contains(&name.as_str())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
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: ffi::Window) -> Vec<ffi::Atom> {
|
||||
let supported_atom = unsafe { self.get_atom_unchecked(b"_NET_SUPPORTED\0") };
|
||||
self.get_property(
|
||||
root,
|
||||
supported_atom,
|
||||
ffi::XA_ATOM,
|
||||
).unwrap_or_else(|_| Vec::with_capacity(0))
|
||||
}
|
||||
|
||||
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
|
||||
// inavailability of time machines, we'll just try to get _NET_SUPPORTING_WM_CHECK
|
||||
// regardless of whether or not the WM claims to support it.
|
||||
//
|
||||
// Blackbox 0.70 also incorrectly reports not supporting this, though that appears to be fixed
|
||||
// in 0.72.
|
||||
/*if !supported_hints.contains(&check_atom) {
|
||||
return None;
|
||||
}*/
|
||||
|
||||
// IceWM (1.3.x and earlier) doesn't report supporting _NET_WM_NAME, but will nonetheless
|
||||
// provide us with a value for it. Note that the unofficial 1.4 fork of IceWM works fine.
|
||||
/*if !supported_hints.contains(&wm_name_atom) {
|
||||
return None;
|
||||
}*/
|
||||
|
||||
// Of the WMs tested, only xmonad and dwm fail to provide a WM name.
|
||||
|
||||
// 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(
|
||||
root,
|
||||
check_atom,
|
||||
ffi::XA_WINDOW,
|
||||
);
|
||||
|
||||
let wm_check = result
|
||||
.ok()
|
||||
.and_then(|wm_check| wm_check.get(0).cloned());
|
||||
|
||||
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(
|
||||
root_window_wm_check,
|
||||
check_atom,
|
||||
ffi::XA_WINDOW,
|
||||
);
|
||||
|
||||
let wm_check = result
|
||||
.ok()
|
||||
.and_then(|wm_check| wm_check.get(0).cloned());
|
||||
|
||||
if let Some(wm_check) = wm_check {
|
||||
wm_check
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// These values should be the same.
|
||||
if root_window_wm_check != child_window_wm_check {
|
||||
return None;
|
||||
}
|
||||
|
||||
// All of that work gives us a window ID that we can get the WM name from.
|
||||
let wm_name = {
|
||||
let utf8_string_atom = unsafe { self.get_atom_unchecked(b"UTF8_STRING\0") };
|
||||
|
||||
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
|
||||
// information in this string (this means you'll have to be careful if you want to match
|
||||
// against it, though).
|
||||
// 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(ffi::XA_STRING)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if no_utf8 {
|
||||
self.get_property(
|
||||
root_window_wm_check,
|
||||
wm_name_atom,
|
||||
ffi::XA_STRING,
|
||||
)
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}.ok();
|
||||
|
||||
wm_name.and_then(|wm_name| String::from_utf8(wm_name).ok())
|
||||
}
|
||||
}
|
||||
1213
src/platform_impl/linux/x11/window.rs
Normal file
1213
src/platform_impl/linux/x11/window.rs
Normal file
File diff suppressed because it is too large
Load diff
155
src/platform_impl/linux/x11/xdisplay.rs
Normal file
155
src/platform_impl/linux/x11/xdisplay.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
use std::ptr;
|
||||
use std::fmt;
|
||||
use std::error::Error;
|
||||
|
||||
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 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
|
||||
};
|
||||
|
||||
Ok(XConnection {
|
||||
xlib,
|
||||
xrandr,
|
||||
xrandr_1_5,
|
||||
xcursor,
|
||||
xinput2,
|
||||
xlib_xcb,
|
||||
display,
|
||||
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())
|
||||
}
|
||||
}
|
||||
742
src/platform_impl/macos/event_loop.rs
Normal file
742
src/platform_impl/macos/event_loop.rs
Normal file
|
|
@ -0,0 +1,742 @@
|
|||
use {ControlFlow, EventLoopClosed};
|
||||
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 EventLoop {
|
||||
modifiers: Modifiers,
|
||||
pub shared: Arc<Shared>,
|
||||
}
|
||||
|
||||
// State shared between the `EventLoop` 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 `EventLoop` 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 EventLoop {
|
||||
|
||||
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 `EventLoopProxy::wakeup()`), causing a wrong thread to be
|
||||
// marked as the main thread.
|
||||
unsafe { appkit::NSApp(); }
|
||||
|
||||
EventLoop {
|
||||
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<(), EventLoopClosed> {
|
||||
// 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 to_virtual_key_code(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::LWin,
|
||||
0x37 => events::VirtualKeyCode::RWin,
|
||||
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,
|
||||
//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_additional_virtual_key_codes(
|
||||
s: &Option<String>
|
||||
) -> Option<events::VirtualKeyCode> {
|
||||
if let &Some(ref s) = s {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
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 keycode = NSEvent::keyCode(ns_event);
|
||||
let scancode = keycode as u32;
|
||||
let virtual_keycode = to_virtual_key_code(keycode);
|
||||
Some(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode,
|
||||
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_impl/macos/ffi.rs
Normal file
107
src/platform_impl/macos/ffi.rs
Normal 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_impl/macos/mod.rs
Normal file
51
src/platform_impl/macos/mod.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#![cfg(target_os = "macos")]
|
||||
|
||||
pub use self::event_loop::{EventLoop, Proxy as EventLoopProxy};
|
||||
pub use self::monitor::MonitorHandle;
|
||||
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(event_loop: &EventLoop,
|
||||
attributes: ::WindowAttributes,
|
||||
pl_attribs: PlatformSpecificWindowBuilderAttributes) -> Result<Self, CreationError>
|
||||
{
|
||||
let weak_shared = Arc::downgrade(&event_loop.shared);
|
||||
let window = Arc::new(try!(Window2::new(weak_shared, attributes, pl_attribs)));
|
||||
let weak_window = Arc::downgrade(&window);
|
||||
event_loop.shared.windows.lock().unwrap().push(weak_window);
|
||||
Ok(Window { window: window })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
mod event_loop;
|
||||
mod ffi;
|
||||
mod monitor;
|
||||
mod util;
|
||||
mod view;
|
||||
mod window;
|
||||
147
src/platform_impl/macos/monitor.rs
Normal file
147
src/platform_impl/macos/monitor.rs
Normal 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::EventLoop;
|
||||
use super::window::{IdRef, Window2};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct MonitorHandle(CGDirectDisplayID);
|
||||
|
||||
fn get_available_monitors() -> VecDeque<MonitorHandle> {
|
||||
if let Ok(displays) = CGDisplay::active_displays() {
|
||||
let mut monitors = VecDeque::with_capacity(displays.len());
|
||||
for d in displays {
|
||||
monitors.push_back(MonitorHandle(d));
|
||||
}
|
||||
monitors
|
||||
} else {
|
||||
VecDeque::with_capacity(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor() -> MonitorHandle {
|
||||
let id = MonitorHandle(CGDisplay::main().id);
|
||||
id
|
||||
}
|
||||
|
||||
impl EventLoop {
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
get_available_monitors()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorHandle {
|
||||
get_primary_monitor()
|
||||
}
|
||||
|
||||
pub fn make_monitor_from_display(id: CGDirectDisplayID) -> MonitorHandle {
|
||||
let id = MonitorHandle(id);
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
impl Window2 {
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
get_available_monitors()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorHandle {
|
||||
get_primary_monitor()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MonitorHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct MonitorHandle {
|
||||
name: Option<String>,
|
||||
native_identifier: u32,
|
||||
dimensions: PhysicalSize,
|
||||
position: PhysicalPosition,
|
||||
hidpi_factor: f64,
|
||||
}
|
||||
|
||||
let monitor_id_proxy = MonitorHandle {
|
||||
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 MonitorHandle {
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
let MonitorHandle(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 MonitorHandle(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
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/platform_impl/macos/util.rs
Normal file
38
src/platform_impl/macos/util.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use cocoa::appkit::NSWindowStyleMask;
|
||||
use cocoa::base::{id, nil};
|
||||
use cocoa::foundation::{NSRect, NSUInteger};
|
||||
use core_graphics::display::CGDisplay;
|
||||
|
||||
use platform_impl::platform::ffi;
|
||||
use platform_impl::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 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];
|
||||
}
|
||||
648
src/platform_impl/macos/view.rs
Normal file
648
src/platform_impl/macos/view.rs
Normal file
|
|
@ -0,0 +1,648 @@
|
|||
// 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_impl::platform::event_loop::{DEVICE_ID, event_mods, Shared, to_virtual_key_code, check_additional_virtual_key_codes};
|
||||
use platform_impl::platform::util;
|
||||
use platform_impl::platform::ffi::*;
|
||||
use platform_impl::platform::window::{get_window_id, IdRef};
|
||||
|
||||
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) -> Option<String> {
|
||||
unsafe {
|
||||
let characters: id = msg_send![event, characters];
|
||||
let slice = slice::from_raw_parts(
|
||||
characters.UTF8String() as *const c_uchar,
|
||||
characters.len(),
|
||||
);
|
||||
let string = str::from_utf8_unchecked(slice);
|
||||
Some(string.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
state.raw_characters = get_characters(event);
|
||||
|
||||
let keycode: c_ushort = msg_send![event, keyCode];
|
||||
// We are checking here for F21-F24 keys, since their keycode
|
||||
// can vary, but we know that they are encoded
|
||||
// in characters property.
|
||||
let virtual_keycode = to_virtual_key_code(keycode)
|
||||
.or_else(|| {
|
||||
check_additional_virtual_key_codes(&state.raw_characters)
|
||||
});
|
||||
let scancode = keycode as u32;
|
||||
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),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let characters: id = msg_send![event, characters];
|
||||
let slice = slice::from_raw_parts(
|
||||
characters.UTF8String() as *const c_uchar,
|
||||
characters.len(),
|
||||
);
|
||||
let string = str::from_utf8_unchecked(slice);
|
||||
|
||||
state.raw_characters = {
|
||||
Some(string.to_owned())
|
||||
};
|
||||
|
||||
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 string.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;
|
||||
|
||||
// We need characters here to check for additional keys such as
|
||||
// F21-F24.
|
||||
let characters = get_characters(event);
|
||||
|
||||
let keycode: c_ushort = msg_send![event, keyCode];
|
||||
let virtual_keycode = to_virtual_key_code(keycode)
|
||||
.or_else(|| {
|
||||
check_additional_virtual_key_codes(&characters)
|
||||
});
|
||||
let scancode = keycode as u32;
|
||||
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
|
||||
}
|
||||
1362
src/platform_impl/macos/window.rs
Normal file
1362
src/platform_impl/macos/window.rs
Normal file
File diff suppressed because it is too large
Load diff
26
src/platform_impl/mod.rs
Normal file
26
src/platform_impl/mod.rs
Normal 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_impl/windows/dpi.rs
Normal file
189
src/platform_impl/windows/dpi.rs
Normal 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) })
|
||||
}
|
||||
234
src/platform_impl/windows/drop_handler.rs
Normal file
234
src/platform_impl/windows/drop_handler.rs
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
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_impl::platform::WindowId;
|
||||
|
||||
use event::Event;
|
||||
use window::WindowId as SuperWindowId;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct FileDropHandlerData {
|
||||
pub interface: IDropTarget,
|
||||
refcount: AtomicUsize,
|
||||
window: HWND,
|
||||
send_event: Box<Fn(Event<()>)>,
|
||||
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, send_event: Box<Fn(Event<()>)>) -> FileDropHandler {
|
||||
let data = Box::new(FileDropHandlerData {
|
||||
interface: IDropTarget {
|
||||
lpVtbl: &DROP_TARGET_VTBL as *const IDropTargetVtbl,
|
||||
},
|
||||
refcount: AtomicUsize::new(1),
|
||||
window,
|
||||
send_event,
|
||||
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 event::WindowEvent::HoveredFile;
|
||||
let drop_handler = Self::from_interface(this);
|
||||
let hdrop = Self::iterate_filenames(pDataObj, |filename| {
|
||||
drop_handler.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 event::WindowEvent::HoveredFileCancelled;
|
||||
let drop_handler = Self::from_interface(this);
|
||||
if drop_handler.hovered_is_valid {
|
||||
drop_handler.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 event::WindowEvent::DroppedFile;
|
||||
let drop_handler = Self::from_interface(this);
|
||||
let hdrop = Self::iterate_filenames(pDataObj, |filename| {
|
||||
drop_handler.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 FileDropHandlerData {
|
||||
fn send_event(&self, event: Event<()>) {
|
||||
(self.send_event)(event);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
264
src/platform_impl/windows/event.rs
Normal file
264
src/platform_impl/windows/event.rs
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
use std::char;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
use event::{ScanCode, ModifiersState, VirtualKeyCode};
|
||||
|
||||
use winapi::shared::minwindef::{WPARAM, LPARAM, UINT};
|
||||
use winapi::um::winuser;
|
||||
|
||||
pub fn get_key_mods() -> ModifiersState {
|
||||
let mut mods = ModifiersState::default();
|
||||
unsafe {
|
||||
if winuser::GetKeyState(winuser::VK_SHIFT) & (1 << 15) == (1 << 15) {
|
||||
mods.shift = true;
|
||||
}
|
||||
if winuser::GetKeyState(winuser::VK_CONTROL) & (1 << 15) == (1 << 15) {
|
||||
mods.ctrl = true;
|
||||
}
|
||||
if winuser::GetKeyState(winuser::VK_MENU) & (1 << 15) == (1 << 15) {
|
||||
mods.alt = true;
|
||||
}
|
||||
if (winuser::GetKeyState(winuser::VK_LWIN) | winuser::GetKeyState(winuser::VK_RWIN)) & (1 << 15) == (1 << 15) {
|
||||
mods.logo = true;
|
||||
}
|
||||
}
|
||||
mods
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
1597
src/platform_impl/windows/event_loop.rs
Normal file
1597
src/platform_impl/windows/event_loop.rs
Normal file
File diff suppressed because it is too large
Load diff
114
src/platform_impl/windows/icon.rs
Normal file
114
src/platform_impl/windows/icon.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
use std::{self, mem, ptr};
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
|
||||
use winapi::ctypes::{c_int, wchar_t};
|
||||
use winapi::shared::minwindef::{BYTE, LPARAM, WPARAM};
|
||||
use winapi::shared::windef::{HICON, HWND};
|
||||
use winapi::um::winuser;
|
||||
|
||||
use icon::{Pixel, PIXEL_SIZE, Icon};
|
||||
use platform_impl::platform::util;
|
||||
|
||||
impl Pixel {
|
||||
fn to_bgra(&mut self) {
|
||||
mem::swap(&mut self.r, &mut self.b);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum IconType {
|
||||
Small = winuser::ICON_SMALL as isize,
|
||||
Big = winuser::ICON_BIG as isize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WinIcon {
|
||||
pub handle: HICON,
|
||||
}
|
||||
|
||||
unsafe impl Send for WinIcon {}
|
||||
|
||||
impl WinIcon {
|
||||
#[allow(dead_code)]
|
||||
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, util::WinError> {
|
||||
let wide_path: Vec<u16> = path.as_ref().as_os_str().encode_wide().collect();
|
||||
let handle = unsafe {
|
||||
winuser::LoadImageW(
|
||||
ptr::null_mut(),
|
||||
wide_path.as_ptr() as *const wchar_t,
|
||||
winuser::IMAGE_ICON,
|
||||
0, // 0 indicates that we want to use the actual width
|
||||
0, // and height
|
||||
winuser::LR_LOADFROMFILE,
|
||||
) as HICON
|
||||
};
|
||||
if !handle.is_null() {
|
||||
Ok(WinIcon { handle })
|
||||
} else {
|
||||
Err(util::WinError::from_last_error())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_icon(icon: Icon) -> Result<Self, util::WinError> {
|
||||
Self::from_rgba(icon.rgba, icon.width, icon.height)
|
||||
}
|
||||
|
||||
pub fn from_rgba(mut rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, util::WinError> {
|
||||
assert_eq!(rgba.len() % PIXEL_SIZE, 0);
|
||||
let pixel_count = rgba.len() / PIXEL_SIZE;
|
||||
assert_eq!(pixel_count, (width * height) as usize);
|
||||
let mut and_mask = Vec::with_capacity(pixel_count);
|
||||
let pixels = rgba.as_mut_ptr() as *mut Pixel; // how not to write idiomatic Rust
|
||||
for pixel_index in 0..pixel_count {
|
||||
let pixel = unsafe { &mut *pixels.offset(pixel_index as isize) };
|
||||
and_mask.push(pixel.a.wrapping_sub(std::u8::MAX)); // invert alpha channel
|
||||
pixel.to_bgra();
|
||||
}
|
||||
assert_eq!(and_mask.len(), pixel_count);
|
||||
let handle = unsafe {
|
||||
winuser::CreateIcon(
|
||||
ptr::null_mut(),
|
||||
width as c_int,
|
||||
height as c_int,
|
||||
1,
|
||||
(PIXEL_SIZE * 8) as BYTE,
|
||||
and_mask.as_ptr() as *const BYTE,
|
||||
rgba.as_ptr() as *const BYTE,
|
||||
) as HICON
|
||||
};
|
||||
if !handle.is_null() {
|
||||
Ok(WinIcon { handle })
|
||||
} else {
|
||||
Err(util::WinError::from_last_error())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_for_window(&self, hwnd: HWND, icon_type: IconType) {
|
||||
unsafe {
|
||||
winuser::SendMessageW(
|
||||
hwnd,
|
||||
winuser::WM_SETICON,
|
||||
icon_type as WPARAM,
|
||||
self.handle as LPARAM,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WinIcon {
|
||||
fn drop(&mut self) {
|
||||
unsafe { winuser::DestroyIcon(self.handle) };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unset_for_window(hwnd: HWND, icon_type: IconType) {
|
||||
unsafe {
|
||||
winuser::SendMessageW(
|
||||
hwnd,
|
||||
winuser::WM_SETICON,
|
||||
icon_type as WPARAM,
|
||||
0 as LPARAM,
|
||||
);
|
||||
}
|
||||
}
|
||||
77
src/platform_impl/windows/mod.rs
Normal file
77
src/platform_impl/windows/mod.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#![cfg(target_os = "windows")]
|
||||
|
||||
use winapi;
|
||||
use winapi::shared::windef::HWND;
|
||||
|
||||
pub use self::event_loop::{EventLoop, EventLoopWindowTarget, EventLoopProxy};
|
||||
pub use self::monitor::MonitorHandle;
|
||||
pub use self::window::Window;
|
||||
|
||||
use window::Icon;
|
||||
use event::DeviceId as RootDeviceId;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub parent: Option<HWND>,
|
||||
pub taskbar_icon: Option<Icon>,
|
||||
pub no_redirection_bitmap: bool,
|
||||
}
|
||||
|
||||
unsafe impl Send for PlatformSpecificWindowBuilderAttributes {}
|
||||
unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {}
|
||||
|
||||
// Cursor name in UTF-16. Used to set cursor in `WM_SETCURSOR`.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Cursor(pub *const winapi::ctypes::wchar_t);
|
||||
unsafe impl Send for Cursor {}
|
||||
unsafe impl Sync for Cursor {}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId(u32);
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceId {
|
||||
pub fn get_persistent_identifier(&self) -> Option<String> {
|
||||
if self.0 != 0 {
|
||||
raw_input::get_raw_input_device_name(self.0 as _)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Constant device ID, to be removed when this backend is updated to report real device IDs.
|
||||
const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId(0));
|
||||
|
||||
fn wrap_device_id(id: u32) -> RootDeviceId {
|
||||
RootDeviceId(DeviceId(id))
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(HWND);
|
||||
unsafe impl Send for WindowId {}
|
||||
unsafe impl Sync for WindowId {}
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
use std::ptr::null_mut;
|
||||
|
||||
WindowId(null_mut())
|
||||
}
|
||||
}
|
||||
|
||||
mod dpi;
|
||||
mod drop_handler;
|
||||
mod event;
|
||||
mod event_loop;
|
||||
mod icon;
|
||||
mod monitor;
|
||||
mod raw_input;
|
||||
mod util;
|
||||
mod window;
|
||||
mod window_state;
|
||||
173
src/platform_impl/windows/monitor.rs
Normal file
173
src/platform_impl/windows/monitor.rs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
use winapi::shared::minwindef::{BOOL, DWORD, LPARAM, TRUE};
|
||||
use winapi::shared::windef::{HDC, HMONITOR, HWND, LPRECT, POINT};
|
||||
use winapi::um::winnt::LONG;
|
||||
use winapi::um::winuser;
|
||||
|
||||
use std::{mem, ptr};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use super::{EventLoop, util};
|
||||
use dpi::{PhysicalPosition, PhysicalSize};
|
||||
use platform_impl::platform::dpi::{dpi_to_scale_factor, get_monitor_dpi};
|
||||
use platform_impl::platform::window::Window;
|
||||
|
||||
/// Win32 implementation of the main `MonitorHandle` object.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MonitorHandle {
|
||||
/// Monitor handle.
|
||||
hmonitor: HMonitor,
|
||||
/// The system name of the monitor.
|
||||
monitor_name: String,
|
||||
/// True if this is the primary monitor.
|
||||
primary: bool,
|
||||
/// The position of the monitor in pixels on the desktop.
|
||||
///
|
||||
/// A window that is positioned at these coordinates will overlap the monitor.
|
||||
position: (i32, i32),
|
||||
/// The current resolution in pixels on the monitor.
|
||||
dimensions: (u32, u32),
|
||||
/// DPI scale factor.
|
||||
hidpi_factor: f64,
|
||||
}
|
||||
|
||||
// Send is not implemented for HMONITOR, we have to wrap it and implement it manually.
|
||||
// For more info see:
|
||||
// https://github.com/retep998/winapi-rs/issues/360
|
||||
// https://github.com/retep998/winapi-rs/issues/396
|
||||
#[derive(Debug, Clone)]
|
||||
struct HMonitor(HMONITOR);
|
||||
|
||||
unsafe impl Send for HMonitor {}
|
||||
|
||||
unsafe extern "system" fn monitor_enum_proc(
|
||||
hmonitor: HMONITOR,
|
||||
_hdc: HDC,
|
||||
_place: LPRECT,
|
||||
data: LPARAM,
|
||||
) -> BOOL {
|
||||
let monitors = data as *mut VecDeque<MonitorHandle>;
|
||||
(*monitors).push_back(MonitorHandle::from_hmonitor(hmonitor));
|
||||
TRUE // continue enumeration
|
||||
}
|
||||
|
||||
pub fn get_available_monitors() -> VecDeque<MonitorHandle> {
|
||||
let mut monitors: VecDeque<MonitorHandle> = VecDeque::new();
|
||||
unsafe {
|
||||
winuser::EnumDisplayMonitors(
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
Some(monitor_enum_proc),
|
||||
&mut monitors as *mut _ as LPARAM,
|
||||
);
|
||||
}
|
||||
monitors
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor() -> MonitorHandle {
|
||||
const ORIGIN: POINT = POINT { x: 0, y: 0 };
|
||||
let hmonitor = unsafe {
|
||||
winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY)
|
||||
};
|
||||
MonitorHandle::from_hmonitor(hmonitor)
|
||||
}
|
||||
|
||||
pub fn get_current_monitor(hwnd: HWND) -> MonitorHandle {
|
||||
let hmonitor = unsafe {
|
||||
winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST)
|
||||
};
|
||||
MonitorHandle::from_hmonitor(hmonitor)
|
||||
}
|
||||
|
||||
impl<T> EventLoop<T> {
|
||||
// TODO: Investigate opportunities for caching
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
get_available_monitors()
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor(&self) -> MonitorHandle {
|
||||
get_primary_monitor()
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
get_available_monitors()
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor(&self) -> MonitorHandle {
|
||||
get_primary_monitor()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result<winuser::MONITORINFOEXW, util::WinError> {
|
||||
let mut monitor_info: winuser::MONITORINFOEXW = unsafe { mem::uninitialized() };
|
||||
monitor_info.cbSize = mem::size_of::<winuser::MONITORINFOEXW>() as DWORD;
|
||||
let status = unsafe {
|
||||
winuser::GetMonitorInfoW(
|
||||
hmonitor,
|
||||
&mut monitor_info as *mut winuser::MONITORINFOEXW as *mut winuser::MONITORINFO,
|
||||
)
|
||||
};
|
||||
if status == 0 {
|
||||
Err(util::WinError::from_last_error())
|
||||
} else {
|
||||
Ok(monitor_info)
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
pub(crate) fn from_hmonitor(hmonitor: HMONITOR) -> Self {
|
||||
let monitor_info = get_monitor_info(hmonitor).expect("`GetMonitorInfoW` failed");
|
||||
let place = monitor_info.rcMonitor;
|
||||
let dimensions = (
|
||||
(place.right - place.left) as u32,
|
||||
(place.bottom - place.top) as u32,
|
||||
);
|
||||
MonitorHandle {
|
||||
hmonitor: HMonitor(hmonitor),
|
||||
monitor_name: util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr()),
|
||||
primary: util::has_flag(monitor_info.dwFlags, winuser::MONITORINFOF_PRIMARY),
|
||||
position: (place.left as i32, place.top as i32),
|
||||
dimensions,
|
||||
hidpi_factor: dpi_to_scale_factor(get_monitor_dpi(hmonitor).unwrap_or(96)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn contains_point(&self, point: &POINT) -> bool {
|
||||
let left = self.position.0 as LONG;
|
||||
let right = left + self.dimensions.0 as LONG;
|
||||
let top = self.position.1 as LONG;
|
||||
let bottom = top + self.dimensions.1 as LONG;
|
||||
point.x >= left && point.x <= right && point.y >= top && point.y <= bottom
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
Some(self.monitor_name.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_native_identifier(&self) -> String {
|
||||
self.monitor_name.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hmonitor(&self) -> HMONITOR {
|
||||
self.hmonitor.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_dimensions(&self) -> PhysicalSize {
|
||||
self.dimensions.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> PhysicalPosition {
|
||||
self.position.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
self.hidpi_factor
|
||||
}
|
||||
}
|
||||
235
src/platform_impl/windows/raw_input.rs
Normal file
235
src/platform_impl/windows/raw_input.rs
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
use std::mem::{self, size_of};
|
||||
use std::ptr;
|
||||
|
||||
use winapi::ctypes::wchar_t;
|
||||
use winapi::shared::minwindef::{UINT, USHORT, TRUE};
|
||||
use winapi::shared::hidusage::{
|
||||
HID_USAGE_PAGE_GENERIC,
|
||||
HID_USAGE_GENERIC_MOUSE,
|
||||
HID_USAGE_GENERIC_KEYBOARD,
|
||||
};
|
||||
use winapi::shared::windef::HWND;
|
||||
use winapi::um::winnt::HANDLE;
|
||||
use winapi::um::winuser::{
|
||||
self,
|
||||
RAWINPUTDEVICELIST,
|
||||
RID_DEVICE_INFO,
|
||||
RID_DEVICE_INFO_MOUSE,
|
||||
RID_DEVICE_INFO_KEYBOARD,
|
||||
RID_DEVICE_INFO_HID,
|
||||
RIM_TYPEMOUSE,
|
||||
RIM_TYPEKEYBOARD,
|
||||
RIM_TYPEHID,
|
||||
RIDI_DEVICEINFO,
|
||||
RIDI_DEVICENAME,
|
||||
RAWINPUTDEVICE,
|
||||
RIDEV_DEVNOTIFY,
|
||||
RIDEV_INPUTSINK,
|
||||
HRAWINPUT,
|
||||
RAWINPUT,
|
||||
RAWINPUTHEADER,
|
||||
RID_INPUT,
|
||||
};
|
||||
|
||||
use platform_impl::platform::util;
|
||||
use event::ElementState;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_raw_input_device_list() -> Option<Vec<RAWINPUTDEVICELIST>> {
|
||||
let list_size = size_of::<RAWINPUTDEVICELIST>() as UINT;
|
||||
|
||||
let mut num_devices = 0;
|
||||
let status = unsafe { winuser::GetRawInputDeviceList(
|
||||
ptr::null_mut(),
|
||||
&mut num_devices,
|
||||
list_size,
|
||||
) };
|
||||
|
||||
if status == UINT::max_value() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut buffer = Vec::with_capacity(num_devices as _);
|
||||
|
||||
let num_stored = unsafe { winuser::GetRawInputDeviceList(
|
||||
buffer.as_ptr() as _,
|
||||
&mut num_devices,
|
||||
list_size,
|
||||
) };
|
||||
|
||||
if num_stored == UINT::max_value() {
|
||||
return None;
|
||||
}
|
||||
|
||||
debug_assert_eq!(num_devices, num_stored);
|
||||
|
||||
unsafe { buffer.set_len(num_devices as _) };
|
||||
|
||||
Some(buffer)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum RawDeviceInfo {
|
||||
Mouse(RID_DEVICE_INFO_MOUSE),
|
||||
Keyboard(RID_DEVICE_INFO_KEYBOARD),
|
||||
Hid(RID_DEVICE_INFO_HID),
|
||||
}
|
||||
|
||||
impl From<RID_DEVICE_INFO> for RawDeviceInfo {
|
||||
fn from(info: RID_DEVICE_INFO) -> Self {
|
||||
unsafe {
|
||||
match info.dwType {
|
||||
RIM_TYPEMOUSE => RawDeviceInfo::Mouse(*info.u.mouse()),
|
||||
RIM_TYPEKEYBOARD => RawDeviceInfo::Keyboard(*info.u.keyboard()),
|
||||
RIM_TYPEHID => RawDeviceInfo::Hid(*info.u.hid()),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_raw_input_device_info(handle: HANDLE) -> Option<RawDeviceInfo> {
|
||||
let mut info: RID_DEVICE_INFO = unsafe { mem::uninitialized() };
|
||||
let info_size = size_of::<RID_DEVICE_INFO>() as UINT;
|
||||
|
||||
info.cbSize = info_size;
|
||||
|
||||
let mut minimum_size = 0;
|
||||
let status = unsafe { winuser::GetRawInputDeviceInfoW(
|
||||
handle,
|
||||
RIDI_DEVICEINFO,
|
||||
&mut info as *mut _ as _,
|
||||
&mut minimum_size,
|
||||
) };
|
||||
|
||||
if status == UINT::max_value() || status == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
debug_assert_eq!(info_size, status);
|
||||
|
||||
Some(info.into())
|
||||
}
|
||||
|
||||
pub fn get_raw_input_device_name(handle: HANDLE) -> Option<String> {
|
||||
let mut minimum_size = 0;
|
||||
let status = unsafe { winuser::GetRawInputDeviceInfoW(
|
||||
handle,
|
||||
RIDI_DEVICENAME,
|
||||
ptr::null_mut(),
|
||||
&mut minimum_size,
|
||||
) };
|
||||
|
||||
if status != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut name: Vec<wchar_t> = Vec::with_capacity(minimum_size as _);
|
||||
|
||||
let status = unsafe { winuser::GetRawInputDeviceInfoW(
|
||||
handle,
|
||||
RIDI_DEVICENAME,
|
||||
name.as_ptr() as _,
|
||||
&mut minimum_size,
|
||||
) };
|
||||
|
||||
if status == UINT::max_value() || status == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
debug_assert_eq!(minimum_size, status);
|
||||
|
||||
unsafe { name.set_len(minimum_size as _) };
|
||||
|
||||
Some(util::wchar_to_string(&name))
|
||||
}
|
||||
|
||||
pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool {
|
||||
let device_size = size_of::<RAWINPUTDEVICE>() as UINT;
|
||||
|
||||
let success = unsafe { winuser::RegisterRawInputDevices(
|
||||
devices.as_ptr() as _,
|
||||
devices.len() as _,
|
||||
device_size,
|
||||
) };
|
||||
|
||||
success == TRUE
|
||||
}
|
||||
|
||||
pub fn register_all_mice_and_keyboards_for_raw_input(window_handle: HWND) -> bool {
|
||||
// RIDEV_DEVNOTIFY: receive hotplug events
|
||||
// RIDEV_INPUTSINK: receive events even if we're not in the foreground
|
||||
let flags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK;
|
||||
|
||||
let devices: [RAWINPUTDEVICE; 2] = [
|
||||
RAWINPUTDEVICE {
|
||||
usUsagePage: HID_USAGE_PAGE_GENERIC,
|
||||
usUsage: HID_USAGE_GENERIC_MOUSE,
|
||||
dwFlags: flags,
|
||||
hwndTarget: window_handle,
|
||||
},
|
||||
RAWINPUTDEVICE {
|
||||
usUsagePage: HID_USAGE_PAGE_GENERIC,
|
||||
usUsage: HID_USAGE_GENERIC_KEYBOARD,
|
||||
dwFlags: flags,
|
||||
hwndTarget: window_handle,
|
||||
},
|
||||
];
|
||||
|
||||
register_raw_input_devices(&devices)
|
||||
}
|
||||
|
||||
pub fn get_raw_input_data(handle: HRAWINPUT) -> Option<RAWINPUT> {
|
||||
let mut data: RAWINPUT = unsafe { mem::uninitialized() };
|
||||
let mut data_size = size_of::<RAWINPUT>() as UINT;
|
||||
let header_size = size_of::<RAWINPUTHEADER>() as UINT;
|
||||
|
||||
let status = unsafe { winuser::GetRawInputData(
|
||||
handle,
|
||||
RID_INPUT,
|
||||
&mut data as *mut _ as _,
|
||||
&mut data_size,
|
||||
header_size,
|
||||
) };
|
||||
|
||||
if status == UINT::max_value() || status == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(data)
|
||||
}
|
||||
|
||||
|
||||
fn button_flags_to_element_state(button_flags: USHORT, down_flag: USHORT, up_flag: USHORT)
|
||||
-> Option<ElementState>
|
||||
{
|
||||
// We assume the same button won't be simultaneously pressed and released.
|
||||
if util::has_flag(button_flags, down_flag) {
|
||||
Some(ElementState::Pressed)
|
||||
} else if util::has_flag(button_flags, up_flag) {
|
||||
Some(ElementState::Released)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option<ElementState>; 3] {
|
||||
[
|
||||
button_flags_to_element_state(
|
||||
button_flags,
|
||||
winuser::RI_MOUSE_LEFT_BUTTON_DOWN,
|
||||
winuser::RI_MOUSE_LEFT_BUTTON_UP,
|
||||
),
|
||||
button_flags_to_element_state(
|
||||
button_flags,
|
||||
winuser::RI_MOUSE_MIDDLE_BUTTON_DOWN,
|
||||
winuser::RI_MOUSE_MIDDLE_BUTTON_UP,
|
||||
),
|
||||
button_flags_to_element_state(
|
||||
button_flags,
|
||||
winuser::RI_MOUSE_RIGHT_BUTTON_DOWN,
|
||||
winuser::RI_MOUSE_RIGHT_BUTTON_UP,
|
||||
),
|
||||
]
|
||||
}
|
||||
183
src/platform_impl/windows/util.rs
Normal file
183
src/platform_impl/windows/util.rs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
use std::{self, mem, ptr, slice, io};
|
||||
use std::ops::BitAnd;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use window::MouseCursor;
|
||||
use winapi::ctypes::wchar_t;
|
||||
use winapi::shared::minwindef::{BOOL, DWORD};
|
||||
use winapi::shared::windef::{HWND, POINT, RECT};
|
||||
use winapi::um::errhandlingapi::GetLastError;
|
||||
use winapi::um::winbase::{
|
||||
FormatMessageW,
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER,
|
||||
FORMAT_MESSAGE_FROM_SYSTEM,
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
lstrlenW,
|
||||
LocalFree,
|
||||
};
|
||||
use winapi::um::winnt::{
|
||||
LPCWSTR,
|
||||
MAKELANGID,
|
||||
LANG_NEUTRAL,
|
||||
SUBLANG_DEFAULT,
|
||||
};
|
||||
use winapi::um::winuser;
|
||||
|
||||
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
||||
where T:
|
||||
Copy + PartialEq + BitAnd<T, Output = T>
|
||||
{
|
||||
bitset & flag == flag
|
||||
}
|
||||
|
||||
pub fn wchar_to_string(wchar: &[wchar_t]) -> String {
|
||||
String::from_utf16_lossy(wchar).to_string()
|
||||
}
|
||||
|
||||
pub fn wchar_ptr_to_string(wchar: *const wchar_t) -> String {
|
||||
let len = unsafe { lstrlenW(wchar) } as usize;
|
||||
let wchar_slice = unsafe { slice::from_raw_parts(wchar, len) };
|
||||
wchar_to_string(wchar_slice)
|
||||
}
|
||||
|
||||
pub unsafe fn status_map<T, F: FnMut(&mut T) -> BOOL>(mut fun: F) -> Option<T> {
|
||||
let mut data: T = mem::uninitialized();
|
||||
if fun(&mut data) != 0 {
|
||||
Some(data)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn win_to_err<F: FnOnce() -> BOOL>(f: F) -> Result<(), io::Error> {
|
||||
if f() != 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cursor_pos() -> Option<POINT> {
|
||||
unsafe { status_map(|cursor_pos| winuser::GetCursorPos(cursor_pos)) }
|
||||
}
|
||||
|
||||
pub fn get_window_rect(hwnd: HWND) -> Option<RECT> {
|
||||
unsafe { status_map(|rect| winuser::GetWindowRect(hwnd, rect)) }
|
||||
}
|
||||
|
||||
pub fn get_client_rect(hwnd: HWND) -> Result<RECT, io::Error> {
|
||||
unsafe {
|
||||
let mut rect = mem::uninitialized();
|
||||
let mut top_left = mem::zeroed();
|
||||
|
||||
win_to_err(|| winuser::ClientToScreen(hwnd, &mut top_left))?;
|
||||
win_to_err(|| winuser::GetClientRect(hwnd, &mut rect))?;
|
||||
rect.left += top_left.x;
|
||||
rect.top += top_left.y;
|
||||
rect.right += top_left.x;
|
||||
rect.bottom += top_left.y;
|
||||
|
||||
Ok(rect)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn adjust_window_rect(hwnd: HWND, rect: RECT) -> Option<RECT> {
|
||||
unsafe {
|
||||
let style = winuser::GetWindowLongW(hwnd, winuser::GWL_STYLE);
|
||||
let style_ex = winuser::GetWindowLongW(hwnd, winuser::GWL_EXSTYLE);
|
||||
adjust_window_rect_with_styles(hwnd, style as _, style_ex as _, rect)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn adjust_window_rect_with_styles(hwnd: HWND, style: DWORD, style_ex: DWORD, rect: RECT) -> Option<RECT> {
|
||||
unsafe { status_map(|r| {
|
||||
*r = rect;
|
||||
|
||||
let b_menu = !winuser::GetMenu(hwnd).is_null() as BOOL;
|
||||
winuser::AdjustWindowRectEx(r, style as _ , b_menu, style_ex as _)
|
||||
}) }
|
||||
}
|
||||
|
||||
pub fn set_cursor_hidden(hidden: bool) {
|
||||
static HIDDEN: AtomicBool = AtomicBool::new(false);
|
||||
let changed = HIDDEN.swap(hidden, Ordering::SeqCst) ^ hidden;
|
||||
if changed {
|
||||
unsafe{ winuser::ShowCursor(!hidden as BOOL) };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_clip(rect: Option<RECT>) -> Result<(), io::Error> {
|
||||
unsafe {
|
||||
let rect_ptr = rect.as_ref().map(|r| r as *const RECT).unwrap_or(ptr::null());
|
||||
win_to_err(|| winuser::ClipCursor(rect_ptr))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_focused(window: HWND) -> bool {
|
||||
window == unsafe{ winuser::GetActiveWindow() }
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct WinError(Option<String>);
|
||||
|
||||
impl WinError {
|
||||
pub fn from_last_error() -> Self {
|
||||
WinError(unsafe { get_last_error() })
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get_last_error() -> Option<String> {
|
||||
let err = GetLastError();
|
||||
if err != 0 {
|
||||
let buf_addr: LPCWSTR = {
|
||||
let mut buf_addr: LPCWSTR = mem::uninitialized();
|
||||
FormatMessageW(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER
|
||||
| FORMAT_MESSAGE_FROM_SYSTEM
|
||||
| FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
ptr::null(),
|
||||
err,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) as DWORD,
|
||||
// This is a pointer to a pointer
|
||||
&mut buf_addr as *mut LPCWSTR as *mut _,
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
buf_addr
|
||||
};
|
||||
if !buf_addr.is_null() {
|
||||
let buf_len = lstrlenW(buf_addr) as usize;
|
||||
let buf_slice = std::slice::from_raw_parts(buf_addr, buf_len);
|
||||
let string = wchar_to_string(buf_slice);
|
||||
LocalFree(buf_addr as *mut _);
|
||||
return Some(string);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
impl MouseCursor {
|
||||
pub(crate) fn to_windows_cursor(self) -> *const wchar_t {
|
||||
match self {
|
||||
MouseCursor::Arrow | MouseCursor::Default => winuser::IDC_ARROW,
|
||||
MouseCursor::Hand => winuser::IDC_HAND,
|
||||
MouseCursor::Crosshair => winuser::IDC_CROSS,
|
||||
MouseCursor::Text | MouseCursor::VerticalText => winuser::IDC_IBEAM,
|
||||
MouseCursor::NotAllowed | MouseCursor::NoDrop => winuser::IDC_NO,
|
||||
MouseCursor::Grab | MouseCursor::Grabbing |
|
||||
MouseCursor::Move | MouseCursor::AllScroll => winuser::IDC_SIZEALL,
|
||||
MouseCursor::EResize | MouseCursor::WResize |
|
||||
MouseCursor::EwResize | MouseCursor::ColResize => winuser::IDC_SIZEWE,
|
||||
MouseCursor::NResize | MouseCursor::SResize |
|
||||
MouseCursor::NsResize | MouseCursor::RowResize => winuser::IDC_SIZENS,
|
||||
MouseCursor::NeResize | MouseCursor::SwResize |
|
||||
MouseCursor::NeswResize => winuser::IDC_SIZENESW,
|
||||
MouseCursor::NwResize | MouseCursor::SeResize |
|
||||
MouseCursor::NwseResize => winuser::IDC_SIZENWSE,
|
||||
MouseCursor::Wait => winuser::IDC_WAIT,
|
||||
MouseCursor::Progress => winuser::IDC_APPSTARTING,
|
||||
MouseCursor::Help => winuser::IDC_HELP,
|
||||
_ => winuser::IDC_ARROW, // use arrow for the missing cases.
|
||||
}
|
||||
}
|
||||
}
|
||||
898
src/platform_impl/windows/window.rs
Normal file
898
src/platform_impl/windows/window.rs
Normal file
|
|
@ -0,0 +1,898 @@
|
|||
#![cfg(target_os = "windows")]
|
||||
|
||||
use std::{io, mem, ptr};
|
||||
use std::cell::Cell;
|
||||
use std::ffi::OsStr;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::channel;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use winapi::ctypes::c_int;
|
||||
use winapi::shared::minwindef::{DWORD, LPARAM, UINT, WORD, WPARAM};
|
||||
use winapi::shared::windef::{HWND, POINT, RECT};
|
||||
use winapi::um::{combaseapi, dwmapi, libloaderapi, ole2, winuser};
|
||||
use winapi::um::objbase::COINIT_APARTMENTTHREADED;
|
||||
use winapi::um::shobjidl_core::{CLSID_TaskbarList, ITaskbarList2};
|
||||
use winapi::um::wingdi::{CreateRectRgn, DeleteObject};
|
||||
use winapi::um::oleidl::LPDROPTARGET;
|
||||
use winapi::um::winnt::{LONG, LPCWSTR};
|
||||
|
||||
use window::{CreationError, Icon, MouseCursor, WindowAttributes};
|
||||
use dpi::{LogicalPosition, LogicalSize, PhysicalSize};
|
||||
use monitor::MonitorHandle as RootMonitorHandle;
|
||||
use platform_impl::platform::{
|
||||
{PlatformSpecificWindowBuilderAttributes, WindowId},
|
||||
dpi::{dpi_to_scale_factor, get_hwnd_dpi},
|
||||
drop_handler::FileDropHandler,
|
||||
event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID, REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID},
|
||||
icon::{self, IconType, WinIcon},
|
||||
monitor,
|
||||
raw_input::register_all_mice_and_keyboards_for_raw_input,
|
||||
util,
|
||||
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
|
||||
};
|
||||
|
||||
/// The Win32 implementation of the main `Window` object.
|
||||
pub struct Window {
|
||||
/// Main handle for the window.
|
||||
window: WindowWrapper,
|
||||
|
||||
/// The current window state.
|
||||
window_state: Arc<Mutex<WindowState>>,
|
||||
|
||||
// The events loop proxy.
|
||||
thread_executor: event_loop::EventLoopThreadExecutor,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new<T: 'static>(
|
||||
event_loop: &EventLoopWindowTarget<T>,
|
||||
w_attr: WindowAttributes,
|
||||
pl_attr: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Window, CreationError> {
|
||||
// We dispatch an `init` function because of code style.
|
||||
// First person to remove the need for cloning here gets a cookie!
|
||||
//
|
||||
// done. you owe me -- ossi
|
||||
unsafe {
|
||||
init(w_attr, pl_attr, event_loop).map(|win| {
|
||||
let file_drop_handler = {
|
||||
use winapi::shared::winerror::{OLE_E_WRONGCOMPOBJ, RPC_E_CHANGED_MODE, S_OK};
|
||||
|
||||
let ole_init_result = ole2::OleInitialize(ptr::null_mut());
|
||||
// It is ok if the initialize result is `S_FALSE` because it might happen that
|
||||
// multiple windows are created on the same thread.
|
||||
if ole_init_result == OLE_E_WRONGCOMPOBJ {
|
||||
panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`");
|
||||
} else if ole_init_result == RPC_E_CHANGED_MODE {
|
||||
panic!("OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`");
|
||||
}
|
||||
|
||||
let file_drop_runner = event_loop.runner_shared.clone();
|
||||
let file_drop_handler = FileDropHandler::new(
|
||||
win.window.0,
|
||||
Box::new(move |event| if let Ok(e) = event.map_nonuser_event() {file_drop_runner.send_event(e)})
|
||||
);
|
||||
let handler_interface_ptr = &mut (*file_drop_handler.data).interface as LPDROPTARGET;
|
||||
|
||||
assert_eq!(ole2::RegisterDragDrop(win.window.0, handler_interface_ptr), S_OK);
|
||||
file_drop_handler
|
||||
};
|
||||
|
||||
let subclass_input = event_loop::SubclassInput {
|
||||
window_state: win.window_state.clone(),
|
||||
event_loop_runner: event_loop.runner_shared.clone(),
|
||||
file_drop_handler,
|
||||
};
|
||||
|
||||
event_loop::subclass_window(win.window.0, subclass_input);
|
||||
win
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_title(&self, text: &str) {
|
||||
let text = OsStr::new(text)
|
||||
.encode_wide()
|
||||
.chain(Some(0).into_iter())
|
||||
.collect::<Vec<_>>();
|
||||
unsafe {
|
||||
winuser::SetWindowTextW(self.window.0, text.as_ptr() as LPCWSTR);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show(&self) {
|
||||
unsafe {
|
||||
winuser::ShowWindow(self.window.0, winuser::SW_SHOW);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide(&self) {
|
||||
unsafe {
|
||||
winuser::ShowWindow(self.window.0, winuser::SW_HIDE);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn request_redraw(&self) {
|
||||
unsafe {
|
||||
if self.thread_executor.trigger_newevents_on_redraw() {
|
||||
winuser::RedrawWindow(
|
||||
self.window.0,
|
||||
ptr::null(),
|
||||
ptr::null_mut(),
|
||||
winuser::RDW_INTERNALPAINT
|
||||
);
|
||||
} else {
|
||||
winuser::PostMessageW(self.window.0, *REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_position_physical(&self) -> Option<(i32, i32)> {
|
||||
util::get_window_rect(self.window.0)
|
||||
.map(|rect| (rect.left as i32, rect.top as i32))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> Option<LogicalPosition> {
|
||||
self.get_position_physical()
|
||||
.map(|physical_position| {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
LogicalPosition::from_physical(physical_position, dpi_factor)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn get_inner_position_physical(&self) -> Option<(i32, i32)> {
|
||||
let mut position: POINT = unsafe { mem::zeroed() };
|
||||
if unsafe { winuser::ClientToScreen(self.window.0, &mut position) } == 0 {
|
||||
return None;
|
||||
}
|
||||
Some((position.x, position.y))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
|
||||
self.get_inner_position_physical()
|
||||
.map(|physical_position| {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
LogicalPosition::from_physical(physical_position, dpi_factor)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn set_position_physical(&self, x: i32, y: i32) {
|
||||
unsafe {
|
||||
winuser::SetWindowPos(
|
||||
self.window.0,
|
||||
ptr::null_mut(),
|
||||
x as c_int,
|
||||
y as c_int,
|
||||
0,
|
||||
0,
|
||||
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOSIZE,
|
||||
);
|
||||
winuser::UpdateWindow(self.window.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_position(&self, logical_position: LogicalPosition) {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
let (x, y) = logical_position.to_physical(dpi_factor).into();
|
||||
self.set_position_physical(x, y);
|
||||
}
|
||||
|
||||
pub(crate) fn get_inner_size_physical(&self) -> Option<(u32, u32)> {
|
||||
let mut rect: RECT = unsafe { mem::uninitialized() };
|
||||
if unsafe { winuser::GetClientRect(self.window.0, &mut rect) } == 0 {
|
||||
return None;
|
||||
}
|
||||
Some((
|
||||
(rect.right - rect.left) as u32,
|
||||
(rect.bottom - rect.top) as u32,
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_size(&self) -> Option<LogicalSize> {
|
||||
self.get_inner_size_physical()
|
||||
.map(|physical_size| {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
LogicalSize::from_physical(physical_size, dpi_factor)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn get_outer_size_physical(&self) -> Option<(u32, u32)> {
|
||||
util::get_window_rect(self.window.0)
|
||||
.map(|rect| (
|
||||
(rect.right - rect.left) as u32,
|
||||
(rect.bottom - rect.top) as u32,
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_outer_size(&self) -> Option<LogicalSize> {
|
||||
self.get_outer_size_physical()
|
||||
.map(|physical_size| {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
LogicalSize::from_physical(physical_size, dpi_factor)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn set_inner_size_physical(&self, x: u32, y: u32) {
|
||||
unsafe {
|
||||
let rect = util::adjust_window_rect(
|
||||
self.window.0,
|
||||
RECT {
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: y as LONG,
|
||||
right: x as LONG,
|
||||
}
|
||||
).expect("adjust_window_rect failed");
|
||||
|
||||
let outer_x = (rect.right - rect.left).abs() as c_int;
|
||||
let outer_y = (rect.top - rect.bottom).abs() as c_int;
|
||||
winuser::SetWindowPos(
|
||||
self.window.0,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
0,
|
||||
outer_x,
|
||||
outer_y,
|
||||
winuser::SWP_ASYNCWINDOWPOS
|
||||
| winuser::SWP_NOZORDER
|
||||
| winuser::SWP_NOREPOSITION
|
||||
| winuser::SWP_NOMOVE,
|
||||
);
|
||||
winuser::UpdateWindow(self.window.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, logical_size: LogicalSize) {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
let (width, height) = logical_size.to_physical(dpi_factor).into();
|
||||
self.set_inner_size_physical(width, height);
|
||||
}
|
||||
|
||||
pub(crate) fn set_min_dimensions_physical(&self, dimensions: Option<(u32, u32)>) {
|
||||
self.window_state.lock().min_size = dimensions.map(Into::into);
|
||||
// Make windows re-check the window size bounds.
|
||||
self.get_inner_size_physical()
|
||||
.map(|(width, height)| self.set_inner_size_physical(width, height));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, logical_size: Option<LogicalSize>) {
|
||||
let physical_size = logical_size.map(|logical_size| {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
logical_size.to_physical(dpi_factor).into()
|
||||
});
|
||||
self.set_min_dimensions_physical(physical_size);
|
||||
}
|
||||
|
||||
pub fn set_max_dimensions_physical(&self, dimensions: Option<(u32, u32)>) {
|
||||
self.window_state.lock().max_size = dimensions.map(Into::into);
|
||||
// Make windows re-check the window size bounds.
|
||||
self.get_inner_size_physical()
|
||||
.map(|(width, height)| self.set_inner_size_physical(width, height));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_dimensions(&self, logical_size: Option<LogicalSize>) {
|
||||
let physical_size = logical_size.map(|logical_size| {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
logical_size.to_physical(dpi_factor).into()
|
||||
});
|
||||
self.set_max_dimensions_physical(physical_size);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
let window = self.window.clone();
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
WindowState::set_window_flags(
|
||||
window_state.lock(),
|
||||
window.0,
|
||||
None,
|
||||
|f| f.set(WindowFlags::RESIZABLE, resizable),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the `hwnd` of this window.
|
||||
#[inline]
|
||||
pub fn hwnd(&self) -> HWND {
|
||||
self.window.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, cursor: MouseCursor) {
|
||||
self.window_state.lock().mouse.cursor = cursor;
|
||||
self.thread_executor.execute_in_thread(move || unsafe {
|
||||
let cursor = winuser::LoadCursorW(
|
||||
ptr::null_mut(),
|
||||
cursor.to_windows_cursor(),
|
||||
);
|
||||
winuser::SetCursor(cursor);
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
|
||||
let window = self.window.clone();
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let (tx, rx) = channel();
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let result = window_state.lock().mouse
|
||||
.set_cursor_flags(window.0, |f| f.set(CursorFlags::GRABBED, grab))
|
||||
.map_err(|e| e.to_string());
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
rx.recv().unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide_cursor(&self, hide: bool) {
|
||||
let window = self.window.clone();
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let (tx, rx) = channel();
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let result = window_state.lock().mouse
|
||||
.set_cursor_flags(window.0, |f| f.set(CursorFlags::HIDDEN, hide))
|
||||
.map_err(|e| e.to_string());
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
rx.recv().unwrap().ok();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
self.window_state.lock().dpi_factor
|
||||
}
|
||||
|
||||
fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), String> {
|
||||
let mut point = POINT { x, y };
|
||||
unsafe {
|
||||
if winuser::ClientToScreen(self.window.0, &mut point) == 0 {
|
||||
return Err("`ClientToScreen` failed".to_owned());
|
||||
}
|
||||
if winuser::SetCursorPos(point.x, point.y) == 0 {
|
||||
return Err("`SetCursorPos` failed".to_owned());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, logical_position: LogicalPosition) -> Result<(), String> {
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
let (x, y) = logical_position.to_physical(dpi_factor).into();
|
||||
self.set_cursor_position_physical(x, y)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
WindowId(self.window.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
let window = self.window.clone();
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
WindowState::set_window_flags(
|
||||
window_state.lock(),
|
||||
window.0,
|
||||
None,
|
||||
|f| f.set(WindowFlags::MAXIMIZED, maximized),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
||||
unsafe {
|
||||
let window = self.window.clone();
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
match &monitor {
|
||||
&Some(RootMonitorHandle { ref inner }) => {
|
||||
let (x, y): (i32, i32) = inner.get_position().into();
|
||||
let (width, height): (u32, u32) = inner.get_dimensions().into();
|
||||
|
||||
let mut monitor = monitor.clone();
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let mut window_state_lock = window_state.lock();
|
||||
|
||||
let client_rect = util::get_client_rect(window.0).expect("get client rect failed!");
|
||||
window_state_lock.saved_window = Some(SavedWindow {
|
||||
client_rect,
|
||||
dpi_factor: window_state_lock.dpi_factor
|
||||
});
|
||||
|
||||
window_state_lock.fullscreen = monitor.take();
|
||||
WindowState::refresh_window_state(
|
||||
window_state_lock,
|
||||
window.0,
|
||||
Some(RECT {
|
||||
left: x,
|
||||
top: y,
|
||||
right: x + width as c_int,
|
||||
bottom: y + height as c_int,
|
||||
})
|
||||
);
|
||||
|
||||
mark_fullscreen(window.0, true);
|
||||
});
|
||||
}
|
||||
&None => {
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let mut window_state_lock = window_state.lock();
|
||||
window_state_lock.fullscreen = None;
|
||||
|
||||
if let Some(SavedWindow{client_rect, dpi_factor}) = window_state_lock.saved_window {
|
||||
window_state_lock.dpi_factor = dpi_factor;
|
||||
window_state_lock.saved_window = None;
|
||||
|
||||
WindowState::refresh_window_state(
|
||||
window_state_lock,
|
||||
window.0,
|
||||
Some(client_rect)
|
||||
);
|
||||
}
|
||||
|
||||
mark_fullscreen(window.0, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
let window = self.window.clone();
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let client_rect = util::get_client_rect(window.0).expect("get client rect failed!");
|
||||
WindowState::set_window_flags(
|
||||
window_state.lock(),
|
||||
window.0,
|
||||
Some(client_rect),
|
||||
|f| f.set(WindowFlags::DECORATIONS, decorations),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_always_on_top(&self, always_on_top: bool) {
|
||||
let window = self.window.clone();
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
WindowState::set_window_flags(
|
||||
window_state.lock(),
|
||||
window.0,
|
||||
None,
|
||||
|f| f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> RootMonitorHandle {
|
||||
RootMonitorHandle {
|
||||
inner: monitor::get_current_monitor(self.window.0),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_icon(&self, mut window_icon: Option<Icon>) {
|
||||
let window_icon = window_icon
|
||||
.take()
|
||||
.map(|icon| WinIcon::from_icon(icon).expect("Failed to create `ICON_SMALL`"));
|
||||
if let Some(ref window_icon) = window_icon {
|
||||
window_icon.set_for_window(self.window.0, IconType::Small);
|
||||
} else {
|
||||
icon::unset_for_window(self.window.0, IconType::Small);
|
||||
}
|
||||
self.window_state.lock().window_icon = window_icon;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_taskbar_icon(&self, mut taskbar_icon: Option<Icon>) {
|
||||
let taskbar_icon = taskbar_icon
|
||||
.take()
|
||||
.map(|icon| WinIcon::from_icon(icon).expect("Failed to create `ICON_BIG`"));
|
||||
if let Some(ref taskbar_icon) = taskbar_icon {
|
||||
taskbar_icon.set_for_window(self.window.0, IconType::Big);
|
||||
} else {
|
||||
icon::unset_for_window(self.window.0, IconType::Big);
|
||||
}
|
||||
self.window_state.lock().taskbar_icon = taskbar_icon;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// The window must be destroyed from the same thread that created it, so we send a
|
||||
// custom message to be handled by our callback to do the actual work.
|
||||
winuser::PostMessageW(self.window.0, *DESTROY_MSG_ID, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple non-owning wrapper around a window.
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone)]
|
||||
pub struct WindowWrapper(HWND);
|
||||
|
||||
// Send and Sync are not implemented for HWND and HDC, we have to wrap it and implement them manually.
|
||||
// For more info see:
|
||||
// https://github.com/retep998/winapi-rs/issues/360
|
||||
// https://github.com/retep998/winapi-rs/issues/396
|
||||
unsafe impl Sync for WindowWrapper {}
|
||||
unsafe impl Send for WindowWrapper {}
|
||||
|
||||
pub unsafe fn adjust_size(
|
||||
physical_size: PhysicalSize,
|
||||
style: DWORD,
|
||||
ex_style: DWORD,
|
||||
) -> (LONG, LONG) {
|
||||
let (width, height): (u32, u32) = physical_size.into();
|
||||
let mut rect = RECT {
|
||||
left: 0,
|
||||
right: width as LONG,
|
||||
top: 0,
|
||||
bottom: height as LONG,
|
||||
};
|
||||
winuser::AdjustWindowRectEx(&mut rect, style, 0, ex_style);
|
||||
(rect.right - rect.left, rect.bottom - rect.top)
|
||||
}
|
||||
|
||||
unsafe fn init<T: 'static>(
|
||||
mut attributes: WindowAttributes,
|
||||
mut pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
||||
event_loop: &EventLoopWindowTarget<T>,
|
||||
) -> Result<Window, CreationError> {
|
||||
let title = OsStr::new(&attributes.title)
|
||||
.encode_wide()
|
||||
.chain(Some(0).into_iter())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let window_icon = {
|
||||
let icon = attributes.window_icon
|
||||
.take()
|
||||
.map(WinIcon::from_icon);
|
||||
if icon.is_some() {
|
||||
Some(icon.unwrap().map_err(|err| {
|
||||
CreationError::OsError(format!("Failed to create `ICON_SMALL`: {:?}", err))
|
||||
})?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let taskbar_icon = {
|
||||
let icon = pl_attribs.taskbar_icon
|
||||
.take()
|
||||
.map(WinIcon::from_icon);
|
||||
if icon.is_some() {
|
||||
Some(icon.unwrap().map_err(|err| {
|
||||
CreationError::OsError(format!("Failed to create `ICON_BIG`: {:?}", err))
|
||||
})?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
// registering the window class
|
||||
let class_name = register_window_class(&window_icon, &taskbar_icon);
|
||||
|
||||
let guessed_dpi_factor = {
|
||||
let monitors = monitor::get_available_monitors();
|
||||
let dpi_factor = if !monitors.is_empty() {
|
||||
let mut dpi_factor = Some(monitors[0].get_hidpi_factor());
|
||||
for monitor in &monitors {
|
||||
if Some(monitor.get_hidpi_factor()) != dpi_factor {
|
||||
dpi_factor = None;
|
||||
}
|
||||
}
|
||||
dpi_factor
|
||||
} else {
|
||||
return Err(CreationError::OsError(format!("No monitors were detected.")));
|
||||
};
|
||||
dpi_factor.unwrap_or_else(|| {
|
||||
util::get_cursor_pos()
|
||||
.and_then(|cursor_pos| {
|
||||
let mut dpi_factor = None;
|
||||
for monitor in &monitors {
|
||||
if monitor.contains_point(&cursor_pos) {
|
||||
dpi_factor = Some(monitor.get_hidpi_factor());
|
||||
break;
|
||||
}
|
||||
}
|
||||
dpi_factor
|
||||
})
|
||||
.unwrap_or(1.0)
|
||||
})
|
||||
};
|
||||
info!("Guessed window DPI factor: {}", guessed_dpi_factor);
|
||||
|
||||
let dimensions = attributes.dimensions.unwrap_or_else(|| (1024, 768).into());
|
||||
|
||||
let mut window_flags = WindowFlags::empty();
|
||||
window_flags.set(WindowFlags::DECORATIONS, attributes.decorations);
|
||||
window_flags.set(WindowFlags::ALWAYS_ON_TOP, attributes.always_on_top);
|
||||
window_flags.set(WindowFlags::NO_BACK_BUFFER, pl_attribs.no_redirection_bitmap);
|
||||
window_flags.set(WindowFlags::TRANSPARENT, attributes.transparent);
|
||||
// WindowFlags::VISIBLE and MAXIMIZED are set down below after the window has been configured.
|
||||
window_flags.set(WindowFlags::RESIZABLE, attributes.resizable);
|
||||
window_flags.set(WindowFlags::CHILD, pl_attribs.parent.is_some());
|
||||
window_flags.set(WindowFlags::ON_TASKBAR, true);
|
||||
|
||||
// creating the real window this time, by using the functions in `extra_functions`
|
||||
let real_window = {
|
||||
let (style, ex_style) = window_flags.to_window_styles();
|
||||
let handle = winuser::CreateWindowExW(
|
||||
ex_style,
|
||||
class_name.as_ptr(),
|
||||
title.as_ptr() as LPCWSTR,
|
||||
style,
|
||||
winuser::CW_USEDEFAULT, winuser::CW_USEDEFAULT,
|
||||
winuser::CW_USEDEFAULT, winuser::CW_USEDEFAULT,
|
||||
pl_attribs.parent.unwrap_or(ptr::null_mut()),
|
||||
ptr::null_mut(),
|
||||
libloaderapi::GetModuleHandleW(ptr::null()),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
|
||||
if handle.is_null() {
|
||||
return Err(CreationError::OsError(format!("CreateWindowEx function failed: {}",
|
||||
format!("{}", io::Error::last_os_error()))));
|
||||
}
|
||||
|
||||
winuser::SetWindowLongW(handle, winuser::GWL_STYLE, 0);
|
||||
winuser::SetWindowLongW(handle, winuser::GWL_EXSTYLE, 0);
|
||||
|
||||
WindowWrapper(handle)
|
||||
};
|
||||
|
||||
// Set up raw input
|
||||
register_all_mice_and_keyboards_for_raw_input(real_window.0);
|
||||
|
||||
// Register for touch events if applicable
|
||||
{
|
||||
let digitizer = winuser::GetSystemMetrics( winuser::SM_DIGITIZER ) as u32;
|
||||
if digitizer & winuser::NID_READY != 0 {
|
||||
winuser::RegisterTouchWindow( real_window.0, winuser::TWF_WANTPALM );
|
||||
}
|
||||
}
|
||||
|
||||
let dpi = get_hwnd_dpi(real_window.0);
|
||||
let dpi_factor = dpi_to_scale_factor(dpi);
|
||||
if dpi_factor != guessed_dpi_factor {
|
||||
let (width, height): (u32, u32) = dimensions.into();
|
||||
let mut packed_dimensions = 0;
|
||||
// MAKELPARAM isn't provided by winapi yet.
|
||||
let ptr = &mut packed_dimensions as *mut LPARAM as *mut WORD;
|
||||
*ptr.offset(0) = width as WORD;
|
||||
*ptr.offset(1) = height as WORD;
|
||||
winuser::PostMessageW(
|
||||
real_window.0,
|
||||
*INITIAL_DPI_MSG_ID,
|
||||
dpi as WPARAM,
|
||||
packed_dimensions,
|
||||
);
|
||||
}
|
||||
|
||||
// making the window transparent
|
||||
if attributes.transparent && !pl_attribs.no_redirection_bitmap {
|
||||
let region = CreateRectRgn(0, 0, -1, -1); // makes the window transparent
|
||||
|
||||
let bb = dwmapi::DWM_BLURBEHIND {
|
||||
dwFlags: dwmapi::DWM_BB_ENABLE | dwmapi::DWM_BB_BLURREGION,
|
||||
fEnable: 1,
|
||||
hRgnBlur: region,
|
||||
fTransitionOnMaximized: 0,
|
||||
};
|
||||
|
||||
dwmapi::DwmEnableBlurBehindWindow(real_window.0, &bb);
|
||||
DeleteObject(region as _);
|
||||
|
||||
if attributes.decorations {
|
||||
// HACK: When opaque (opacity 255), there is a trail whenever
|
||||
// the transparent window is moved. By reducing it to 254,
|
||||
// the window is rendered properly.
|
||||
let opacity = 254;
|
||||
|
||||
// The color key can be any value except for black (0x0).
|
||||
let color_key = 0x0030c100;
|
||||
|
||||
winuser::SetLayeredWindowAttributes(real_window.0, color_key, opacity, winuser::LWA_ALPHA);
|
||||
}
|
||||
}
|
||||
|
||||
window_flags.set(WindowFlags::VISIBLE, attributes.visible);
|
||||
window_flags.set(WindowFlags::MAXIMIZED, attributes.maximized);
|
||||
|
||||
let window_state = {
|
||||
let mut window_state = WindowState::new(
|
||||
&attributes,
|
||||
window_icon,
|
||||
taskbar_icon,
|
||||
dpi_factor,
|
||||
);
|
||||
let window_state = Arc::new(Mutex::new(window_state));
|
||||
WindowState::set_window_flags(
|
||||
window_state.lock(),
|
||||
real_window.0,
|
||||
None,
|
||||
|f| *f = window_flags,
|
||||
);
|
||||
window_state
|
||||
};
|
||||
|
||||
let win = Window {
|
||||
window: real_window,
|
||||
window_state,
|
||||
thread_executor: event_loop.create_thread_executor(),
|
||||
};
|
||||
|
||||
if let Some(_) = attributes.fullscreen {
|
||||
win.set_fullscreen(attributes.fullscreen);
|
||||
force_window_active(win.window.0);
|
||||
}
|
||||
|
||||
if let Some(dimensions) = attributes.dimensions {
|
||||
win.set_inner_size(dimensions);
|
||||
}
|
||||
|
||||
Ok(win)
|
||||
}
|
||||
|
||||
unsafe fn register_window_class(
|
||||
window_icon: &Option<WinIcon>,
|
||||
taskbar_icon: &Option<WinIcon>,
|
||||
) -> Vec<u16> {
|
||||
let class_name: Vec<_> = OsStr::new("Window Class")
|
||||
.encode_wide()
|
||||
.chain(Some(0).into_iter())
|
||||
.collect();
|
||||
|
||||
let h_icon = taskbar_icon
|
||||
.as_ref()
|
||||
.map(|icon| icon.handle)
|
||||
.unwrap_or(ptr::null_mut());
|
||||
let h_icon_small = window_icon
|
||||
.as_ref()
|
||||
.map(|icon| icon.handle)
|
||||
.unwrap_or(ptr::null_mut());
|
||||
|
||||
let class = winuser::WNDCLASSEXW {
|
||||
cbSize: mem::size_of::<winuser::WNDCLASSEXW>() as UINT,
|
||||
style: winuser::CS_HREDRAW | winuser::CS_VREDRAW | winuser::CS_OWNDC,
|
||||
lpfnWndProc: Some(winuser::DefWindowProcW),
|
||||
cbClsExtra: 0,
|
||||
cbWndExtra: 0,
|
||||
hInstance: libloaderapi::GetModuleHandleW(ptr::null()),
|
||||
hIcon: h_icon,
|
||||
hCursor: ptr::null_mut(), // must be null in order for cursor state to work properly
|
||||
hbrBackground: ptr::null_mut(),
|
||||
lpszMenuName: ptr::null(),
|
||||
lpszClassName: class_name.as_ptr(),
|
||||
hIconSm: h_icon_small,
|
||||
};
|
||||
|
||||
// We ignore errors because registering the same window class twice would trigger
|
||||
// an error, and because errors here are detected during CreateWindowEx anyway.
|
||||
// Also since there is no weird element in the struct, there is no reason for this
|
||||
// call to fail.
|
||||
winuser::RegisterClassExW(&class);
|
||||
|
||||
class_name
|
||||
}
|
||||
|
||||
struct ComInitialized(*mut ());
|
||||
impl Drop for ComInitialized {
|
||||
fn drop(&mut self) {
|
||||
unsafe { combaseapi::CoUninitialize() };
|
||||
}
|
||||
}
|
||||
|
||||
thread_local!{
|
||||
static COM_INITIALIZED: ComInitialized = {
|
||||
unsafe {
|
||||
combaseapi::CoInitializeEx(ptr::null_mut(), COINIT_APARTMENTTHREADED);
|
||||
ComInitialized(ptr::null_mut())
|
||||
}
|
||||
};
|
||||
|
||||
static TASKBAR_LIST: Cell<*mut ITaskbarList2> = Cell::new(ptr::null_mut());
|
||||
}
|
||||
|
||||
pub fn com_initialized() {
|
||||
COM_INITIALIZED.with(|_| {});
|
||||
}
|
||||
|
||||
// Reference Implementation:
|
||||
// https://github.com/chromium/chromium/blob/f18e79d901f56154f80eea1e2218544285e62623/ui/views/win/fullscreen_handler.cc
|
||||
//
|
||||
// As per MSDN marking the window as fullscreen should ensure that the
|
||||
// taskbar is moved to the bottom of the Z-order when the fullscreen window
|
||||
// is activated. If the window is not fullscreen, the Shell falls back to
|
||||
// heuristics to determine how the window should be treated, which means
|
||||
// that it could still consider the window as fullscreen. :(
|
||||
unsafe fn mark_fullscreen(handle: HWND, fullscreen: bool) {
|
||||
com_initialized();
|
||||
|
||||
TASKBAR_LIST.with(|task_bar_list_ptr| {
|
||||
let mut task_bar_list = task_bar_list_ptr.get();
|
||||
|
||||
if task_bar_list == ptr::null_mut() {
|
||||
use winapi::shared::winerror::S_OK;
|
||||
use winapi::Interface;
|
||||
|
||||
let hr = combaseapi::CoCreateInstance(
|
||||
&CLSID_TaskbarList,
|
||||
ptr::null_mut(),
|
||||
combaseapi::CLSCTX_ALL,
|
||||
&ITaskbarList2::uuidof(),
|
||||
&mut task_bar_list as *mut _ as *mut _,
|
||||
);
|
||||
|
||||
if hr != S_OK || (*task_bar_list).HrInit() != S_OK {
|
||||
// In some old windows, the taskbar object could not be created, we just ignore it
|
||||
return;
|
||||
}
|
||||
task_bar_list_ptr.set(task_bar_list)
|
||||
}
|
||||
|
||||
task_bar_list = task_bar_list_ptr.get();
|
||||
(*task_bar_list).MarkFullscreenWindow(handle, if fullscreen { 1 } else { 0 });
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn force_window_active(handle: HWND) {
|
||||
// In some situation, calling SetForegroundWindow could not bring up the window,
|
||||
// This is a little hack which can "steal" the foreground window permission
|
||||
// We only call this function in the window creation, so it should be fine.
|
||||
// See : https://stackoverflow.com/questions/10740346/setforegroundwindow-only-working-while-visual-studio-is-open
|
||||
let alt_sc = winuser::MapVirtualKeyW(winuser::VK_MENU as _, winuser::MAPVK_VK_TO_VSC);
|
||||
|
||||
let mut inputs: [winuser::INPUT; 2] = mem::zeroed();
|
||||
inputs[0].type_ = winuser::INPUT_KEYBOARD;
|
||||
inputs[0].u.ki_mut().wVk = winuser::VK_LMENU as _;
|
||||
inputs[0].u.ki_mut().wScan = alt_sc as _;
|
||||
inputs[0].u.ki_mut().dwFlags = winuser::KEYEVENTF_EXTENDEDKEY;
|
||||
|
||||
inputs[1].type_ = winuser::INPUT_KEYBOARD;
|
||||
inputs[1].u.ki_mut().wVk = winuser::VK_LMENU as _;
|
||||
inputs[1].u.ki_mut().wScan = alt_sc as _;
|
||||
inputs[1].u.ki_mut().dwFlags = winuser::KEYEVENTF_EXTENDEDKEY | winuser::KEYEVENTF_KEYUP;
|
||||
|
||||
// Simulate a key press and release
|
||||
winuser::SendInput(
|
||||
inputs.len() as _,
|
||||
inputs.as_mut_ptr(),
|
||||
mem::size_of::<winuser::INPUT>() as _,
|
||||
);
|
||||
|
||||
winuser::SetForegroundWindow(handle);
|
||||
}
|
||||
332
src/platform_impl/windows/window_state.rs
Normal file
332
src/platform_impl/windows/window_state.rs
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
use monitor::MonitorHandle;
|
||||
use window::{MouseCursor, WindowAttributes};
|
||||
use std::{io, ptr};
|
||||
use parking_lot::MutexGuard;
|
||||
use dpi::LogicalSize;
|
||||
use platform_impl::platform::{util, event_loop};
|
||||
use platform_impl::platform::icon::WinIcon;
|
||||
use winapi::shared::windef::{RECT, HWND};
|
||||
use winapi::shared::minwindef::DWORD;
|
||||
use winapi::um::winuser;
|
||||
|
||||
/// Contains information about states and the window that the callback is going to use.
|
||||
#[derive(Clone)]
|
||||
pub struct WindowState {
|
||||
pub mouse: MouseProperties,
|
||||
|
||||
/// Used by `WM_GETMINMAXINFO`.
|
||||
pub min_size: Option<LogicalSize>,
|
||||
pub max_size: Option<LogicalSize>,
|
||||
|
||||
pub window_icon: Option<WinIcon>,
|
||||
pub taskbar_icon: Option<WinIcon>,
|
||||
|
||||
pub saved_window: Option<SavedWindow>,
|
||||
pub dpi_factor: f64,
|
||||
|
||||
pub fullscreen: Option<MonitorHandle>,
|
||||
window_flags: WindowFlags,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SavedWindow {
|
||||
pub client_rect: RECT,
|
||||
pub dpi_factor: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MouseProperties {
|
||||
pub cursor: MouseCursor,
|
||||
pub buttons_down: u32,
|
||||
cursor_flags: CursorFlags,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct CursorFlags: u8 {
|
||||
const GRABBED = 1 << 0;
|
||||
const HIDDEN = 1 << 1;
|
||||
const IN_WINDOW = 1 << 2;
|
||||
}
|
||||
}
|
||||
bitflags! {
|
||||
pub struct WindowFlags: u32 {
|
||||
const RESIZABLE = 1 << 0;
|
||||
const DECORATIONS = 1 << 1;
|
||||
const VISIBLE = 1 << 2;
|
||||
const ON_TASKBAR = 1 << 3;
|
||||
const ALWAYS_ON_TOP = 1 << 4;
|
||||
const NO_BACK_BUFFER = 1 << 5;
|
||||
const TRANSPARENT = 1 << 6;
|
||||
const CHILD = 1 << 7;
|
||||
const MAXIMIZED = 1 << 8;
|
||||
|
||||
/// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is
|
||||
/// included here to make masking easier.
|
||||
const MARKER_FULLSCREEN = 1 << 9;
|
||||
|
||||
/// The `WM_SIZE` event contains some parameters that can effect the state of `WindowFlags`.
|
||||
/// In most cases, it's okay to let those parameters change the state. However, when we're
|
||||
/// running the `WindowFlags::apply_diff` function, we *don't* want those parameters to
|
||||
/// effect our stored state, because the purpose of `apply_diff` is to update the actual
|
||||
/// window's state to match our stored state. This controls whether to accept those changes.
|
||||
const MARKER_RETAIN_STATE_ON_SIZE = 1 << 10;
|
||||
|
||||
const FULLSCREEN_AND_MASK = !(
|
||||
WindowFlags::DECORATIONS.bits |
|
||||
WindowFlags::RESIZABLE.bits |
|
||||
WindowFlags::MAXIMIZED.bits
|
||||
);
|
||||
const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits;
|
||||
const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits;
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
pub fn new(
|
||||
attributes: &WindowAttributes,
|
||||
window_icon: Option<WinIcon>,
|
||||
taskbar_icon: Option<WinIcon>,
|
||||
dpi_factor: f64
|
||||
) -> WindowState {
|
||||
WindowState {
|
||||
mouse: MouseProperties {
|
||||
cursor: MouseCursor::default(),
|
||||
buttons_down: 0,
|
||||
cursor_flags: CursorFlags::empty(),
|
||||
},
|
||||
|
||||
min_size: attributes.min_dimensions,
|
||||
max_size: attributes.max_dimensions,
|
||||
|
||||
window_icon,
|
||||
taskbar_icon,
|
||||
|
||||
saved_window: None,
|
||||
dpi_factor,
|
||||
|
||||
fullscreen: None,
|
||||
window_flags: WindowFlags::empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_flags(&self) -> WindowFlags {
|
||||
self.window_flags
|
||||
}
|
||||
|
||||
pub fn set_window_flags<F>(mut this: MutexGuard<Self>, window: HWND, set_client_rect: Option<RECT>, f: F)
|
||||
where F: FnOnce(&mut WindowFlags)
|
||||
{
|
||||
let old_flags = this.window_flags;
|
||||
f(&mut this.window_flags);
|
||||
|
||||
let is_fullscreen = this.fullscreen.is_some();
|
||||
this.window_flags.set(WindowFlags::MARKER_FULLSCREEN, is_fullscreen);
|
||||
let new_flags = this.window_flags;
|
||||
|
||||
drop(this);
|
||||
old_flags.apply_diff(window, new_flags, set_client_rect);
|
||||
}
|
||||
|
||||
pub fn refresh_window_state(this: MutexGuard<Self>, window: HWND, set_client_rect: Option<RECT>) {
|
||||
Self::set_window_flags(this, window, set_client_rect, |_| ());
|
||||
}
|
||||
|
||||
pub fn set_window_flags_in_place<F>(&mut self, f: F)
|
||||
where F: FnOnce(&mut WindowFlags)
|
||||
{
|
||||
f(&mut self.window_flags);
|
||||
}
|
||||
}
|
||||
|
||||
impl MouseProperties {
|
||||
pub fn cursor_flags(&self) -> CursorFlags {
|
||||
self.cursor_flags
|
||||
}
|
||||
|
||||
pub fn set_cursor_flags<F>(&mut self, window: HWND, f: F) -> Result<(), io::Error>
|
||||
where F: FnOnce(&mut CursorFlags)
|
||||
{
|
||||
let old_flags = self.cursor_flags;
|
||||
f(&mut self.cursor_flags);
|
||||
match self.cursor_flags.refresh_os_cursor(window) {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
self.cursor_flags = old_flags;
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowFlags {
|
||||
fn mask(mut self) -> WindowFlags {
|
||||
if self.contains(WindowFlags::MARKER_FULLSCREEN) {
|
||||
self &= WindowFlags::FULLSCREEN_AND_MASK;
|
||||
}
|
||||
if !self.contains(WindowFlags::VISIBLE) {
|
||||
self &= WindowFlags::INVISIBLE_AND_MASK;
|
||||
}
|
||||
if !self.contains(WindowFlags::DECORATIONS) {
|
||||
self &= WindowFlags::NO_DECORATIONS_AND_MASK;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to_window_styles(self) -> (DWORD, DWORD) {
|
||||
use winapi::um::winuser::*;
|
||||
|
||||
let (mut style, mut style_ex) = (0, 0);
|
||||
|
||||
if self.contains(WindowFlags::RESIZABLE) {
|
||||
style |= WS_SIZEBOX | WS_MAXIMIZEBOX;
|
||||
}
|
||||
if self.contains(WindowFlags::DECORATIONS) {
|
||||
style |= WS_CAPTION | WS_MINIMIZEBOX | WS_BORDER;
|
||||
style_ex = WS_EX_WINDOWEDGE;
|
||||
}
|
||||
if self.contains(WindowFlags::VISIBLE) {
|
||||
style |= WS_VISIBLE;
|
||||
}
|
||||
if self.contains(WindowFlags::ON_TASKBAR) {
|
||||
style_ex |= WS_EX_APPWINDOW;
|
||||
}
|
||||
if self.contains(WindowFlags::ALWAYS_ON_TOP) {
|
||||
style_ex |= WS_EX_TOPMOST;
|
||||
}
|
||||
if self.contains(WindowFlags::NO_BACK_BUFFER) {
|
||||
style_ex |= WS_EX_NOREDIRECTIONBITMAP;
|
||||
}
|
||||
if self.contains(WindowFlags::TRANSPARENT) {
|
||||
// Is this necessary? The docs say that WS_EX_LAYERED requires a windows class without
|
||||
// CS_OWNDC, and Winit windows have that flag set.
|
||||
style_ex |= WS_EX_LAYERED;
|
||||
}
|
||||
if self.contains(WindowFlags::CHILD) {
|
||||
style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually.
|
||||
}
|
||||
if self.contains(WindowFlags::MAXIMIZED) {
|
||||
style |= WS_MAXIMIZE;
|
||||
}
|
||||
|
||||
style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU;
|
||||
style_ex |= WS_EX_ACCEPTFILES;
|
||||
|
||||
(style, style_ex)
|
||||
}
|
||||
|
||||
/// Adjust the window client rectangle to the return value, if present.
|
||||
fn apply_diff(mut self, window: HWND, mut new: WindowFlags, set_client_rect: Option<RECT>) {
|
||||
self = self.mask();
|
||||
new = new.mask();
|
||||
|
||||
let diff = self ^ new;
|
||||
if diff == WindowFlags::empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if diff.contains(WindowFlags::VISIBLE) {
|
||||
unsafe {
|
||||
winuser::ShowWindow(
|
||||
window,
|
||||
match new.contains(WindowFlags::VISIBLE) {
|
||||
true => winuser::SW_SHOW,
|
||||
false => winuser::SW_HIDE
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
if diff.contains(WindowFlags::ALWAYS_ON_TOP) {
|
||||
unsafe {
|
||||
winuser::SetWindowPos(
|
||||
window,
|
||||
match new.contains(WindowFlags::ALWAYS_ON_TOP) {
|
||||
true => winuser::HWND_TOPMOST,
|
||||
false => winuser::HWND_NOTOPMOST,
|
||||
},
|
||||
0, 0, 0, 0,
|
||||
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOMOVE | winuser::SWP_NOSIZE,
|
||||
);
|
||||
winuser::UpdateWindow(window);
|
||||
}
|
||||
}
|
||||
|
||||
if diff.contains(WindowFlags::MAXIMIZED) || new.contains(WindowFlags::MAXIMIZED) {
|
||||
unsafe {
|
||||
winuser::ShowWindow(
|
||||
window,
|
||||
match new.contains(WindowFlags::MAXIMIZED) {
|
||||
true => winuser::SW_MAXIMIZE,
|
||||
false => winuser::SW_RESTORE
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if diff != WindowFlags::empty() {
|
||||
let (style, style_ex) = new.to_window_styles();
|
||||
|
||||
unsafe {
|
||||
winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 1, 0);
|
||||
|
||||
winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _);
|
||||
winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _);
|
||||
|
||||
match set_client_rect.and_then(|r| util::adjust_window_rect_with_styles(window, style, style_ex, r)) {
|
||||
Some(client_rect) => {
|
||||
let (x, y, w, h) = (
|
||||
client_rect.left,
|
||||
client_rect.top,
|
||||
client_rect.right - client_rect.left,
|
||||
client_rect.bottom - client_rect.top,
|
||||
);
|
||||
winuser::SetWindowPos(
|
||||
window,
|
||||
ptr::null_mut(),
|
||||
x, y, w, h,
|
||||
winuser::SWP_NOZORDER
|
||||
| winuser::SWP_FRAMECHANGED,
|
||||
);
|
||||
},
|
||||
None => {
|
||||
// Refresh the window frame.
|
||||
winuser::SetWindowPos(
|
||||
window,
|
||||
ptr::null_mut(),
|
||||
0, 0, 0, 0,
|
||||
winuser::SWP_NOZORDER
|
||||
| winuser::SWP_NOMOVE
|
||||
| winuser::SWP_NOSIZE
|
||||
| winuser::SWP_FRAMECHANGED,
|
||||
);
|
||||
}
|
||||
}
|
||||
winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CursorFlags {
|
||||
fn refresh_os_cursor(self, window: HWND) -> Result<(), io::Error> {
|
||||
let client_rect = util::get_client_rect(window)?;
|
||||
|
||||
if util::is_focused(window) {
|
||||
if self.contains(CursorFlags::GRABBED) {
|
||||
util::set_cursor_clip(Some(client_rect))?;
|
||||
} else {
|
||||
util::set_cursor_clip(None)?;
|
||||
}
|
||||
}
|
||||
|
||||
let cursor_in_client = self.contains(CursorFlags::IN_WINDOW);
|
||||
if cursor_in_client {
|
||||
util::set_cursor_hidden(self.contains(CursorFlags::HIDDEN));
|
||||
} else {
|
||||
util::set_cursor_hidden(false);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue