From 108220e82b3373018f6a4a816a1729eca8c791d3 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Sun, 8 Sep 2019 00:51:08 +0200 Subject: [PATCH] First steps in windows notification --- native/libwinbridge/bridge.cpp | 130 +++++++++++++++++++++++++++++++++ native/libwinbridge/bridge.h | 4 + src/bridge/windows.rs | 5 ++ src/res/win/espanso.bmp | Bin 0 -> 19256 bytes src/ui/mod.rs | 5 +- src/ui/windows.rs | 36 +++++++-- 6 files changed, 170 insertions(+), 10 deletions(-) create mode 100644 src/res/win/espanso.bmp diff --git a/native/libwinbridge/bridge.cpp b/native/libwinbridge/bridge.cpp index f6c8756..316ff29 100644 --- a/native/libwinbridge/bridge.cpp +++ b/native/libwinbridge/bridge.cpp @@ -272,4 +272,134 @@ int32_t get_active_window_executable(wchar_t * buffer, int32_t size) { CloseHandle(process); return res; +} + +// UI + +const wchar_t* const notification_winclass = L"EspansoNotification"; +HWND nw; +HBITMAP g_espanso_icon = NULL; + +/* + * Message handler procedure for the notification window + */ +LRESULT CALLBACK notification_worker_procedure(HWND window, unsigned int msg, WPARAM wp, LPARAM lp) +{ + HDC hdcStatic = NULL; + + switch (msg) + { + case WM_CREATE: + g_espanso_icon = (HBITMAP)LoadImage(NULL, L"C:\\Users\\fredd\\Documents\\espanso.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); + break; + case WM_DESTROY: + std::cout << "\ndestroying window\n"; + PostQuitMessage(0); + DeleteObject(g_espanso_icon); + return 0L; + case WM_PAINT: + { + BITMAP bm; + PAINTSTRUCT ps; + + HDC hdc = BeginPaint(window, &ps); + + HDC hdcMem = CreateCompatibleDC(hdc); + HBITMAP hbmOld = (HBITMAP) SelectObject(hdcMem, g_espanso_icon); + + GetObject(g_espanso_icon, sizeof(bm), &bm); + + BitBlt(hdc, 10, 10, 80, 80, hdcMem, 0, 0, SRCCOPY); + + SelectObject(hdcMem, hbmOld); + DeleteDC(hdcMem); + + EndPaint(window, &ps); + } + break; + case WM_CTLCOLORSTATIC: + + hdcStatic = (HDC)wp; + SetTextColor(hdcStatic, RGB(0, 0, 0)); + SetBkMode(hdcStatic, TRANSPARENT); + + return (LRESULT)GetStockObject(NULL_BRUSH); + default: + return DefWindowProc(window, msg, wp, lp); + } +} + +int32_t show_notification(wchar_t * message, wchar_t * icon_path) { + g_espanso_icon = (HBITMAP)LoadImage(NULL, icon_path, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); + + SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); + + // Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexa + WNDCLASSEX wndclass = { + sizeof(WNDCLASSEX), // cbSize: Size of this structure + 0, // style: Class styles + notification_worker_procedure, // lpfnWndProc: Pointer to the window procedure + 0, // cbClsExtra: Number of extra bytes to allocate following the window-class structure + 0, // cbWndExtra: The number of extra bytes to allocate following the window instance. + GetModuleHandle(0), // hInstance: A handle to the instance that contains the window procedure for the class. + NULL, // hIcon: A handle to the class icon. + LoadCursor(0,IDC_ARROW), // hCursor: A handle to the class cursor. + NULL, // hbrBackground: A handle to the class background brush. + NULL, // lpszMenuName: Pointer to a null-terminated character string that specifies the resource name of the class menu + notification_winclass, // lpszClassName: A pointer to a null-terminated string or is an atom. + NULL // hIconSm: A handle to a small icon that is associated with the window class. + }; + + if (RegisterClassEx(&wndclass)) + { + // Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw + nw = CreateWindowEx( + WS_EX_TOOLWINDOW, // dwExStyle: The extended window style of the window being created. + notification_winclass, // lpClassName: A null-terminated string or a class atom created by a previous call to the RegisterClass + L"Espanso Notification", // lpWindowName: The window name. + WS_POPUPWINDOW, // dwStyle: The style of the window being created. + CW_USEDEFAULT, // X: The initial horizontal position of the window. + CW_USEDEFAULT, // Y: The initial vertical position of the window. + 300, // nWidth: The width, in device units, of the window. + 100, // nHeight: The height, in device units, of the window. + NULL, // hWndParent: handle to the parent or owner window of the window being created. + NULL, // hMenu: A handle to a menu, or specifies a child-window identifier, depending on the window style. + GetModuleHandle(0), // hInstance: A handle to the instance of the module to be associated with the window. + NULL // lpParam: Pointer to a value to be passed to the window + ); + + if (nw) + { + + static HWND hwnd_st_u, hwnd_ed_u; + int x, w, y, h; + y = 40; h = 30; + x = 100; w = 180; + hwnd_st_u = CreateWindowEx(0, L"static", L"ST_U", + WS_CHILD | WS_VISIBLE | WS_TABSTOP | SS_CENTER, + x, y, w, h, + nw, (HMENU)(501), + (HINSTANCE)GetWindowLong(nw, GWLP_HINSTANCE), NULL); + + SetWindowText(hwnd_st_u, message); + + int posX = GetSystemMetrics(SM_CXSCREEN) - 350; + int posY = GetSystemMetrics(SM_CYSCREEN) - 200; + + SetWindowPos(window, HWND_TOP, posX, posY, 0, 0, SWP_NOSIZE); + + // Hide the window + ShowWindow(nw, SW_SHOW); + + // Enter the Event loop + MSG msg; + while (GetMessage(&msg, 0, 0, 0)) DispatchMessage(&msg); + } + } + else { + // Something went wrong, error. + return -1; + } + + return 1; } \ No newline at end of file diff --git a/native/libwinbridge/bridge.h b/native/libwinbridge/bridge.h index bb29dc1..aca8d87 100644 --- a/native/libwinbridge/bridge.h +++ b/native/libwinbridge/bridge.h @@ -54,4 +54,8 @@ extern "C" int32_t get_active_window_name(wchar_t * buffer, int32_t size); */ extern "C" int32_t get_active_window_executable(wchar_t * buffer, int32_t size); +// UI + +extern "C" int32_t show_notification(wchar_t * message, wchar_t * icon_path); + #endif //ESPANSO_BRIDGE_H \ No newline at end of file diff --git a/src/bridge/windows.rs b/src/bridge/windows.rs index f401812..653e8cd 100644 --- a/src/bridge/windows.rs +++ b/src/bridge/windows.rs @@ -3,9 +3,14 @@ use std::os::raw::{c_void}; #[allow(improper_ctypes)] #[link(name="winbridge", kind="static")] extern { + // SYSTEM pub fn get_active_window_name(buffer: *mut u16, size: i32) -> i32; pub fn get_active_window_executable(buffer: *mut u16, size: i32) -> i32; + // UI + pub fn show_notification(message: *const u16, icon_path: *const u16) -> i32; + + // KEYBOARD pub fn register_keypress_callback(s: *const c_void, cb: extern fn(_self: *mut c_void, *const i32, i32, i32, i32)); diff --git a/src/res/win/espanso.bmp b/src/res/win/espanso.bmp new file mode 100644 index 0000000000000000000000000000000000000000..882c849d2beaf70e291a0fa7eaef04b61bcf6df1 GIT binary patch literal 19256 zcmeHPX>c4z6&|}F1e?Sul9Xd&QsxK{YzhJ?Y(f>~GDRxnDxfF|DuF-@CP0e(2&v#C ziWA{DB$j2zvL#EhF6%z6Ti(^Nt9xag-qq?_>s%eyt<`<3Hhin0CheWs*`A)=wSHK& z+IsWm^?UE@p6-7Ax@QkPd*EqqEPmE00tUK0jvG>&G2PQNn#utH6 z2A#)S(Ygl;n}2!zMux+&PHow*f(t7*TKa;Ms`h%sqDUZC|7G+7+hVt0!N?k0wa?Xb zzTxqSRRmKkTw?m;>{7q1BmTIF9(u9tKCyeg;GEZNHCW!hRb$$ittq+sJHEgruM=In zZ!F&*ZAsXuvmBH6%GY;w=D!l|_>i=(FXc7-+}Chu^ruvR*VjGTw%8L_zK6yc|Hxb@ z{`}yi&PS~~F>4Gj{(^siD6-N!mg{xM6SLP8`QT?^??RrOGhHa&=4S{>%?(Uwz0%5S z_J#10yOdhLCGu1AZR#N>RqgYZ^Jj~=GX>n~d@eYjJ6*tiT*!sUE+UlLjVQTWWi+C1 z`@2%3r2Kh}6V!jRx%bkY9}vLH3!E+FPUUgOGoAeZWO65SxsW0b@?>!aI=lX-%uU=s zUc_LLFA}QZKFr~crEx)dTzthNI`co8dV~ANHPiDQb4&emOIF)L_n57@V<_HW2`p^> zO+?9kCoXgEraPM+Wtr`wAVORQ$mq5gOYdc{3vYu9oJga}9;$3VI%YG{SYKOT?inp8 zYJTzKf-Puq*j3ZkGYi*gjFK{Ct$$$wtL4y4_*8WStUFdog_QKnLQkgQNmSRFLUzcR zh96U!{AiZ@j29CpIA zyB1f5#QrmIklNa)J;uHqqx%x2Q4A?c!J@Byl*0w(Zkb>1p|nCH7OLHb;ZCVDf;`ee zG*In|Rrk0@7PFi7aOgU_GT~Y2eN`fsg%47k_YxjJ+U0PXKRKhiANE zbKuM@bYsn1+5UQR^#MG_PzCtN@L&X^cYsDRSE|S5H9k#K)nUw19xnQBW+!(K(5^H1TSVnSU_4|&cbEi}%b_D+=XOM)7 zhXnz80$mRk#f;8f!G`YBn7=Tld1Y2gsQiZ55Sa8Z1xZYlm&*5uwW;S=C$v~of8Y(s z36INUnos6&;lpLjS_R$}^tn#2x5WzrL#o?hr8 zJyArXnW{Qo73*~2q#M4ECMKs;JR6av_jttH(sx=-V#hL)>f*`9rGxn-6;`id+HLmr zWhu3v@^DNJeHI}ZzmUiG(Iif5eEw4jH?itOIafA7YAU5)lX+%e!UmlRr&vq-P{vdiiJw`ziCmh!7i!w${ z{pST?r340E%o?%f56kFhxV5ro3n|*lmWbAyUzWGHvNod>CMnN{5V`fw$Xii#x4HLy zmq3NIC{N=gl}B65qqJZe9l4;%^DN`h69}x@QU7;;l*{rkCslo$#sm&w3Z63%{Ej@P zr^L$r3(IQvzLTmR!^}m7DD*DTpFqoRe1=}Fq$GxG^yRZf+Z%g4Ps)<5u(CFhR(l9x z72A;pEaxTFN;2n{1|?A_Vxli2$j9n6PQTY|jdM68O-VCN1Ci&h?7$p^dSkYgQD*t4!_!W_i?j;Y%qrkMdYNVk-f;RxBK_Jmf!si9iKgtIR6`(r(*e zv^*}EyUa-d(gA4PdRYX(6jWu_>+#v~sL|_hy5P*~eq5P>h9=gE)b0 z8^TAmghQb?hC%5(vlw2@_L2XJ)Z*wqmd4B|6ct2TAXM`iMecc5xVGy}MGmVMo?YzC zssEXGQP5~Z#>u-wv7)NLt^{^hY5Y_#6J55G?OhB-h-if228Isxh}>zw3txDEk_`!j)*2BW@tKf$n cTCL&u^%bnOH}gZg32XJXOSfwPHNbKI17P=2{r~^~ literal 0 HcmV?d00001 diff --git a/src/ui/mod.rs b/src/ui/mod.rs index bada960..0165f50 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -8,7 +8,6 @@ mod linux; mod macos; pub trait UIManager { - fn initialize(&self); fn notify(&self, message: &str); } @@ -31,7 +30,5 @@ pub fn get_uimanager() -> impl UIManager { // WINDOWS IMPLEMENTATION #[cfg(target_os = "windows")] pub fn get_uimanager() -> impl UIManager { - let manager = windows::WindowsUIManager{}; - manager.initialize(); - manager + windows::WindowsUIManager::new() } \ No newline at end of file diff --git a/src/ui/windows.rs b/src/ui/windows.rs index 9642188..524c6dc 100644 --- a/src/ui/windows.rs +++ b/src/ui/windows.rs @@ -1,19 +1,43 @@ use std::process::Command; +use crate::bridge::windows::show_notification; +use widestring::U16CString; +use std::fs; +use log::{info}; + +const ICON_BINARY : &'static [u8] = include_bytes!("../res/win/espanso.bmp"); pub struct WindowsUIManager { - + icon_file: String, } impl super::UIManager for WindowsUIManager { - fn initialize(&self) { - // TODO: check if notify-send is present and log an error otherwise. - } - fn notify(&self, message: &str) { - + unsafe { + let message = U16CString::from_str(message).unwrap(); + let icon_file = U16CString::from_str(&self.icon_file).unwrap(); + let res = show_notification(message.as_ptr(), icon_file.as_ptr()); + info!("{}", res); + } } } impl WindowsUIManager { + pub fn new() -> WindowsUIManager { + let res = dirs::cache_dir(); + let mut icon_file:String = "".to_owned(); + if let Some(cache_dir) = res { + let espanso_icon_file = cache_dir.join("espansoicon.bmp"); + fs::write(&espanso_icon_file, ICON_BINARY) + .expect("Unable to write windows icon file"); + + icon_file = espanso_icon_file.to_str().unwrap_or(&"".to_owned()).to_owned(); + } + + info!("Extracted cached icon to: {}", icon_file); + + WindowsUIManager { + icon_file + } + } } \ No newline at end of file