use std::sync::Mutex;
use once_cell::sync::Lazy;
use crate::common::x_win_struct::{
icon_info::IconInfo, process_info::ProcessInfo, usage_info::UsageInfo, window_info::WindowInfo,
window_position::WindowPosition,
};
use super::common_api::get_gnome_version;
pub const GNOME_XWIN_UUID: &str = r#"x-win@miniben90.org"#;
pub static DESTINATION: std::option::Option<&str> = Some("org.gnome.Shell");
pub static XWIN_IFACE: std::option::Option<&str> =
Some("org.gnome.Shell.Extensions.XWinWaylandExtension");
pub static XWIN_PATH: &str = "/org/gnome/Shell/Extensions/XWinWaylandExtension";
pub static SHELL_PATH: &str = "/org/gnome/Shell";
pub static SHELL_IFACE: std::option::Option<&str> = Some("org.gnome.Shell.Extensions");
pub const GNOME_XWIN_EXTENSION_META: &str = r#"
{
"name": "@mininben90/x-win extended",
"description": "Get active and open window(s) informations for @miniben90/x-win node package.",
"uuid": "x-win@miniben90.org",
"shell-version": [ "42", "43", "44", "45", "46" ],
"url": "https://github.com/miniben-90/x-win.git",
"version-name": "1.8.0"
}
"#;
pub const GNOME_XWIN_EXTENSION_FOLDER_PATH: &str =
r#".local/share/gnome-shell/extensions/x-win@miniben90.org"#;
pub const GNOME_XWIN_GET_ICON_SCRIPT: &str = r#"function _get_icon(window_id) {
if (window_id) {
let meta_window = global.get_window_actors()
.filter(x => _filterWindow)
.find(x => x && x.get_meta_window && x.get_meta_window() && x.get_meta_window().get_id() === window_id)
.get_meta_window();
if (meta_window) {
const tracker = Shell.WindowTracker.get_default();
const window_app = tracker.get_window_app(meta_window);
if (window_app) {
const icon = window_app.get_icon();
if (icon) {
const iconTheme = new St.IconTheme();
const iconInfo = iconTheme.lookup_by_gicon(icon, 128, St.IconLookupFlags.FORCE_SIZE);
if (iconInfo) {
const pixBuf = iconInfo.load_icon();
if (pixBuf) {
const [ success, unitArray ] = pixBuf.save_to_bufferv('png', [], []);
if(success && unitArray.length) {
const data = GLib.base64_encode(unitArray);
if (data) {
return {
data: "data:image/png;base64," + data,
height: pixBuf.get_height(),
width: pixBuf.get_width(),
};
}
}
}
}
}
}
}
}
return {
data: "",
height: 0,
width: 0,
};
}"#;
pub const GNOME_XWIN_EVAL_SCRIPT: &str = r#"
const { Gio, GLib, Meta, Gtk: St, Shell } = imports.gi;
const AllowedWindow = [
Meta.WindowType.DESKTOP,
Meta.WindowType.OVERRIDE_OTHER,
Meta.WindowType.UTILITY,
Meta.WindowType.MODAL_DIALOG,
Meta.WindowType.DIALOG,
Meta.WindowType.NORMAL
];
function _filterWindow(x, index, array) {
if (x && x.get_meta_window && x.get_meta_window().get_window_type) {
return x.get_meta_window().get_window_type() !== -1;
} else {
return false;
}
}
function _get_open_windows() {
return global.get_window_actors()
.filter(_filterWindow)
.map(_strcut_data);
}
function get_open_windows() {
return Object(_get_open_windows());
}
function get_active_window() {
const activeWindow = global.get_window_actors().find(x => x.get_meta_window().has_focus() && _filterWindow(x));
return Object(_strcut_data(activeWindow));
}
function get_icon(window_id) {
const iconInfo = _get_icon(window_id);
return Object(iconInfo);
}
function _strcut_data(window_actor) {
if (window_actor && window_actor.get_meta_window) {
const _window = window_actor.get_meta_window();
const process_id = _window.get_pid ? _window.get_pid() : 0;
const info = _get_process_info(process_id);
return {
id: _window.get_id(),
os: 'linux',
info: {
process_id,
name: _window.get_wm_class ? _window.get_wm_class() : '',
path: info.path,
exec_name: info.exec_name,
},
title: _window.get_title ? _window.get_title() : '',
position: {
width: window_actor.get_width ? window_actor.get_width() : 0,
height: window_actor.get_height ? window_actor.get_height() : 0,
x: window_actor.get_x ? window_actor.get_x() : 0,
y: window_actor.get_y ? window_actor.get_y() : 0,
isFullScreen: _window.is_fullscreen(),
},
usage: { memory: _get_memory_usage(process_id) },
};
} else {
return {
id: 0,
os: 'linux',
title: '',
info: {
process_id: 0,
path: '',
exec_name: '',
name: '',
},
position: {
width: 0,
height: 0,
x: 0,
y: 0,
isFullScreen: false,
},
usage: { memory: 0 },
};
}
}
function _get_memory_usage(pid) {
const [isOk, contents] = GLib.file_get_contents(`/proc/${pid}/statm`);
if (isOk) {
return parseInt(contents.toString().split(' ')[0], 10);
}
return 0;
}
function _get_process_info(pid) {
try {
const path = GLib.file_read_link(`/proc/${pid}/exe`, null);
if (path) {
return {
path,
exec_name: path.split('/').pop(),
};
}
} catch (e) { }
return {
path: '',
exec_name: '',
};
}"#;
pub const GNOME_XWIN_EXTENSION_COMMON_SCRIPT: &str = r#"
const WaylandInterface = `
<node>
<interface name="org.gnome.Shell.Extensions.XWinWaylandExtension">
<method name="get_active_window">
<arg name="value" type="s" direction="out" />
</method>
<method name="get_open_windows">
<arg name="value" type="s" direction="out" />
</method>
<method name="get_icon">
<arg name="value" type="d" direction="in" />
<arg name="value" type="s" direction="out" />
</method>
</interface>
</node>
`;
const AllowedWindow = [
Meta.WindowType.DESKTOP,
Meta.WindowType.OVERRIDE_OTHER,
Meta.WindowType.UTILITY,
Meta.WindowType.MODAL_DIALOG,
Meta.WindowType.DIALOG,
Meta.WindowType.NORMAL
];
function _filterWindow(x, index, array) {
if (x && x.get_meta_window && x.get_meta_window().get_window_type) {
return x.get_meta_window().get_window_type() !== -1;
} else {
return false;
}
}
function _get_open_windows() {
return global.get_window_actors()
.filter(_filterWindow)
.map(_strcut_data);
}
function get_open_windows() {
return JSON.stringify(_get_open_windows());
}
function get_active_window() {
const activeWindow = global.get_window_actors().find(x => x.get_meta_window().has_focus() && _filterWindow(x));
return JSON.stringify(_strcut_data(activeWindow));
}
function get_icon(window_id) {
const iconInfo = _get_icon(window_id);
return JSON.stringify(iconInfo);
}
function _strcut_data(window_actor) {
if (window_actor && window_actor.get_meta_window) {
const _window = window_actor.get_meta_window();
const process_id = _window.get_pid ? _window.get_pid() : 0;
const info = _get_process_info(process_id);
return {
id: _window.get_id(),
os: 'linux',
info: {
process_id,
name: _window.get_wm_class ? _window.get_wm_class() : '',
path: info.path,
exec_name: info.exec_name,
},
title: _window.get_title ? _window.get_title() : '',
position: {
width: window_actor.get_width ? window_actor.get_width() : 0,
height: window_actor.get_height ? window_actor.get_height() : 0,
x: window_actor.get_x ? window_actor.get_x() : 0,
y: window_actor.get_y ? window_actor.get_y() : 0,
isFullScreen: _window.is_fullscreen(),
},
usage: { memory: _get_memory_usage(process_id) },
};
} else {
return {
id: 0,
os: 'linux',
title: '',
info: {
process_id: 0,
path: '',
exec_name: '',
name: '',
},
position: {
width: 0,
height: 0,
x: 0,
y: 0,
isFullScreen: false,
},
usage: { memory: 0 },
};
}
}
function _get_memory_usage(pid) {
const [isOk, contents] = GLib.file_get_contents(`/proc/${pid}/statm`);
if (isOk) {
const value = String.fromCharCode.apply(null, contents);
return parseInt(value.toString().split(' ')[0], 10);
}
return 0;
}
function _get_process_info(pid) {
try {
const path = GLib.file_read_link(`/proc/${pid}/exe`);
if (path) {
return {
path,
exec_name: path.split('/').pop(),
};
}
} catch (e) { }
return {
path: '',
exec_name: '',
};
}"#;
pub const GNOME_XWIN_EXTENSION_SCRIPT: &str = r#"const { Gio, GLib, Meta, Gtk: St, Shell } = imports.gi;
let _dbus = undefined;
function enable() {
_dbus = Gio.DBusExportedObject.wrapJSObject(
WaylandInterface,
this,
);
_dbus.export(
Gio.DBus.session,
'/org/gnome/Shell/Extensions/XWinWaylandExtension',
);
}
function disable() {
_dbus.flush();
_dbus.unexport();
_dbus = undefined;
}
function init() {
/** Do nothing */
}
"#;
pub const GNOME45_XWIN_EXTENSION_SCRIPT: &str = r#"import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import Meta from 'gi://Meta';
import St from "gi://St";
import Shell from "gi://Shell";
export default class XWinWaylandExtension extends Extension {
_dbus = undefined;
enable() {
this._dbus = Gio.DBusExportedObject.wrapJSObject(
WaylandInterface,
this,
);
this._dbus.export(
Gio.DBus.session,
'/org/gnome/Shell/Extensions/XWinWaylandExtension',
);
}
disable() {
this._dbus.flush();
this._dbus.unexport();
this._dbus = undefined;
}
get_open_windows() {
return get_open_windows();
}
get_active_window() {
return get_active_window();
}
get_icon(window_id) {
return get_icon(window_id);
}
}
"#;
fn number_to_u32(value: &serde_json::Map<String, serde_json::Value>, key: &str) -> u32 {
value_to_i64(value, key) as u32
}
fn number_to_i32(value: &serde_json::Map<String, serde_json::Value>, key: &str) -> i32 {
value_to_i64(value, key) as i32
}
fn value_to_i64(value: &serde_json::Map<String, serde_json::Value>, key: &str) -> i64 {
value.get(key).and_then(|v| v.as_i64()).unwrap_or(0)
}
fn str_to_string(value: &serde_json::Map<String, serde_json::Value>, key: &str) -> String {
value
.get(key)
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string()
}
pub fn value_to_icon_info(response: &serde_json::Value) -> Result<IconInfo, &'static str> {
let response = response.as_object().ok_or("Expected JSON object")?;
Ok(IconInfo {
data: str_to_string(response, "data"),
height: number_to_u32(response, "height"),
width: number_to_u32(response, "width"),
})
}
pub fn value_to_window_info(response: &serde_json::Value) -> Result<WindowInfo, &'static str> {
let response = response.as_object().ok_or("Expected JSON object")?;
let position = response["position"]
.as_object()
.ok_or("Expected JSON object")?;
let info = response["info"].as_object().ok_or("Expected JSON object")?;
let usage = response["usage"]
.as_object()
.ok_or("Expected JSON object")?;
Ok(WindowInfo {
id: number_to_u32(response, "id"),
os: str_to_string(response, "os"),
title: str_to_string(response, "title"),
position: WindowPosition {
height: number_to_i32(position, "height"),
width: number_to_i32(position, "width"),
x: number_to_i32(position, "x"),
y: number_to_i32(position, "y"),
is_full_screen: position
.get("isFullScreen")
.and_then(|v| v.as_bool())
.unwrap_or(false),
},
info: ProcessInfo {
exec_name: str_to_string(info, "exec_name"),
name: str_to_string(info, "name"),
path: str_to_string(info, "path"),
process_id: number_to_u32(info, "process_id"),
},
usage: UsageInfo {
memory: number_to_u32(usage, "memory"),
},
})
}
pub struct GnomeVersion {
pub version: u32,
pub use_eval: bool,
}
impl GnomeVersion {
fn new() -> Self {
let version = get_gnome_version();
let version: u32 = version.split(".").collect::<Vec<&str>>()[0]
.parse()
.unwrap_or(999);
let use_eval = version < 41;
Self { use_eval, version }
}
}
pub static GNOME_SINGLETON: Lazy<Mutex<GnomeVersion>> =
Lazy::new(|| Mutex::new(GnomeVersion::new()));
#[cfg(test)]
mod tests {
use crate::common::x_win_struct::{
icon_info::IconInfo, process_info::ProcessInfo, usage_info::UsageInfo, window_info::WindowInfo,
window_position::WindowPosition,
};
use crate::linux::api::gnome_shell::{
number_to_i32, number_to_u32, str_to_string, value_to_icon_info, value_to_window_info,
};
#[test]
fn test_str_to_string() -> Result<(), Box<dyn std::error::Error>> {
let title = {
let value: serde_json::Value = serde_json::from_str("{}")?;
let value = value.as_object().ok_or("failed to test")?;
str_to_string(value, "title")
};
assert_eq!(title, "");
let title = {
let value: serde_json::Value = serde_json::from_str("{\"title\":\"\"}")?;
let value = value.as_object().ok_or("failed to test")?;
str_to_string(value, "title")
};
assert_eq!(title, "");
let title = {
let value: serde_json::Value = serde_json::from_str("{\"title\":\"test\"}")?;
let value = value.as_object().ok_or("failed to test")?;
str_to_string(value, "title")
};
assert_eq!(title, "test");
Ok(())
}
#[test]
fn test_number_to_u32() -> Result<(), Box<dyn std::error::Error>> {
let width = {
let value: serde_json::Value = serde_json::from_str("{}")?;
let value = value.as_object().ok_or("failed to test")?;
number_to_u32(value, "width")
};
assert_eq!(width, 0);
let width = {
let value: serde_json::Value = serde_json::from_str("{\"width\":\"\"}")?;
let value = value.as_object().ok_or("failed to test")?;
number_to_u32(value, "width")
};
assert_eq!(width, 0);
let width = {
let value: serde_json::Value = serde_json::from_str("{\"width\":\"test\"}")?;
let value = value.as_object().ok_or("failed to test")?;
number_to_u32(value, "width")
};
assert_eq!(width, 0);
let width = {
let value: serde_json::Value = serde_json::from_str("{\"width\":0}")?;
let value = value.as_object().ok_or("failed to test")?;
number_to_u32(value, "width")
};
assert_eq!(width, 0);
let width = {
let value: serde_json::Value = serde_json::from_str("{\"width\":0.0}")?;
let value = value.as_object().ok_or("failed to test")?;
number_to_u32(value, "width")
};
assert_eq!(width, 0);
let width = {
let value: serde_json::Value = serde_json::from_str("{\"width\":1000}")?;
let value = value.as_object().ok_or("failed to test")?;
number_to_u32(value, "width")
};
assert_eq!(width, 1000);
let width = {
let value: serde_json::Value = serde_json::from_str("{\"width\":\"1000\"}")?;
let value = value.as_object().ok_or("failed to test")?;
number_to_u32(value, "width")
};
assert_eq!(width, 0);
Ok(())
}
#[test]
fn test_number_to_i32() -> Result<(), Box<dyn std::error::Error>> {
let width = {
let value: serde_json::Value = serde_json::from_str("{}")?;
let value = value.as_object().ok_or("failed to test")?;
number_to_i32(value, "width")
};
assert_eq!(width, 0);
let width = {
let value: serde_json::Value = serde_json::from_str("{\"width\":\"\"}")?;
let value = value.as_object().ok_or("failed to test")?;
number_to_i32(value, "width")
};
assert_eq!(width, 0);
let width = {
let value: serde_json::Value = serde_json::from_str("{\"width\":\"test\"}")?;
let value = value.as_object().ok_or("failed to test")?;
number_to_i32(value, "width")
};
assert_eq!(width, 0);
let width = {
let value: serde_json::Value = serde_json::from_str("{\"width\":0}")?;
let value = value.as_object().ok_or("failed to test")?;
number_to_i32(value, "width")
};
assert_eq!(width, 0);
let width = {
let value: serde_json::Value = serde_json::from_str("{\"width\":0.0}")?;
let value = value.as_object().ok_or("failed to test")?;
number_to_i32(value, "width")
};
assert_eq!(width, 0);
let width = {
let value: serde_json::Value = serde_json::from_str("{\"width\":1000}")?;
let value = value.as_object().ok_or("failed to test")?;
number_to_i32(value, "width")
};
assert_eq!(width, 1000);
let width = {
let value: serde_json::Value = serde_json::from_str("{\"width\":\"1000\"}")?;
let value = value.as_object().ok_or("failed to test")?;
number_to_i32(value, "width")
};
assert_eq!(width, 0);
Ok(())
}
#[test]
fn test_value_to_icon_info() -> Result<(), Box<dyn std::error::Error>> {
let icon_info = {
let value: serde_json::Value = serde_json::from_str("{}")?;
value_to_icon_info(&value)?
};
assert_eq!(
icon_info,
IconInfo {
data: String::from(""),
height: 0,
width: 0,
}
);
let icon_info = {
let value: serde_json::Value =
serde_json::from_str("{\"data\": \"test\", \"width\": 100, \"height\": 200}")?;
value_to_icon_info(&value)?
};
assert_eq!(
icon_info,
IconInfo {
data: String::from("test"),
height: 200,
width: 100,
}
);
let icon_info = {
let value: serde_json::Value = serde_json::from_str("{\"width\": 100, \"height\": 200}")?;
value_to_icon_info(&value)?
};
assert_eq!(
icon_info,
IconInfo {
data: String::from(""),
height: 200,
width: 100,
}
);
let icon_info = {
let value: serde_json::Value = serde_json::from_str("{\"data\": \"test\", \"height\": 200}")?;
value_to_icon_info(&value)?
};
assert_eq!(
icon_info,
IconInfo {
data: String::from("test"),
height: 200,
width: 0,
}
);
let icon_info = {
let value: serde_json::Value = serde_json::from_str("{\"data\": \"test\", \"width\": 100}")?;
value_to_icon_info(&value)?
};
assert_eq!(
icon_info,
IconInfo {
data: String::from("test"),
height: 0,
width: 100,
}
);
Ok(())
}
#[test]
fn test_value_to_window_info() -> Result<(), Box<dyn std::error::Error>> {
let window_info = {
let value: serde_json::Value = serde_json::from_str(
r#"{"id":0,"os":"","info":{"process_id":0,"name":"","path":"","exec_name":""},"title":"","position":{"width":0,"height":0,"x":0,"y":0,"isFullScreen":false},"usage":{"memory":0}}"#,
)?;
value_to_window_info(&value)?
};
assert_eq!(
window_info,
WindowInfo::new(
0,
String::from(""),
String::from(""),
WindowPosition::new(0, 0, 0, 0, false),
ProcessInfo::new(0, String::from(""), String::from(""), String::from("")),
UsageInfo::new(0)
)
);
let window_info = {
let value: serde_json::Value = serde_json::from_str(
r#"{"id":0,"os":"","info":{"process_id":0,"path":"","exec_name":""},"title":"","position":{"width":0,"height":0,"x":0,"y":0,"isFullScreen":false},"usage":{"memory":0}}"#,
)?;
value_to_window_info(&value)?
};
assert_eq!(
window_info,
WindowInfo::new(
0,
String::from(""),
String::from(""),
WindowPosition::new(0, 0, 0, 0, false),
ProcessInfo::new(0, String::from(""), String::from(""), String::from("")),
UsageInfo::new(0)
)
);
let window_info = {
let value: serde_json::Value = serde_json::from_str(
r#"{"id":3038270935,"os":"linux","info":{"process_id":95389,"name":"gnome-terminal-server","path":"/usr/libexec/gnome-terminal-server","exec_name":"gnome-terminal-server"},"title":"new terminal","position":{"width":866,"height":629,"x":1077,"y":192,"isFullScreen":true},"usage":{"memory":139473}}"#,
)?;
value_to_window_info(&value)?
};
assert_eq!(
window_info,
WindowInfo::new(
3038270935,
String::from("linux"),
String::from("new terminal"),
WindowPosition::new(1077, 192, 866, 629, true),
ProcessInfo::new(
95389,
String::from("/usr/libexec/gnome-terminal-server"),
String::from("gnome-terminal-server"),
String::from("gnome-terminal-server")
),
UsageInfo::new(139473)
)
);
Ok(())
}
}