Setup Windows UI with inert notification icon

This commit is contained in:
Antoine Gersant 2016-09-17 03:05:31 -07:00
parent b5dba126d6
commit a4f118d9e6
10 changed files with 302 additions and 23 deletions

3
.gitignore vendored
View file

@ -1,3 +1,4 @@
target
release
*.dll
*.dll
*.res

55
Cargo.lock generated
View file

@ -4,15 +4,20 @@ version = "0.1.0"
dependencies = [
"id3 0.1.10 (git+https://github.com/jameshurst/rust-id3)",
"iron 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"kernel32-sys 0.2.2 (git+https://github.com/retep998/winapi-rs)",
"mount 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"oven 1.0.0 (git+https://github.com/agersant/oven?branch=remove_cookie_dep)",
"params 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.1.77 (registry+https://github.com/rust-lang/crates.io-index)",
"router 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"shell32-sys 0.1.1 (git+https://github.com/retep998/winapi-rs)",
"staticfile 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"toml 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"user32-sys 0.2.0 (git+https://github.com/retep998/winapi-rs)",
"uuid 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (git+https://github.com/retep998/winapi-rs)",
]
[[package]]
@ -274,6 +279,15 @@ name = "itoa"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "kernel32-sys"
version = "0.2.2"
source = "git+https://github.com/retep998/winapi-rs#77588710a683e1a4ec085b0f3d6c8939ecc7f030"
dependencies = [
"winapi 0.2.8 (git+https://github.com/retep998/winapi-rs)",
"winapi-build 0.1.1 (git+https://github.com/retep998/winapi-rs)",
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
@ -671,6 +685,20 @@ dependencies = [
"serde 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "sha1"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "shell32-sys"
version = "0.1.1"
source = "git+https://github.com/retep998/winapi-rs#77588710a683e1a4ec085b0f3d6c8939ecc7f030"
dependencies = [
"winapi 0.2.8 (git+https://github.com/retep998/winapi-rs)",
"winapi-build 0.1.1 (git+https://github.com/retep998/winapi-rs)",
]
[[package]]
name = "solicit"
version = "0.4.4"
@ -819,6 +847,15 @@ dependencies = [
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "user32-sys"
version = "0.2.0"
source = "git+https://github.com/retep998/winapi-rs#77588710a683e1a4ec085b0f3d6c8939ecc7f030"
dependencies = [
"winapi 0.2.8 (git+https://github.com/retep998/winapi-rs)",
"winapi-build 0.1.1 (git+https://github.com/retep998/winapi-rs)",
]
[[package]]
name = "user32-sys"
version = "0.2.0"
@ -841,11 +878,29 @@ dependencies = [
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "uuid"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi"
version = "0.2.8"
source = "git+https://github.com/retep998/winapi-rs#77588710a683e1a4ec085b0f3d6c8939ecc7f030"
[[package]]
name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-build"
version = "0.1.1"
source = "git+https://github.com/retep998/winapi-rs#77588710a683e1a4ec085b0f3d6c8939ecc7f030"
[[package]]
name = "winapi-build"
version = "0.1.1"

View file

@ -3,6 +3,9 @@ name = "polaris"
version = "0.1.0"
authors = ["Antoine Gersant <antoine.gersant@lesforges.org>"]
[features]
ui = []
[dependencies]
id3 = { git = "https://github.com/jameshurst/rust-id3" }
iron = "0.4.0"
@ -14,4 +17,10 @@ router = "0.4.0"
rustc-serialize = "0.3"
staticfile = "0.3.0"
toml = "0.2"
url = "1.2.0"
url = "1.2.0"
winapi = { git = "https://github.com/retep998/winapi-rs" }
kernel32-sys = { git = "https://github.com/retep998/winapi-rs" }
shell32-sys = { git = "https://github.com/retep998/winapi-rs" }
user32-sys = { git = "https://github.com/retep998/winapi-rs" }
uuid = { version = "0.3.0", features = ["v5"] }

17
build_release_windows.ps1 Normal file
View file

@ -0,0 +1,17 @@
"Compiling resource file"
RC /fo res\application.res res\application.rc
"Compiling executable"
cargo rustc --release --features "ui" -- -C link-args="/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup res\application.res"
"Creating output directory"
New-Item .\release\windows -type directory -Force
Remove-Item -Recurse .\release\windows\*
"Copying to output directory"
Copy-Item .\target\release\polaris.exe .\release\windows\
Copy-Item .\res\libeay32.dll .\release\windows\
Copy-Item .\res\libeay32md.dll .\release\windows\
Copy-Item .\web\ .\release\windows\ -recurse
Read-Host -Prompt "All clear! Press Enter to exit"

15
res/application.manifest Normal file
View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<asmv3:application>
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>

5
res/application.rc Normal file
View file

@ -0,0 +1,5 @@
#define IDI_POLARIS 0x101
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "application.manifest"
IDI_POLARIS ICON "icon_polaris_32.ico"

View file

@ -10,6 +10,17 @@ extern crate staticfile;
extern crate toml;
extern crate url;
#[cfg(windows)]
extern crate uuid;
#[cfg(windows)]
extern crate winapi;
#[cfg(windows)]
extern crate kernel32;
#[cfg(windows)]
extern crate shell32;
#[cfg(windows)]
extern crate user32;
use std::path::Path;
use std::sync::Arc;
use std::sync::Mutex;
@ -21,33 +32,35 @@ use staticfile::Static;
mod api;
mod collection;
mod error;
mod ui;
mod vfs;
use api::*;
use collection::*;
fn main() {
let mut api_chain;
{
let api_handler;
println!("Spawning server thread");
std::thread::spawn(move || {
let mut api_chain;
{
let mut collection = Collection::new();
collection.load_config(Path::new("Polaris.toml")).unwrap();
let collection = Arc::new(Mutex::new(collection));
api_handler = get_api_handler(collection);
let api_handler;
{
let mut collection = collection::Collection::new();
collection.load_config(Path::new("Polaris.toml")).unwrap();
let collection = Arc::new(Mutex::new(collection));
api_handler = api::get_api_handler(collection);
}
api_chain = Chain::new(api_handler);
let auth_secret = std::env::var("POLARIS_SECRET")
.expect("Environment variable POLARIS_SECRET must be set");
let cookie_middleware = oven::new(auth_secret.into_bytes());
api_chain.link(cookie_middleware);
}
api_chain = Chain::new(api_handler);
let auth_secret = std::env::var("POLARIS_SECRET").expect("Environment variable POLARIS_SECRET must be set");
let cookie_middleware = oven::new(auth_secret.into_bytes());
api_chain.link(cookie_middleware);
}
let mut mount = Mount::new();
mount.mount("/api/", api_chain);
mount.mount("/", Static::new(Path::new("web")));
let mut mount = Mount::new();
mount.mount("/api/", api_chain);
mount.mount("/", Static::new(Path::new("web")));
Iron::new(mount).http("localhost:3000").unwrap();
});
Iron::new(mount).http("localhost:3000").unwrap();
ui::run();
}

9
src/ui/headless.rs Normal file
View file

@ -0,0 +1,9 @@
use std::time;
use std::thread;
pub fn run() {
println!("Starting up UI (headless)");
loop {
thread::sleep(time::Duration::from_secs(10));
}
}

11
src/ui/mod.rs Normal file
View file

@ -0,0 +1,11 @@
#[cfg(all(windows, feature = "ui"))]
mod windows;
#[cfg(all(windows, feature = "ui"))]
pub use self::windows::*;
#[cfg(not(all(windows, feature = "ui")))]
mod headless;
#[cfg(not(all(windows, feature = "ui")))]
pub use self::headless::*;

144
src/ui/windows.rs Normal file
View file

@ -0,0 +1,144 @@
use kernel32;
use shell32;
use std;
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use std::thread;
use std::time;
use user32;
use uuid;
use winapi;
const IDI_POLARIS: isize = 0x101;
pub trait ToWin {
type Out;
fn to_win(&self) -> Self::Out;
}
impl<'a> ToWin for &'a str {
type Out = Vec<u16>;
fn to_win(&self) -> Self::Out {
OsStr::new(self)
.encode_wide()
.chain(std::iter::once(0))
.collect()
}
}
impl ToWin for uuid::Uuid {
type Out = winapi::GUID;
fn to_win(&self) -> Self::Out {
let bytes = self.as_bytes();
let end = [bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14],
bytes[15]];
winapi::GUID {
Data1: ((bytes[0] as u32) << 24 | (bytes[1] as u32) << 16 | (bytes[2] as u32) << 8 |
(bytes[3] as u32)),
Data2: ((bytes[4] as u16) << 8 | (bytes[5] as u16)),
Data3: ((bytes[6] as u16) << 8 | (bytes[7] as u16)),
Data4: end,
}
}
}
fn create_window() -> Option<winapi::HWND> {
let class_name = "Polaris-class".to_win();
let window_name = "Polaris-window".to_win();
unsafe {
let module_handle = kernel32::GetModuleHandleW(std::ptr::null());
let wnd = winapi::WNDCLASSW {
style: 0,
lpfnWndProc: Some(window_proc),
hInstance: module_handle,
hIcon: std::ptr::null_mut(),
hCursor: std::ptr::null_mut(),
lpszClassName: class_name.as_ptr(),
hbrBackground: winapi::COLOR_WINDOW as winapi::HBRUSH,
lpszMenuName: std::ptr::null_mut(),
cbClsExtra: 0,
cbWndExtra: 0,
};
let atom = user32::RegisterClassW(&wnd);
if atom == 0 {
return None;
}
let window_handle = user32::CreateWindowExW(0,
atom as winapi::LPCWSTR,
window_name.as_ptr(),
winapi::WS_DISABLED,
0,
0,
0,
0,
user32::GetDesktopWindow(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut());
if window_handle.is_null() {
return None;
}
return Some(window_handle);
}
}
fn add_notification_icon(window_handle: winapi::HWND, id: winapi::GUID) {
unsafe {
let module = kernel32::GetModuleHandleW(std::ptr::null());
let icon = user32::LoadIconW(module, std::mem::transmute(IDI_POLARIS));
assert!(!icon.is_null());
let mut icon_data = winapi::NOTIFYICONDATAW {
cbSize: std::mem::size_of::<winapi::NOTIFYICONDATAW>() as u32,
hWnd: window_handle,
uFlags: winapi::NIF_ICON | winapi::NIF_GUID,
guidItem: id,
hIcon: icon,
uID: 0,
uCallbackMessage: 0,
szTip: [0; 128],
dwState: 0,
dwStateMask: 0,
szInfo: [0; 256],
uTimeout: 0,
szInfoTitle: [0; 64],
dwInfoFlags: 0,
hBalloonIcon: std::ptr::null_mut(),
};
shell32::Shell_NotifyIconW(winapi::NIM_ADD, &mut icon_data);
}
}
pub fn run() {
println!("Starting up UI (Windows)");
let path = std::env::current_exe().expect("Could not get current path");
let guid = uuid::Uuid::new_v5(&uuid::NAMESPACE_OID, path.to_str().unwrap()).to_win();
let window_handle = create_window().expect("Could not initialize window");
add_notification_icon(window_handle, guid);
loop {
thread::sleep(time::Duration::from_secs(10));
}
}
pub unsafe extern "system" fn window_proc(h_wnd: winapi::HWND,
msg: winapi::UINT,
w_param: winapi::WPARAM,
l_param: winapi::LPARAM)
-> winapi::LRESULT {
if msg == winapi::winuser::WM_DESTROY {
user32::PostQuitMessage(0);
}
return user32::DefWindowProcW(h_wnd, msg, w_param, l_param);
}