From 9ce453e58bb04f6114d622a100176d09386b4ea4 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Sat, 20 Mar 2021 21:47:07 +0100 Subject: [PATCH] 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 {