druid_win_shell/
util.rs

1// Copyright 2017 The xi-editor Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Various utilities for working with windows. Includes utilities for converting between Windows 
16//! and Rust types, including strings. 
17//! Also includes some code to dynamically load functions at runtime. This is needed for functions
18//! which are only supported on certain versions of windows.
19
20use std::ffi::{OsStr, OsString, CString};
21use std::fmt;
22use std::mem;
23use std::os::windows::ffi::{OsStrExt, OsStringExt};
24use std::ptr;
25use std::slice;
26use winapi::ctypes::c_void;
27use winapi::shared::guiddef::REFIID;
28use winapi::shared::minwindef::*;
29use winapi::shared::ntdef::*;
30use winapi::shared::windef::*;
31use winapi::shared::winerror::SUCCEEDED;
32use winapi::um::fileapi::*;
33use winapi::um::handleapi::*;
34use winapi::um::libloaderapi::*;
35use winapi::um::processenv::*;
36use winapi::um::shellscalingapi::*;
37use winapi::um::unknwnbase::IUnknown;
38use winapi::um::winbase::*;
39use winapi::um::wincon::*;
40// This needs to be explicit, otherwise HRESULT will conflict
41use winapi::um::winnt::{GENERIC_READ, GENERIC_WRITE, FILE_SHARE_WRITE};
42
43use direct2d::enums::DrawTextOptions;
44
45/// Error codes. At the moment, this is little more than HRESULT, but that
46/// might change.
47pub enum Error {
48    Null,
49    Hr(HRESULT),
50    // Maybe include the full error from the direct2d crate.
51    D2Error,
52    /// A function is available on newer version of windows.
53    OldWindows,
54}
55
56impl fmt::Debug for Error {
57    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
58        match *self {
59            Error::Null => write!(f, "Null error"),
60            Error::Hr(hr) => write!(f, "HRESULT 0x{:x}", hr),
61            Error::D2Error => write!(f, "Direct2D error"),
62            Error::OldWindows => write!(f, "Attempted newer API on older Windows"),
63        }
64    }
65}
66
67pub fn as_result(hr: HRESULT) -> Result<(), Error> {
68    if SUCCEEDED(hr) {
69        Ok(())
70    } else {
71        Err(Error::Hr(hr))
72    }
73}
74
75impl From<HRESULT> for Error {
76    fn from(hr: HRESULT) -> Error {
77        Error::Hr(hr)
78    }
79}
80
81pub trait ToWide {
82    fn to_wide_sized(&self) -> Vec<u16>;
83    fn to_wide(&self) -> Vec<u16>;
84}
85
86impl<T> ToWide for T where T: AsRef<OsStr> {
87    fn to_wide_sized(&self) -> Vec<u16> {
88        self.as_ref().encode_wide().collect()
89    }
90    fn to_wide(&self) -> Vec<u16> {
91        self.as_ref().encode_wide().chain(Some(0)).collect()
92    }
93}
94
95pub trait FromWide {
96    fn to_u16_slice(&self) -> &[u16];
97
98    fn to_os_string(&self) -> OsString {
99        OsStringExt::from_wide(self.to_u16_slice())
100    }
101
102    fn from_wide(&self) -> Option<String> {
103        String::from_utf16(self.to_u16_slice()).ok()
104    }
105}
106
107impl FromWide for LPWSTR {
108    fn to_u16_slice(&self) -> &[u16] {
109        unsafe {
110            let mut len = 0;
111            while *self.offset(len) != 0 {
112                len += 1;
113            }
114            slice::from_raw_parts(*self, len as usize)
115        }
116    }
117}
118
119impl FromWide for [u16] {
120    fn to_u16_slice(&self) -> &[u16] { self }
121}
122
123// Types for functions we want to load, which are only supported on newer windows versions
124// from shcore.dll
125type GetDpiForSystem = unsafe extern "system" fn() -> UINT;
126type GetDpiForMonitor = unsafe extern "system" fn(HMONITOR, MONITOR_DPI_TYPE, *mut UINT, *mut UINT);
127// from user32.dll
128type SetProcessDpiAwareness = unsafe extern "system" fn(PROCESS_DPI_AWARENESS) -> HRESULT;
129type DCompositionCreateDevice2 = unsafe extern "system" fn(
130    renderingDevice: *const IUnknown,
131    iid: REFIID,
132    dcompositionDevice: *mut *mut c_void,
133) -> HRESULT;
134type CreateDXGIFactory2 = unsafe extern "system" fn(
135    Flags: UINT,
136    riid: REFIID,
137    ppFactory: *mut *mut c_void,
138) -> HRESULT;
139
140#[allow(non_snake_case)] // For member fields
141pub struct OptionalFunctions {
142    pub GetDpiForSystem: Option<GetDpiForSystem>,
143    pub GetDpiForMonitor: Option<GetDpiForMonitor>,
144    pub SetProcessDpiAwareness: Option<SetProcessDpiAwareness>,
145    pub DCompositionCreateDevice2: Option<DCompositionCreateDevice2>,
146    pub CreateDXGIFactory2: Option<CreateDXGIFactory2>,
147}
148
149#[allow(non_snake_case)] // For local variables
150fn load_optional_functions() -> OptionalFunctions { 
151    // Tries to load $function from $lib. $function should be one of the types defined just before 
152    // `load_optional_functions`. This sets the corresponding local field to `Some(function pointer)`
153    // if it manages to load the function.
154    macro_rules! load_function {
155        ($lib: expr, $function: ident, $min_windows_version: expr) => {{
156            let name = stringify!($function);
157
158            // The `unwrap` is fine, because we only call this macro for winapi functions, which
159            // have simple ascii-only names
160            let cstr = CString::new(name).unwrap();
161
162            let function_ptr = unsafe { GetProcAddress($lib, cstr.as_ptr()) };
163
164            if function_ptr.is_null() {
165                println!(
166                    "Could not load `{}`. Windows {} or later is needed",
167                    name, $min_windows_version
168                );
169            } else {
170                let function = unsafe { mem::transmute::<_, $function>(function_ptr) };
171                $function = Some(function);
172            }
173        }};
174    }
175
176    fn load_library(name: &str) -> HMODULE {
177        let encoded_name = name.to_wide();
178
179        // If we already have loaded the library (somewhere else in the process) we don't need to
180        // call LoadLibrary again
181        let library = unsafe { GetModuleHandleW(encoded_name.as_ptr()) };
182        if !library.is_null() {
183            return library;
184        }
185
186        let library = unsafe { LoadLibraryW(encoded_name.as_ptr()) };
187        return library;
188    }
189
190    let shcore = load_library("shcore.dll");
191    let user32 = load_library("user32.dll");
192    let dcomp = load_library("dcomp.dll");
193    let dxgi = load_library("dxgi.dll");
194
195    let mut GetDpiForSystem = None;
196    let mut GetDpiForMonitor = None;
197    let mut SetProcessDpiAwareness = None;
198    let mut DCompositionCreateDevice2 = None;
199    let mut CreateDXGIFactory2 = None;
200
201    if shcore.is_null() {
202        println!("No shcore.dll");
203    } else {
204        load_function!(shcore, SetProcessDpiAwareness, "8.1");
205        load_function!(shcore, GetDpiForMonitor, "8.1");
206    }
207
208    if user32.is_null() {
209        println!("No user32.dll");
210    } else {
211        load_function!(user32, GetDpiForSystem, "10");
212    }
213
214    if !dcomp.is_null() {
215        load_function!(dcomp, DCompositionCreateDevice2, "8.1");
216    }
217
218    if !dxgi.is_null() {
219        load_function!(dxgi, CreateDXGIFactory2, "8.1");
220    }
221
222    OptionalFunctions {
223        GetDpiForSystem,
224        GetDpiForMonitor,
225        SetProcessDpiAwareness,
226        DCompositionCreateDevice2,
227        CreateDXGIFactory2,
228    }
229}
230
231lazy_static! {
232    pub static ref OPTIONAL_FUNCTIONS: OptionalFunctions = load_optional_functions();
233}
234
235/// Initialize the app. At the moment, this is mostly needed for hi-dpi.
236pub fn init() {
237    attach_console();
238    if let Some(func) = OPTIONAL_FUNCTIONS.SetProcessDpiAwareness {
239        // This function is only supported on windows 10
240        unsafe {
241            func(PROCESS_SYSTEM_DPI_AWARE); // TODO: per monitor (much harder)
242        }
243    }
244}
245
246/// Determine a suitable default set of text options. Enables color fonts
247/// on systems that are capable of them (8.1 and above).
248pub fn default_text_options() -> DrawTextOptions {
249    // This is an arbitrary optional function that is 8.1 and above.
250    if OPTIONAL_FUNCTIONS.SetProcessDpiAwareness.is_some() {
251        DrawTextOptions::ENABLE_COLOR_FONT
252    } else {
253        DrawTextOptions::NONE
254    }
255}
256
257/// Convenience macro for defining accelerator tables.
258#[macro_export]
259macro_rules! accel {
260    ( $( $fVirt:expr, $key:expr, $cmd:expr, )* ) => {
261        [
262            $(
263                ACCEL { fVirt: $fVirt | FVIRTKEY, key: $key as WORD, cmd: $cmd as WORD },
264            )*
265        ]
266    }
267}
268
269/// Attach the process to the console of the parent process. This allows xi-win to
270/// correctly print to a console when run from powershell or cmd.
271/// If no console is available, allocate a new console.
272fn attach_console() {
273    unsafe {
274        let stdout = GetStdHandle(STD_OUTPUT_HANDLE);
275        if stdout != INVALID_HANDLE_VALUE && GetFileType(stdout) != FILE_TYPE_UNKNOWN {
276            // We already have a perfectly valid stdout and must not attach.
277            // This happens, for example in MingW consoles like git bash.
278            return;
279        }
280
281        if AttachConsole(ATTACH_PARENT_PROCESS) > 0 {
282            let chnd = CreateFileA(
283                CString::new("CONOUT$").unwrap().as_ptr(),
284                GENERIC_READ | GENERIC_WRITE,
285                FILE_SHARE_WRITE,
286                ptr::null_mut(),
287                OPEN_EXISTING,
288                0,
289                ptr::null_mut());
290
291            if chnd == INVALID_HANDLE_VALUE {
292                // CreateFileA failed.
293                return;
294            }
295
296            SetStdHandle(STD_OUTPUT_HANDLE, chnd);
297            SetStdHandle(STD_ERROR_HANDLE, chnd);
298        }
299    }
300}
301