From 9ce453e58bb04f6114d622a100176d09386b4ea4 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Sat, 20 Mar 2021 21:47:07 +0100 Subject: [PATCH 1/3] feat(info): implement app info provider on linux --- Cargo.lock | 14 +++ Cargo.toml | 1 + espanso-info/Cargo.toml | 23 ++++ espanso-info/build.rs | 72 +++++++++++ espanso-info/src/lib.rs | 72 +++++++++++ espanso-info/src/wayland/mod.rs | 39 ++++++ espanso-info/src/x11/ffi.rs | 27 +++++ espanso-info/src/x11/mod.rs | 91 ++++++++++++++ espanso-info/src/x11/native.cpp | 204 ++++++++++++++++++++++++++++++++ espanso-info/src/x11/native.h | 29 +++++ espanso/Cargo.toml | 3 +- espanso/src/main.rs | 7 +- 12 files changed, 578 insertions(+), 4 deletions(-) create mode 100644 espanso-info/Cargo.toml create mode 100644 espanso-info/build.rs create mode 100644 espanso-info/src/lib.rs create mode 100644 espanso-info/src/wayland/mod.rs create mode 100644 espanso-info/src/x11/ffi.rs create mode 100644 espanso-info/src/x11/mod.rs create mode 100644 espanso-info/src/x11/native.cpp create mode 100644 espanso-info/src/x11/native.h diff --git a/Cargo.lock b/Cargo.lock index 13074cc..c3b8f13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -227,6 +227,7 @@ dependencies = [ "espanso-clipboard", "espanso-config", "espanso-detect", + "espanso-info", "espanso-inject", "espanso-match", "espanso-ui", @@ -281,6 +282,19 @@ dependencies = [ "widestring", ] +[[package]] +name = "espanso-info" +version = "0.1.0" +dependencies = [ + "anyhow", + "cc", + "lazy_static", + "lazycell", + "log", + "thiserror", + "widestring", +] + [[package]] name = "espanso-inject" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index a8e3bce..e7db66c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,5 @@ members = [ "espanso-match", "espanso-clipboard", "espanso-render", + "espanso-info", ] \ No newline at end of file diff --git a/espanso-info/Cargo.toml b/espanso-info/Cargo.toml new file mode 100644 index 0000000..56aa979 --- /dev/null +++ b/espanso-info/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "espanso-info" +version = "0.1.0" +authors = ["Federico Terzi "] +edition = "2018" +build="build.rs" + +[features] +# If the wayland feature is enabled, all X11 dependencies will be dropped +wayland = [] + +[dependencies] +log = "0.4.14" +lazycell = "1.3.0" +anyhow = "1.0.38" +thiserror = "1.0.23" +lazy_static = "1.4.0" + +[target.'cfg(windows)'.dependencies] +widestring = "0.4.3" + +[build-dependencies] +cc = "1.0.66" \ No newline at end of file diff --git a/espanso-info/build.rs b/espanso-info/build.rs new file mode 100644 index 0000000..28a331e --- /dev/null +++ b/espanso-info/build.rs @@ -0,0 +1,72 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +#[cfg(target_os = "windows")] +fn cc_config() { + println!("cargo:rerun-if-changed=src/win32/native.cpp"); + println!("cargo:rerun-if-changed=src/win32/native.h"); + cc::Build::new() + .cpp(true) + .include("src/win32/native.h") + .file("src/win32/native.cpp") + .compile("espansoclipboard"); + + println!("cargo:rustc-link-lib=static=espansoclipboard"); + println!("cargo:rustc-link-lib=dylib=user32"); + #[cfg(target_env = "gnu")] + println!("cargo:rustc-link-lib=dylib=stdc++"); +} + +#[cfg(target_os = "linux")] +fn cc_config() { + if cfg!(not(feature = "wayland")) { + println!("cargo:rerun-if-changed=src/x11/native.h"); + println!("cargo:rerun-if-changed=src/x11/native.c"); + cc::Build::new() + .cpp(true) + .include("src/x11/native.h") + .file("src/x11/native.cpp") + .compile("espansoinfo"); + + println!("cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu/"); + println!("cargo:rustc-link-lib=static=espansoinfo"); + println!("cargo:rustc-link-lib=dylib=stdc++"); + println!("cargo:rustc-link-lib=dylib=X11"); + } else { + // Nothing to compile on wayland + } +} + +#[cfg(target_os = "macos")] +fn cc_config() { + println!("cargo:rerun-if-changed=src/cocoa/native.mm"); + println!("cargo:rerun-if-changed=src/cocoa/native.h"); + cc::Build::new() + .cpp(true) + .include("src/cocoa/native.h") + .file("src/cocoa/native.mm") + .compile("espansoclipboard"); + println!("cargo:rustc-link-lib=dylib=c++"); + println!("cargo:rustc-link-lib=static=espansoclipboard"); + println!("cargo:rustc-link-lib=framework=Cocoa"); +} + +fn main() { + cc_config(); +} diff --git a/espanso-info/src/lib.rs b/espanso-info/src/lib.rs new file mode 100644 index 0000000..4f94cac --- /dev/null +++ b/espanso-info/src/lib.rs @@ -0,0 +1,72 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use anyhow::Result; +use log::info; + +#[cfg(target_os = "windows")] +mod win32; + +#[cfg(target_os = "linux")] +#[cfg(not(feature = "wayland"))] +mod x11; + +#[cfg(target_os = "linux")] +#[cfg(feature = "wayland")] +mod wayland; + +#[cfg(target_os = "macos")] +mod cocoa; + +pub trait AppInfoProvider { + fn get_info(&self) -> AppInfo; +} + +#[derive(Debug, Clone)] +pub struct AppInfo { + pub title: Option, + pub exec: Option, + pub class: Option, +} + +#[cfg(target_os = "windows")] +pub fn get_clipboard(_: ClipboardOptions) -> Result> { + info!("using Win32Clipboard"); + Ok(Box::new(win32::Win32Clipboard::new()?)) +} + +#[cfg(target_os = "macos")] +pub fn get_clipboard(_: ClipboardOptions) -> Result> { + info!("using CocoaClipboard"); + Ok(Box::new(cocoa::CocoaClipboard::new()?)) +} + +#[cfg(target_os = "linux")] +#[cfg(not(feature = "wayland"))] +pub fn get_provider() -> Result> { + info!("using X11AppInfoProvider"); + Ok(Box::new(x11::X11AppInfoProvider::new())) +} + +#[cfg(target_os = "linux")] +#[cfg(feature = "wayland")] +pub fn get_provider() -> Result> { + info!("using WaylandAppInfoProvider"); + Ok(Box::new(wayland::WaylandAppInfoProvider::new())) +} \ No newline at end of file diff --git a/espanso-info/src/wayland/mod.rs b/espanso-info/src/wayland/mod.rs new file mode 100644 index 0000000..84c2dbf --- /dev/null +++ b/espanso-info/src/wayland/mod.rs @@ -0,0 +1,39 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use crate::{AppInfoProvider, AppInfo}; + +pub(crate) struct WaylandAppInfoProvider {} + +impl WaylandAppInfoProvider { + pub fn new() -> Self { + Self {} + } +} + +impl AppInfoProvider for WaylandAppInfoProvider { + // TODO: can we read these info on Wayland? + fn get_info(&self) -> AppInfo { + AppInfo { + title: None, + exec: None, + class: None, + } + } +} diff --git a/espanso-info/src/x11/ffi.rs b/espanso-info/src/x11/ffi.rs new file mode 100644 index 0000000..4a94f7b --- /dev/null +++ b/espanso-info/src/x11/ffi.rs @@ -0,0 +1,27 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use std::os::raw::c_char; + +#[link(name = "espansoinfo", kind = "static")] +extern "C" { + pub fn info_get_title(buffer: *mut c_char, buffer_size: i32) -> i32; + pub fn info_get_exec(buffer: *mut c_char, buffer_size: i32) -> i32; + pub fn info_get_class(buffer: *mut c_char, buffer_size: i32) -> i32; +} diff --git a/espanso-info/src/x11/mod.rs b/espanso-info/src/x11/mod.rs new file mode 100644 index 0000000..6649940 --- /dev/null +++ b/espanso-info/src/x11/mod.rs @@ -0,0 +1,91 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use std::{ffi::CStr, os::raw::c_char}; + +use crate::{AppInfo, AppInfoProvider}; + +use self::ffi::{info_get_class, info_get_exec, info_get_title}; + +mod ffi; + +pub struct X11AppInfoProvider {} + +impl X11AppInfoProvider { + pub fn new() -> Self { + Self {} + } +} + +impl AppInfoProvider for X11AppInfoProvider { + fn get_info(&self) -> AppInfo { + AppInfo { + title: self.get_title(), + class: self.get_class(), + exec: self.get_exec(), + } + } +} + +impl X11AppInfoProvider { + fn get_exec(&self) -> Option { + let mut buffer: [c_char; 2048] = [0; 2048]; + if unsafe { info_get_exec(buffer.as_mut_ptr(), (buffer.len() - 1) as i32) } > 0 { + let string = unsafe { CStr::from_ptr(buffer.as_ptr()) }; + let string = string.to_string_lossy(); + if !string.is_empty() { + Some(string.to_string()) + } else { + None + } + } else { + None + } + } + + fn get_class(&self) -> Option { + let mut buffer: [c_char; 2048] = [0; 2048]; + if unsafe { info_get_class(buffer.as_mut_ptr(), (buffer.len() - 1) as i32) } > 0 { + let string = unsafe { CStr::from_ptr(buffer.as_ptr()) }; + let string = string.to_string_lossy(); + if !string.is_empty() { + Some(string.to_string()) + } else { + None + } + } else { + None + } + } + + fn get_title(&self) -> Option { + let mut buffer: [c_char; 2048] = [0; 2048]; + if unsafe { info_get_title(buffer.as_mut_ptr(), (buffer.len() - 1) as i32) } > 0 { + let string = unsafe { CStr::from_ptr(buffer.as_ptr()) }; + let string = string.to_string_lossy(); + if !string.is_empty() { + Some(string.to_string()) + } else { + None + } + } else { + None + } + } +} \ No newline at end of file diff --git a/espanso-info/src/x11/native.cpp b/espanso-info/src/x11/native.cpp new file mode 100644 index 0000000..a28cab6 --- /dev/null +++ b/espanso-info/src/x11/native.cpp @@ -0,0 +1,204 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +#include "native.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Function taken from the wmlib tool source code +char *get_property(Display *disp, Window win, + Atom xa_prop_type, char *prop_name, unsigned long *size) +{ + unsigned long ret_nitems, ret_bytes_after, tmp_size; + Atom xa_prop_name, xa_ret_type; + unsigned char *ret_prop; + int ret_format; + char *ret; + int size_in_byte; + + xa_prop_name = XInternAtom(disp, prop_name, False); + + if (XGetWindowProperty(disp, win, xa_prop_name, 0, 4096 / 4, False, + xa_prop_type, &xa_ret_type, &ret_format, &ret_nitems, + &ret_bytes_after, &ret_prop) != Success) + return NULL; + + if (xa_ret_type != xa_prop_type) + { + XFree(ret_prop); + return NULL; + } + + switch (ret_format) + { + case 8: + size_in_byte = sizeof(char); + break; + case 16: + size_in_byte = sizeof(short); + break; + case 32: + size_in_byte = sizeof(long); + break; + } + + tmp_size = size_in_byte * ret_nitems; + ret = (char *)malloc(tmp_size + 1); + memcpy(ret, ret_prop, tmp_size); + ret[tmp_size] = '\0'; + + if (size) + *size = tmp_size; + + XFree(ret_prop); + return ret; +} + +// Function taken from Window Management Library for Ruby +char *xwm_get_win_title(Display *disp, Window win) +{ + char *wname = (char *)get_property(disp, win, XA_STRING, "WM_NAME", NULL); + char *nwname = (char *)get_property(disp, win, XInternAtom(disp, "UTF8_STRING", False), "_NET_WM_NAME", NULL); + + return nwname ? nwname : (wname ? wname : NULL); +} + +int32_t info_get_title(char *buffer, int32_t buffer_size) +{ + Display *display = XOpenDisplay(0); + + if (!display) + { + return -1; + } + + Window focused; + int revert_to; + int ret = XGetInputFocus(display, &focused, &revert_to); + + int result = 1; + if (!ret) + { + fprintf(stderr, "xdo_get_active_window reported an error\n"); + result = -2; + } + else + { + char *title = xwm_get_win_title(display, focused); + + snprintf(buffer, buffer_size, "%s", title); + + XFree(title); + } + + XCloseDisplay(display); + + return result; +} + +int32_t info_get_exec(char *buffer, int32_t buffer_size) +{ + Display *display = XOpenDisplay(0); + + if (!display) + { + return -1; + } + + Window focused; + int revert_to; + int ret = XGetInputFocus(display, &focused, &revert_to); + + int result = 1; + if (!ret) + { + fprintf(stderr, "xdo_get_active_window reported an error\n"); + result = -2; + } + else + { + // Get the window process PID + char *pid_raw = (char *)get_property(display, focused, XA_CARDINAL, "_NET_WM_PID", NULL); + if (pid_raw == NULL) + { + result = -3; + } + else + { + int pid = pid_raw[0] | pid_raw[1] << 8 | pid_raw[2] << 16 | pid_raw[3] << 24; + + // Get the executable path from it + char proc_path[250]; + snprintf(proc_path, 250, "/proc/%d/exe", pid); + + readlink(proc_path, buffer, buffer_size); + + XFree(pid_raw); + } + } + + XCloseDisplay(display); + + return result; +} + +int32_t info_get_class(char *buffer, int32_t buffer_size) +{ + Display *display = XOpenDisplay(0); + + if (!display) + { + return -1; + } + + Window focused; + int revert_to; + int ret = XGetInputFocus(display, &focused, &revert_to); + + int result = 1; + if (!ret) + { + fprintf(stderr, "xdo_get_active_window reported an error\n"); + result = -2; + } + else + { + XClassHint hint; + + if (XGetClassHint(display, focused, &hint)) + { + snprintf(buffer, buffer_size, "%s", hint.res_class); + XFree(hint.res_name); + XFree(hint.res_class); + } + } + + XCloseDisplay(display); + + return result; +} \ No newline at end of file diff --git a/espanso-info/src/x11/native.h b/espanso-info/src/x11/native.h new file mode 100644 index 0000000..027299a --- /dev/null +++ b/espanso-info/src/x11/native.h @@ -0,0 +1,29 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +#ifndef ESPANSO_INFO_H +#define ESPANSO_INFO_H + +#include + +extern "C" int32_t info_get_title(char * buffer, int32_t buffer_size); +extern "C" int32_t info_get_exec(char * buffer, int32_t buffer_size); +extern "C" int32_t info_get_class(char * buffer, int32_t buffer_size); + +#endif //ESPANSO_INFO_H \ No newline at end of file diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml index cf83027..f372498 100644 --- a/espanso/Cargo.toml +++ b/espanso/Cargo.toml @@ -11,7 +11,7 @@ edition = "2018" [features] # If the wayland feature is enabled, all X11 dependencies will be dropped # and only methods suitable for Wayland will be used -wayland = ["espanso-detect/wayland", "espanso-inject/wayland", "espanso-clipboard/wayland"] +wayland = ["espanso-detect/wayland", "espanso-inject/wayland", "espanso-clipboard/wayland", "espanso-info/wayland"] [dependencies] espanso-detect = { path = "../espanso-detect" } @@ -20,5 +20,6 @@ espanso-inject = { path = "../espanso-inject" } espanso-config = { path = "../espanso-config" } espanso-match = { path = "../espanso-match" } espanso-clipboard = { path = "../espanso-clipboard" } +espanso-info = { path = "../espanso-info" } maplit = "1.0.2" simplelog = "0.9.0" \ No newline at end of file diff --git a/espanso/src/main.rs b/espanso/src/main.rs index 6bf296c..0b9c01d 100644 --- a/espanso/src/main.rs +++ b/espanso/src/main.rs @@ -23,9 +23,6 @@ fn main() { ]) .unwrap(); - let clipboard = espanso_clipboard::get_clipboard(Default::default()).unwrap(); - println!("clipboard: {:?}", clipboard.get_text()); - // let icon_paths = vec![ // ( // espanso_ui::icons::TrayIcon::Normal, @@ -80,6 +77,7 @@ fn main() { }) .unwrap(); let clipboard = espanso_clipboard::get_clipboard(Default::default()).unwrap(); + let provider = espanso_info::get_provider().unwrap(); source.initialize().unwrap(); source .eventloop(Box::new(move |event: InputEvent| { @@ -96,6 +94,9 @@ fn main() { //std::thread::sleep(std::time::Duration::from_secs(2)); //injector.send_key_combination(&[keys::Key::Control, keys::Key::V], Default::default()).unwrap(); } + if evt.key == espanso_detect::event::Key::Shift && evt.status == Status::Released { + println!("info {:?}", provider.get_info()); + } } InputEvent::HotKey(hotkey) => { if hotkey.hotkey_id == 2 { From 15d78dc13f6e35e52ed247a208e4aa26d387dfac Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Sat, 20 Mar 2021 22:04:47 +0100 Subject: [PATCH 2/3] feat(info): implement app info provider on Windows --- espanso-info/build.rs | 4 +- espanso-info/src/lib.rs | 6 +-- espanso-info/src/win32/ffi.rs | 24 ++++++++++ espanso-info/src/win32/mod.rs | 76 +++++++++++++++++++++++++++++++ espanso-info/src/win32/native.cpp | 65 ++++++++++++++++++++++++++ espanso-info/src/win32/native.h | 28 ++++++++++++ 6 files changed, 198 insertions(+), 5 deletions(-) create mode 100644 espanso-info/src/win32/ffi.rs create mode 100644 espanso-info/src/win32/mod.rs create mode 100644 espanso-info/src/win32/native.cpp create mode 100644 espanso-info/src/win32/native.h diff --git a/espanso-info/build.rs b/espanso-info/build.rs index 28a331e..c67b678 100644 --- a/espanso-info/build.rs +++ b/espanso-info/build.rs @@ -25,9 +25,9 @@ fn cc_config() { .cpp(true) .include("src/win32/native.h") .file("src/win32/native.cpp") - .compile("espansoclipboard"); + .compile("espansoinfo"); - println!("cargo:rustc-link-lib=static=espansoclipboard"); + println!("cargo:rustc-link-lib=static=espansoinfo"); println!("cargo:rustc-link-lib=dylib=user32"); #[cfg(target_env = "gnu")] println!("cargo:rustc-link-lib=dylib=stdc++"); diff --git a/espanso-info/src/lib.rs b/espanso-info/src/lib.rs index 4f94cac..c65643f 100644 --- a/espanso-info/src/lib.rs +++ b/espanso-info/src/lib.rs @@ -46,9 +46,9 @@ pub struct AppInfo { } #[cfg(target_os = "windows")] -pub fn get_clipboard(_: ClipboardOptions) -> Result> { - info!("using Win32Clipboard"); - Ok(Box::new(win32::Win32Clipboard::new()?)) +pub fn get_provider() -> Result> { + info!("using Win32AppInfoProvider"); + Ok(Box::new(win32::WinAppInfoProvider::new())) } #[cfg(target_os = "macos")] diff --git a/espanso-info/src/win32/ffi.rs b/espanso-info/src/win32/ffi.rs new file mode 100644 index 0000000..9d8b964 --- /dev/null +++ b/espanso-info/src/win32/ffi.rs @@ -0,0 +1,24 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +#[link(name = "espansoinfo", kind = "static")] +extern "C" { + pub fn info_get_title(buffer: *mut u16, buffer_size: i32) -> i32; + pub fn info_get_exec(buffer: *mut u16, buffer_size: i32) -> i32; +} diff --git a/espanso-info/src/win32/mod.rs b/espanso-info/src/win32/mod.rs new file mode 100644 index 0000000..cb40ece --- /dev/null +++ b/espanso-info/src/win32/mod.rs @@ -0,0 +1,76 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use widestring::U16CStr; + +use crate::{AppInfo, AppInfoProvider}; + +use self::ffi::{info_get_exec, info_get_title}; + +mod ffi; + +pub struct WinAppInfoProvider {} + +impl WinAppInfoProvider { + pub fn new() -> Self { + Self {} + } +} + +impl AppInfoProvider for WinAppInfoProvider { + fn get_info(&self) -> AppInfo { + AppInfo { + title: self.get_title(), + class: None, + exec: self.get_exec(), + } + } +} + +impl WinAppInfoProvider { + fn get_exec(&self) -> Option { + let mut buffer: [u16; 2048] = [0; 2048]; + if unsafe { info_get_exec(buffer.as_mut_ptr(), (buffer.len() - 1) as i32) } != 0 { + let string = unsafe { U16CStr::from_ptr_str(buffer.as_ptr()) }; + let string = string.to_string_lossy(); + if !string.is_empty() { + Some(string) + } else { + None + } + } else { + None + } + } + + fn get_title(&self) -> Option { + let mut buffer: [u16; 2048] = [0; 2048]; + if unsafe { info_get_title(buffer.as_mut_ptr(), (buffer.len() - 1) as i32) } > 0 { + let string = unsafe { U16CStr::from_ptr_str(buffer.as_ptr()) }; + let string = string.to_string_lossy(); + if !string.is_empty() { + Some(string) + } else { + None + } + } else { + None + } + } +} diff --git a/espanso-info/src/win32/native.cpp b/espanso-info/src/win32/native.cpp new file mode 100644 index 0000000..ef00d63 --- /dev/null +++ b/espanso-info/src/win32/native.cpp @@ -0,0 +1,65 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +#include "native.h" +#include +#include +#include +#include +#include +#include + +#define UNICODE + +#ifdef __MINGW32__ +#ifndef WINVER +#define WINVER 0x0606 +#endif +#define STRSAFE_NO_DEPRECATE +#endif + +#include +#include +#include + +#include + +int32_t info_get_title(wchar_t *buffer, int32_t buffer_size) +{ + HWND hwnd = GetForegroundWindow(); + return GetWindowText(hwnd, buffer, buffer_size); +} + +int32_t info_get_exec(wchar_t *buffer, int32_t buffer_size) +{ + HWND hwnd = GetForegroundWindow(); + + // Extract the window PID + DWORD windowPid; + GetWindowThreadProcessId(hwnd, &windowPid); + + DWORD dsize = (DWORD)buffer_size; + + // Extract the process executable file path + HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, windowPid); + int res = QueryFullProcessImageNameW(process, 0, buffer, &dsize); + CloseHandle(process); + + return res; +} \ No newline at end of file diff --git a/espanso-info/src/win32/native.h b/espanso-info/src/win32/native.h new file mode 100644 index 0000000..6f6dd02 --- /dev/null +++ b/espanso-info/src/win32/native.h @@ -0,0 +1,28 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +#ifndef ESPANSO_INFO_H +#define ESPANSO_INFO_H + +#include + +extern "C" int32_t info_get_title(wchar_t * buffer, int32_t buffer_size); +extern "C" int32_t info_get_exec(wchar_t * buffer, int32_t buffer_size); + +#endif //ESPANSO_INFO_H \ No newline at end of file From bfb38c19c7fc4f19a7f295acefb6e03ecb20f9cf Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Sun, 21 Mar 2021 10:57:08 +0100 Subject: [PATCH 3/3] feat(info): implement app info manager on macOS --- espanso-info/build.rs | 4 +- espanso-info/src/cocoa/ffi.rs | 27 ++++++++++ espanso-info/src/cocoa/mod.rs | 91 ++++++++++++++++++++++++++++++++ espanso-info/src/cocoa/native.h | 29 ++++++++++ espanso-info/src/cocoa/native.mm | 76 ++++++++++++++++++++++++++ espanso-info/src/lib.rs | 6 +-- 6 files changed, 228 insertions(+), 5 deletions(-) create mode 100644 espanso-info/src/cocoa/ffi.rs create mode 100644 espanso-info/src/cocoa/mod.rs create mode 100644 espanso-info/src/cocoa/native.h create mode 100644 espanso-info/src/cocoa/native.mm diff --git a/espanso-info/build.rs b/espanso-info/build.rs index c67b678..e08980f 100644 --- a/espanso-info/build.rs +++ b/espanso-info/build.rs @@ -61,9 +61,9 @@ fn cc_config() { .cpp(true) .include("src/cocoa/native.h") .file("src/cocoa/native.mm") - .compile("espansoclipboard"); + .compile("espansoinfo"); println!("cargo:rustc-link-lib=dylib=c++"); - println!("cargo:rustc-link-lib=static=espansoclipboard"); + println!("cargo:rustc-link-lib=static=espansoinfo"); println!("cargo:rustc-link-lib=framework=Cocoa"); } diff --git a/espanso-info/src/cocoa/ffi.rs b/espanso-info/src/cocoa/ffi.rs new file mode 100644 index 0000000..4a94f7b --- /dev/null +++ b/espanso-info/src/cocoa/ffi.rs @@ -0,0 +1,27 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use std::os::raw::c_char; + +#[link(name = "espansoinfo", kind = "static")] +extern "C" { + pub fn info_get_title(buffer: *mut c_char, buffer_size: i32) -> i32; + pub fn info_get_exec(buffer: *mut c_char, buffer_size: i32) -> i32; + pub fn info_get_class(buffer: *mut c_char, buffer_size: i32) -> i32; +} diff --git a/espanso-info/src/cocoa/mod.rs b/espanso-info/src/cocoa/mod.rs new file mode 100644 index 0000000..4f45fcd --- /dev/null +++ b/espanso-info/src/cocoa/mod.rs @@ -0,0 +1,91 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use std::{ffi::CStr, os::raw::c_char}; + +use crate::{AppInfo, AppInfoProvider}; + +use self::ffi::{info_get_class, info_get_exec, info_get_title}; + +mod ffi; + +pub struct CocoaAppInfoProvider {} + +impl CocoaAppInfoProvider { + pub fn new() -> Self { + Self {} + } +} + +impl AppInfoProvider for CocoaAppInfoProvider { + fn get_info(&self) -> AppInfo { + AppInfo { + title: self.get_title(), + class: self.get_class(), + exec: self.get_exec(), + } + } +} + +impl CocoaAppInfoProvider { + fn get_exec(&self) -> Option { + let mut buffer: [c_char; 2048] = [0; 2048]; + if unsafe { info_get_exec(buffer.as_mut_ptr(), (buffer.len() - 1) as i32) } > 0 { + let string = unsafe { CStr::from_ptr(buffer.as_ptr()) }; + let string = string.to_string_lossy(); + if !string.is_empty() { + Some(string.to_string()) + } else { + None + } + } else { + None + } + } + + fn get_class(&self) -> Option { + let mut buffer: [c_char; 2048] = [0; 2048]; + if unsafe { info_get_class(buffer.as_mut_ptr(), (buffer.len() - 1) as i32) } > 0 { + let string = unsafe { CStr::from_ptr(buffer.as_ptr()) }; + let string = string.to_string_lossy(); + if !string.is_empty() { + Some(string.to_string()) + } else { + None + } + } else { + None + } + } + + fn get_title(&self) -> Option { + let mut buffer: [c_char; 2048] = [0; 2048]; + if unsafe { info_get_title(buffer.as_mut_ptr(), (buffer.len() - 1) as i32) } > 0 { + let string = unsafe { CStr::from_ptr(buffer.as_ptr()) }; + let string = string.to_string_lossy(); + if !string.is_empty() { + Some(string.to_string()) + } else { + None + } + } else { + None + } + } +} \ No newline at end of file diff --git a/espanso-info/src/cocoa/native.h b/espanso-info/src/cocoa/native.h new file mode 100644 index 0000000..027299a --- /dev/null +++ b/espanso-info/src/cocoa/native.h @@ -0,0 +1,29 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +#ifndef ESPANSO_INFO_H +#define ESPANSO_INFO_H + +#include + +extern "C" int32_t info_get_title(char * buffer, int32_t buffer_size); +extern "C" int32_t info_get_exec(char * buffer, int32_t buffer_size); +extern "C" int32_t info_get_class(char * buffer, int32_t buffer_size); + +#endif //ESPANSO_INFO_H \ No newline at end of file diff --git a/espanso-info/src/cocoa/native.mm b/espanso-info/src/cocoa/native.mm new file mode 100644 index 0000000..9e6306f --- /dev/null +++ b/espanso-info/src/cocoa/native.mm @@ -0,0 +1,76 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +#include "native.h" +#import +#import + +int32_t info_get_title(char *buffer, int32_t buffer_size) +{ + CFArrayRef windows = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements | kCGWindowListOptionOnScreenOnly, kCGNullWindowID); + int32_t result = 0; + + if (windows) { + for (NSDictionary *window in (NSArray *)windows) { + NSNumber *ownerPid = window[(id) kCGWindowOwnerPID]; + + NSRunningApplication *currentApp = [NSRunningApplication runningApplicationWithProcessIdentifier: [ownerPid intValue]]; + + if ([currentApp isActive]) { + NSString *name = window[(id) kCGWindowName]; + if (name.length > 0) { + const char * title = [name UTF8String]; + snprintf(buffer, buffer_size, "%s", title); + result = 1; + } + break; + } + } + + CFRelease(windows); + } + + return result; +} + +int32_t info_get_exec(char *buffer, int32_t buffer_size) +{ + NSRunningApplication *frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication]; + NSString *bundlePath = [frontApp bundleURL].path; + const char * path = [bundlePath UTF8String]; + + snprintf(buffer, buffer_size, "%s", path); + + [bundlePath release]; + + return 1; +} + +int32_t info_get_class(char *buffer, int32_t buffer_size) +{ + NSRunningApplication *frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication]; + NSString *bundleId = frontApp.bundleIdentifier; + const char * bundle = [bundleId UTF8String]; + + snprintf(buffer, buffer_size, "%s", bundle); + + [bundleId release]; + + return 1; +} \ No newline at end of file diff --git a/espanso-info/src/lib.rs b/espanso-info/src/lib.rs index c65643f..715045d 100644 --- a/espanso-info/src/lib.rs +++ b/espanso-info/src/lib.rs @@ -52,9 +52,9 @@ pub fn get_provider() -> Result> { } #[cfg(target_os = "macos")] -pub fn get_clipboard(_: ClipboardOptions) -> Result> { - info!("using CocoaClipboard"); - Ok(Box::new(cocoa::CocoaClipboard::new()?)) +pub fn get_provider() -> Result> { + info!("using CocoaAppInfoProvider"); + Ok(Box::new(cocoa::CocoaAppInfoProvider::new())) } #[cfg(target_os = "linux")]