diff --git a/.cargo/config.toml b/.cargo/config.toml index babccd6..522eab3 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,6 @@ [target.x86_64-unknown-linux-musl] -rustflags = [ "-C", "target-feature=-crt-static"] +rustflags = ["-C", "target-feature=-crt-static", "-lm"] [target.aarch64-unknown-linux-musl] linker = "aarch64-linux-musl-gcc" -rustflags = ["-C", "target-feature=-crt-static"] \ No newline at end of file +rustflags = ["-C", "target-feature=-crt-static", "-lm"] \ No newline at end of file diff --git a/.github/docker-ubuntu-rust-install.sh b/.github/docker-ubuntu-rust-install.sh new file mode 100644 index 0000000..84facdf --- /dev/null +++ b/.github/docker-ubuntu-rust-install.sh @@ -0,0 +1,25 @@ +#!bin/bash + +target=${1:-''} + +curl https://sh.rustup.rs -sSf | bash -s -- -y + +. "$HOME/.cargo/env" + +sudo apt update + +sudo apt upgrade -y + +sudo apt install musl-tools -y + +sudo apt install libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxcb-shm0-dev pkg-config build-essential -y + +rustup target add $target + +curl -L -o /tmp/firefox.tar.bz2 "https://download.mozilla.org/?product=firefox-latest&os=linux64" + +sudo tar xjf /tmp/firefox.tar.bz2 -C /opt/ + +/opt/firefox/firefox --safe-mode https://github.com/ & + +sleep 1 diff --git a/.github/workflows/CI.yml b/.github/workflows/ci-napi.yml similarity index 94% rename from .github/workflows/CI.yml rename to .github/workflows/ci-napi.yml index aa3accd..f84ba13 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/ci-napi.yml @@ -1,4 +1,4 @@ -name: Build & Test +name: Build & Test & publich node package "@miniben90/x-win" env: DEBUG: napi:* @@ -67,6 +67,14 @@ jobs: yarn build --target x86_64-unknown-linux-gnu && strip *.node + # Build for linux (using Ubuntu) (x64) + - host: ubuntu-latest + target: x86_64-unknown-linux-musl + build: |- + set -e && + yarn build --target x86_64-unknown-linux-musl && + strip *.node + # Disabled problem with lxcb # # Build for linux (using Ubuntu) (arm64) # - host: ubuntu-latest @@ -149,6 +157,14 @@ jobs: if: ${{ !matrix.settings.docker }} run: yarn install + # Lint Rust code + - name: Ceheck lint Rust + run: cargo fmt -- --check + + # Check clippy code + - name: Check clippy code + run: cargo clippy -- -D warnings + # Build the project - name: Build if: ${{ !matrix.settings.docker }} @@ -169,7 +185,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: bindings-${{ matrix.settings.target }} - path: ${{ env.APP_NAME }}.*.node + path: ${{ github.workspace }}/${{ env.APP_NAME }}.*.node if-no-files-found: error test: @@ -303,7 +319,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: bindings-universal-apple-darwin - path: ${{ env.APP_NAME }}.*.node + path: ${{ github.workspace }}/${{ env.APP_NAME }}.*.node if-no-files-found: error publish: diff --git a/.github/workflows/ci-rs.yml b/.github/workflows/ci-rs.yml new file mode 100644 index 0000000..ec38d10 --- /dev/null +++ b/.github/workflows/ci-rs.yml @@ -0,0 +1,187 @@ +name: Build & Test & publich Rust package "x-win" + +env: + APP_NAME: x-win + MACOSX_DEPLOYMENT_TARGET: "10.13" + +on: + push: + branches: + - main + tags-ignore: + - "**" + paths-ignore: + - "**/*.md" + - LICENSE + - "**/*.gitignore" + - .editorconfig + - docs/** + pull_request: null + release: + types: [published] + +jobs: + build-n-test: + defaults: + run: + working-directory: ${{ github.workspace }}/x-win-rs + strategy: + fail-fast: false + matrix: + settings: + # Build matrix for MacOS (x64) + - host: macos-latest + target: x86_64-apple-darwin + + # Build matrix for MacOS (arm64) + - host: macos-latest + target: aarch64-apple-darwin + + # Build matrix for Windows (x64) + - host: windows-latest + target: x86_64-pc-windows-msvc + + # Build matrix for Windows (x32) + - host: windows-latest + target: i686-pc-windows-msvc + architecture: "x86" + + # Build matrix for Windows (arm64) + - host: windows-latest + target: aarch64-pc-windows-msvc + + # Build for linux (using Ubuntu) (x64) + - host: ubuntu-latest + target: x86_64-unknown-linux-gnu + image: miniben90/ubuntu-dummy-desktop:latest + + # Build for linux (using Ubuntu) (x64) + - host: ubuntu-latest + target: x86_64-unknown-linux-musl + image: miniben90/ubuntu-dummy-desktop:latest + + name: stable - ${{ matrix.settings.target }} + runs-on: ${{ matrix.settings.host }} + steps: + # Add actions/checkout + - uses: actions/checkout@v4 + + # Install lib dev required for compilation of the project + - name: (Linux) Install libx11 & libxcb for building + if: ${{ matrix.settings.host == 'ubuntu-latest' }} + run: | + sudo apt-get update + sudo apt-get install -y libx11-dev libxcb-ewmh-dev libxcb-randr0-dev + + # Install Rust version + - name: Install Rust + if: ${{ !matrix.settings.docker }} + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + targets: ${{ matrix.settings.target }} + + # Set Cache cargo + - name: Cache cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + .cargo-cache + target/ + key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }} + + # Lint Rust code + - name: Lint + run: cargo fmt -- --check + + # Check clippy code + - name: Clippy + run: cargo clippy -- -D warnings + + - name: Open finder to have an active window (MacOS) + if: ${{ matrix.settings.host == 'macos-latest' }} + run: open . + + - name: Open explorer to have an active window (windows) + if: ${{ matrix.settings.host == 'windows-latest' }} + run: explorer . + + - name: Test + if: ${{ matrix.settings.host != 'ubuntu-latest' }} + run: cargo test + + - name: Test (x86_64-unknown-linux-gnu & x86_64-unknown-linux-musl) + if: ${{ matrix.settings.target == 'x86_64-unknown-linux-gnu' || matrix.settings.target == 'x86_64-unknown-linux-musl' }} + uses: addnab/docker-run-action@v3 + with: + image: ${{ matrix.settings.image }} + options: --user 0:0 -v ${{ github.workspace }}/.github/docker-ubuntu-rust-install.sh:/usr/local/docker-ubuntu-rust-install.sh -v ${{ github.workspace }}/x-win-rs:/work -w /work + run: | + Xvfb :0 & + sleep 1 + gpg-agent --daemon + sleep 1 + xfce4-session & + sleep 1 + sh /usr/local/docker-ubuntu-rust-install.sh ${{ matrix.settings.target }} + . "$HOME/.cargo/env" + cp /work /x-win -Rf + cd /x-win + cargo test --target ${{ matrix.settings.target }} + + - name: Build + run: cargo build --release --target ${{ matrix.settings.target }} + + # Upload artifact for the next steps (test and publish) + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: build-rs-${{ matrix.settings.host }}-${{ matrix.settings.target }} + path: ${{ github.workspace }}/x-win-rs/target/${{ matrix.settings.target }} + if-no-files-found: error + + publish: + defaults: + run: + working-directory: ${{ github.workspace }}/x-win-rs + if: ${{ github.event_name == 'release' && github.ref_type == 'tag' }} + name: Publish ${{ github.ref_name }} (${{ github.ref }}) + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + TAG: ${{ github.ref_name }} + needs: + - build-n-test + steps: + - uses: actions/checkout@v4 + + # Download artifacts from build part + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: ${{ github.workspace }}/x-win-rs/target + + # Setup node version from matrix.node and install deps + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: 20 + check-latest: true + cache: yarn + + # Install Rust version + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + + # Update Cargo.toml version + - name: Set version to package.json + run: node ${{ github.workspace }}/.scripts/before-publish.cjs x-win-rs + + - name: Publish package + run: cargo publish diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..e001b84 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +cd x-win-rs && cargo fmt -- --check && cargo clippy -- -D warnings && cargo test +cargo fmt -- --check +cargo clippy -- -D warnings +yarn test \ No newline at end of file diff --git a/.scripts/before-publish.cjs b/.scripts/before-publish.cjs index 51195db..ddd6aba 100644 --- a/.scripts/before-publish.cjs +++ b/.scripts/before-publish.cjs @@ -1,26 +1,65 @@ const path = require('node:path'); const fs = require('node:fs'); +const { argv } = require('node:process'); -console.log('[BEFORE-PUBLISH]', 'Update package.json and cargo.toml version...'); +function raplceCargoVersion(cargoToml, version) { + return cargoToml.replace(/version\s*=\s*".*?"/, `version = "${version}"`); +} + +if (process.env.TAG) { + const version = process.env.TAG.startsWith('v') ? process.env.TAG.substring(1) : process.env.TAG; -const packageJsonPath = path.join(process.cwd(), 'package.json'); -const cargoTomlPath = path.join(process.cwd(), 'Cargo.toml'); + if (argv && argv.findIndex(v => v === 'x-win-rs') !== -1) { + console.log('[BEFORE-PUBLISH]', 'Update cargo.toml version to', version); -const packageJson = require(packageJsonPath); + const cargoTomlPath = path.join(process.cwd(), process.cwd().endsWith('x-win-rs') ? '' : 'x-win-rs', 'Cargo.toml'); + const cargoToml = fs.readFileSync(cargoTomlPath, { encoding: 'utf8' }); -if (packageJson && process.env.TAG) { - const version = process.env.TAG.startsWith('v') ? process.env.TAG.substring(1) : process.env.TAG; - console.log('[BEFORE-PUBLISH]', 'Update package.json version to', version); - packageJson.version = version; - fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, undefined, 2)); - - const cargoToml = fs.readFileSync(cargoTomlPath, { encoding: 'utf8' }); - console.log('[BEFORE-PUBLISH]', 'Update cargo.toml version to', version); - const newCargoToml = cargoToml.replace(/version\s*=\s*".*?"/, `version = "${version}"`); - fs.writeFileSync(cargoTomlPath, newCargoToml); + console.log('[BEFORE-PUBLISH]', 'Updating cargo.toml'); + + const newCargoToml = raplceCargoVersion(cargoToml, version); + fs.writeFileSync(cargoTomlPath, newCargoToml); + + console.log('[BEFORE-PUBLISH]', 'Cargo.toml updated.'); + console.log('[BEFORE-PUBLISH]', 'Finished.'); + } else { + console.log('[BEFORE-PUBLISH]', 'Update package.json and cargo.toml version to', version); + + const packageJsonPath = path.join(process.cwd(), 'package.json'); + const cargoTomlPath = path.join(process.cwd(), 'Cargo.toml'); + + if (!fs.existsSync(packageJsonPath)) { + console.error('package.json file not found!'); + process.exit(1); + } + + if (!fs.existsSync(cargoTomlPath)) { + console.error('package.json file not found!'); + process.exit(1); + } + + const packageJson = require(packageJsonPath); + console.log('[BEFORE-PUBLISH]', 'Updating package.json...'); + + packageJson.version = version; + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, undefined, 2)); + + console.log('[BEFORE-PUBLISH]', 'package.json updated.'); + + const cargoToml = fs.readFileSync(cargoTomlPath, { encoding: 'utf8' }); + + console.log('[BEFORE-PUBLISH]', 'Updating cargo.toml'); + + const newCargoToml = raplceCargoVersion(cargoToml, version); + fs.writeFileSync(cargoTomlPath, newCargoToml); + + console.log('[BEFORE-PUBLISH]', 'Cargo.toml updated.'); + console.log('[BEFORE-PUBLISH]', 'Finished.'); + } } else { - console.error('package or TAG env not found!'); + console.error('TAG env not found!'); process.exit(1); } -console.log('[BEFORE-PUBLISH]', 'Finished.'); \ No newline at end of file +/** Exist process with success */ +process.exit(0); \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 6ffc1d8..2d97515 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" -name = "x-win" -version = "1.6.0" +name = "x-win-napi" +version = "0.0.0" authors = ["BENKHADRA Hocine "] keywords = ["window", "active", "current", "position", "title", "list", "open"] description = "This package allows you to retrieve precise information about active and open windows on Windows, MacOS, and Linux. You can obtain the position, size, title, and other memory of windows." @@ -12,48 +12,14 @@ crate-type = ["cdylib"] [dependencies] # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix -napi = { version = "2.16.8", default-features = false, features = ["napi4", "async"] } -napi-derive = "2.16.8" +napi = { version = "2.16.8", default-features = false, features = [ + "napi4", + "async", +] } +napi-derive = "2.16.9" once_cell = "1.19.0" base64 = "0.22.1" - -[target.'cfg(target_os = "windows")'.dependencies] -windows = { version = "0.58.0", features = [ - "Win32_Foundation", - "Win32_UI_WindowsAndMessaging", - "Win32_System_Threading", - "Win32_Storage_FileSystem", - "Win32_System_ProcessStatus", - "Win32_System_StationsAndDesktops", - "Win32_UI_Input_KeyboardAndMouse", - "Win32_Graphics_Dwm", - "UI_UIAutomation", - "Win32_System_Com", - "Win32_UI_Accessibility", - "Win32_System_Ole", - "Win32_UI_Shell_PropertiesSystem", - "Win32_UI_Shell_Common", - "Win32_System_Variant", - "Win32_Graphics_Gdi", - "Win32_Graphics_Imaging" -]} -png = "0.17.13" - -[target.'cfg(target_os = "linux")'.dependencies] -xcb = { version = "1.4.0" } -x11 = { version = "2.21.0", features = ["xlib"], optional = true } -zbus = { version = "1.9.2" } -serde_json = { version = "1.0.117" } -image = "0.25.1" - -[target.'cfg(target_os = "macos")'.dependencies] -cocoa = "0.25.0" -libc = "0.2.155" -cocoa-foundation = "0.1.2" -core-foundation = "0.9.4" -core-foundation-sys = "0.8.6" -core-graphics = "0.23.2" -objc = "0.2.7" +x-win = { path = "./x-win-rs" } [build-dependencies] napi-build = "2.1.3" diff --git a/LICENSE b/LICENSE index 2ff15e7..9cf1062 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,5 @@ MIT License -Copyright (c) 2023 BENKHADRA Hocine - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/README.md b/README.md index 863c575..3fb5a33 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # @miniben90/x-win - -[![CI](https://github.com/miniben-90/x-win/actions/workflows/CI.yml/badge.svg)](https://github.com/miniben-90/x-win/actions/workflows/CI.yml) +[![ci-rs](https://github.com/miniben-90/x-win/actions/workflows/ci-rs.yml/badge.svg)](https://github.com/miniben-90/x-win/actions/workflows/ci-rs.yml) +[![ci-napi](https://github.com/miniben-90/x-win/actions/workflows/ci-napi.yml/badge.svg)](https://github.com/miniben-90/x-win/actions/workflows/ci-napi.yml) [![Node version](https://img.shields.io/node/v/@miniben90/x-win.svg)](https://www.npmjs.com/package/@miniben90/x-win) ![npm type definitions](https://img.shields.io/npm/types/@miniben90/x-win) ![NPM License](https://img.shields.io/npm/l/@miniben90/x-win) @@ -183,13 +183,11 @@ Dependencies are required to be installed for development purposes. sudo apt install libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxcb-shm0-dev pkg-config build-essential ``` -> ⚠️**Warning** -> libc.so.6 is needed - -> ⚠️**Warning** +> ⚠️**Warning**
+> libc.so.6 is needed
+> ⚠️**Warning**
> Recovery url is not available on linux - ### GNOME (Wayland) In order to recover data, you'll need to install and activate an extension designed for systems running GNOME version 41 or newer (as evaluation is disabled from this version onward). diff --git a/index.d.ts b/index.d.ts index e97ae28..8602a88 100644 --- a/index.d.ts +++ b/index.d.ts @@ -4,10 +4,12 @@ /* auto-generated by NAPI-RS */ /** - * Struct to store usage data of the window + * Struct to store process information of the window */ -export interface UsageInfo { - memory: number +export interface IconInfo { + data: string + height: number + width: number } /** * Struct to store process information of the window @@ -18,6 +20,12 @@ export interface ProcessInfo { name: string execName: string } +/** + * Struct to store usage data of the window +*/ +export interface UsageInfo { + memory: number +} /** * Struct to store position and size of the window */ @@ -28,14 +36,6 @@ export interface WindowPosition { height: number isFullScreen: boolean } -/** - * Struct to store process information of the window -*/ -export interface IconInfo { - data: string - height: number - width: number -} /** * Retrieve information the about currently active window. * Returns an object of `WindowInfo`. diff --git a/npm/linux-x64-musl/README.md b/npm/linux-x64-musl/README.md new file mode 100644 index 0000000..c93eb43 --- /dev/null +++ b/npm/linux-x64-musl/README.md @@ -0,0 +1,3 @@ +# `@miniben90/x-win-linux-x64-musl` + +This is the **x86_64-unknown-linux-musl** binary for `@miniben90/x-win` diff --git a/npm/linux-x64-musl/package.json b/npm/linux-x64-musl/package.json new file mode 100644 index 0000000..69bda07 --- /dev/null +++ b/npm/linux-x64-musl/package.json @@ -0,0 +1,28 @@ +{ + "name": "@miniben90/x-win-linux-x64-musl", + "version": "1.3.1", + "os": [ + "linux" + ], + "cpu": [ + "x64" + ], + "main": "x-win.linux-x64-musl.node", + "files": [ + "x-win.linux-x64-musl.node" + ], + "description": "This package allows you to retrieve precise information about active and open windows on Windows, MacOS, and Linux. You can obtain the position, size, title, and other memory of windows.", + "author": { + "name": "BENKHADRA Hocine", + "email": "contact@benkhadra.com", + "url": "https://benkhadra.com/" + }, + "license": "MIT", + "engines": { + "node": ">= 14" + }, + "repository": "https://github.com/miniben-90/x-win.git", + "libc": [ + "musl" + ] +} \ No newline at end of file diff --git a/package.json b/package.json index d3a357e..c94d39c 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "aarch64-apple-darwin", "aarch64-pc-windows-msvc", "i686-pc-windows-msvc", - "universal-apple-darwin" + "universal-apple-darwin", + "x86_64-unknown-linux-musl" ] } }, @@ -42,12 +43,15 @@ ], "devDependencies": { "@napi-rs/cli": "^2.18.3", - "ava": "^6.1.3" + "ava": "^6.1.3", + "husky": "^9.1.4" }, "ava": { "timeout": "3m", "failFast": true, - "files": ["__test__/**/*.mjs"] + "files": [ + "__test__/**/*.mjs" + ] }, "engines": { "node": ">= 14" @@ -59,7 +63,8 @@ "prepublishOnly": "napi prepublish -t npm", "test": "ava", "universal": "napi universal", - "version": "napi version" + "version": "napi version", + "prepare": "husky" }, "packageManager": "yarn@4.1.1" } diff --git a/src/common/mod.rs b/src/common/mod.rs index 696b950..7ec45a7 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,5 +1,4 @@ #![deny(unused_imports)] +pub mod thread; pub mod x_win_struct; -pub mod api; -pub mod thread; \ No newline at end of file diff --git a/src/common/thread.rs b/src/common/thread.rs index d878441..9c1afc3 100644 --- a/src/common/thread.rs +++ b/src/common/thread.rs @@ -35,7 +35,7 @@ impl ThreadManager { let sender_ = sender.clone(); let handle = thread::spawn(move || { - work(receiver); + work(receiver); }); threads_clone.lock().unwrap().insert(key, sender_); let threads_clone_for_cleanup = Arc::clone(&self.threads); @@ -63,7 +63,9 @@ impl ThreadManager { pub fn stop_all_threads(&self) -> Result<(), String> { let threads = self.threads.lock().unwrap(); for (_, sender) in threads.iter() { - sender.send(()).map_err(|_| "Failed to send stop signal.".to_string())?; + sender + .send(()) + .map_err(|_| "Failed to send stop signal.".to_string())?; } Ok(()) } diff --git a/src/common/x_win_struct/icon_info.rs b/src/common/x_win_struct/icon_info.rs index 2498c61..beb2a73 100644 --- a/src/common/x_win_struct/icon_info.rs +++ b/src/common/x_win_struct/icon_info.rs @@ -21,3 +21,23 @@ impl IconInfo { } } } + +impl From for IconInfo { + fn from(value: x_win::IconInfo) -> Self { + IconInfo { + data: value.data, + height: value.height, + width: value.width, + } + } +} + +impl From for x_win::IconInfo { + fn from(value: IconInfo) -> Self { + x_win::IconInfo { + data: value.data, + height: value.height, + width: value.width, + } + } +} diff --git a/src/common/x_win_struct/mod.rs b/src/common/x_win_struct/mod.rs index bf5c2d9..9a8aaaa 100644 --- a/src/common/x_win_struct/mod.rs +++ b/src/common/x_win_struct/mod.rs @@ -1,7 +1,7 @@ #![deny(unused_imports)] -pub mod usage_info; +pub mod icon_info; pub mod process_info; -pub mod window_position; +pub mod usage_info; pub mod window_info; -pub mod icon_info; +pub mod window_position; diff --git a/src/common/x_win_struct/process_info.rs b/src/common/x_win_struct/process_info.rs index d03a32e..b9e5ed1 100644 --- a/src/common/x_win_struct/process_info.rs +++ b/src/common/x_win_struct/process_info.rs @@ -15,6 +15,33 @@ pub struct ProcessInfo { impl ProcessInfo { pub fn new(process_id: u32, path: String, name: String, exec_name: String) -> Self { - Self { process_id, path, name, exec_name } + Self { + process_id, + path, + name, + exec_name, + } + } +} + +impl From for ProcessInfo { + fn from(value: x_win::ProcessInfo) -> Self { + ProcessInfo { + exec_name: value.exec_name, + name: value.name, + path: value.path, + process_id: value.process_id, + } + } +} + +impl From for x_win::ProcessInfo { + fn from(value: ProcessInfo) -> Self { + x_win::ProcessInfo { + exec_name: value.exec_name, + name: value.name, + path: value.path, + process_id: value.process_id, + } } } diff --git a/src/common/x_win_struct/usage_info.rs b/src/common/x_win_struct/usage_info.rs index daae758..c3ea596 100644 --- a/src/common/x_win_struct/usage_info.rs +++ b/src/common/x_win_struct/usage_info.rs @@ -14,3 +14,19 @@ impl UsageInfo { Self { memory } } } + +impl From for UsageInfo { + fn from(value: x_win::UsageInfo) -> Self { + UsageInfo { + memory: value.memory, + } + } +} + +impl From for x_win::UsageInfo { + fn from(value: UsageInfo) -> Self { + x_win::UsageInfo { + memory: value.memory, + } + } +} diff --git a/src/common/x_win_struct/window_info.rs b/src/common/x_win_struct/window_info.rs index 3f9aa4a..71e86f8 100644 --- a/src/common/x_win_struct/window_info.rs +++ b/src/common/x_win_struct/window_info.rs @@ -39,3 +39,31 @@ impl WindowInfo { } } } + +impl From for WindowInfo { + fn from(value: x_win::WindowInfo) -> Self { + WindowInfo { + id: value.id, + info: value.info.into(), + os: value.os, + title: value.title, + position: value.position.into(), + url: value.url, + usage: value.usage.into(), + } + } +} + +impl From for x_win::WindowInfo { + fn from(value: WindowInfo) -> Self { + x_win::WindowInfo { + id: value.id, + info: value.info.into(), + os: value.os, + title: value.title, + position: value.position.into(), + url: value.url, + usage: value.usage.into(), + } + } +} diff --git a/src/common/x_win_struct/window_position.rs b/src/common/x_win_struct/window_position.rs index a4b4061..b3145c1 100644 --- a/src/common/x_win_struct/window_position.rs +++ b/src/common/x_win_struct/window_position.rs @@ -23,4 +23,28 @@ impl WindowPosition { is_full_screen, } } -} \ No newline at end of file +} + +impl From for WindowPosition { + fn from(value: x_win::WindowPosition) -> Self { + WindowPosition { + x: value.x, + y: value.y, + width: value.width, + height: value.height, + is_full_screen: value.is_full_screen, + } + } +} + +impl From for x_win::WindowPosition { + fn from(value: WindowPosition) -> Self { + x_win::WindowPosition { + x: value.x, + y: value.y, + width: value.width, + height: value.height, + is_full_screen: value.is_full_screen, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index d976d91..2bebeda 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,41 +2,15 @@ //#![deny(clippy::all)] //#![allow(unused_imports)] -#[cfg(target_os = "macos")] -#[macro_use] -extern crate objc; - -#[cfg(target_os = "macos")] -#[macro_use] -extern crate core; +mod common; use common::{ - api::{empty_entity, API}, thread::ThreadManager, x_win_struct::{icon_info::IconInfo, window_info::WindowInfo}, }; use napi::{bindgen_prelude::AsyncTask, JsFunction, Result, Task}; use napi_derive::napi; - -mod common; - -#[cfg(target_os = "windows")] -mod win32; - -#[cfg(target_os = "linux")] -mod linux; - -#[cfg(target_os = "macos")] -mod macos; - -#[cfg(target_os = "windows")] -use win32::init_platform_api; - -#[cfg(target_os = "linux")] -use linux::init_platform_api; - -#[cfg(target_os = "macos")] -use macos::init_platform_api; +use x_win::{empty_entity, get_active_window, get_open_windows, get_window_icon}; #[macro_use] extern crate napi_derive; @@ -104,8 +78,8 @@ impl Task for GetIconTask { } fn get_icon(window_info: &WindowInfo) -> Result { - let api = init_platform_api(); - Ok(api.get_app_icon(&window_info)) + let t: x_win::WindowInfo = window_info.clone().into(); + Ok(get_window_icon(&t).unwrap().into()) } #[napi] @@ -115,7 +89,7 @@ impl WindowInfo { */ #[napi] pub fn get_icon(&self) -> Result { - get_icon(&self) + get_icon(self) } /** @@ -158,8 +132,7 @@ impl WindowInfo { */ #[napi] pub fn active_window() -> Result { - let api = init_platform_api(); - Ok(api.get_active_window()) + Ok(get_active_window().unwrap().into()) } /** @@ -231,8 +204,13 @@ pub fn active_window_async() -> AsyncTask { */ #[napi] pub fn open_windows() -> Result> { - let api = init_platform_api(); - Ok(api.get_open_windows()) + Ok( + get_open_windows() + .unwrap() + .into_iter() + .map(WindowInfo::from) + .collect(), + ) } /** @@ -320,7 +298,6 @@ pub fn open_windows_async() -> AsyncTask { */ #[napi(ts_args_type = "callback: (info: WindowInfo) => void")] pub fn subscribe_active_window(callback: JsFunction) -> Result { - let api = init_platform_api(); let tsfn: ThreadsafeFunction = callback .create_threadsafe_function( 0, @@ -332,14 +309,14 @@ pub fn subscribe_active_window(callback: JsFunction) -> Result { let thread_manager = THREAD_MANAGER.lock().unwrap(); let id = thread_manager.start_thread(move |receiver| { - let mut current_window: WindowInfo = empty_entity(); + let mut current_window: WindowInfo = empty_entity().into(); loop { match receiver.try_recv() { Ok(_) | Err(std::sync::mpsc::TryRecvError::Disconnected) => { break; } _ => { - let new_current_window = api.get_active_window(); + let new_current_window = get_active_window().unwrap(); if new_current_window.id.ne(¤t_window.id) || new_current_window.title.ne(¤t_window.title) || new_current_window @@ -348,8 +325,11 @@ pub fn subscribe_active_window(callback: JsFunction) -> Result { .ne(¤t_window.info.process_id) || new_current_window.id.eq(&0) { - current_window = new_current_window.clone(); - tsfn_clone.call(new_current_window, ThreadsafeFunctionCallMode::Blocking); + current_window = new_current_window.clone().into(); + tsfn_clone.call( + new_current_window.into(), + ThreadsafeFunctionCallMode::Blocking, + ); } thread::sleep(Duration::from_millis(100)); } @@ -469,14 +449,7 @@ pub fn unsubscribe_all_active_window() -> Result<()> { */ #[napi] pub fn install_extension() -> Result { - #[cfg(not(target_os = "linux"))] - { - Ok(false) - } - #[cfg(target_os = "linux")] - { - Ok(linux::gnome_install_extension()) - } + Ok(x_win::install_extension().unwrap()) } /** @@ -486,14 +459,7 @@ pub fn install_extension() -> Result { */ #[napi] pub fn uninstall_extension() -> Result { - #[cfg(not(target_os = "linux"))] - { - Ok(false) - } - #[cfg(target_os = "linux")] - { - Ok(linux::gnome_uninstall_extension()) - } + Ok(x_win::uninstall_extension().unwrap()) } /** @@ -502,14 +468,7 @@ pub fn uninstall_extension() -> Result { */ #[napi] pub fn enable_extension() -> Result { - #[cfg(not(target_os = "linux"))] - { - Ok(false) - } - #[cfg(target_os = "linux")] - { - Ok(linux::gnome_enable_extension()) - } + Ok(x_win::enable_extension().unwrap()) } /** @@ -518,12 +477,5 @@ pub fn enable_extension() -> Result { */ #[napi] pub fn disable_extension() -> Result { - #[cfg(not(target_os = "linux"))] - { - Ok(false) - } - #[cfg(target_os = "linux")] - { - Ok(linux::gnome_disable_extension()) - } + Ok(x_win::disable_extension().unwrap()) } diff --git a/src/macos/mod.rs b/src/macos/mod.rs deleted file mode 100644 index 5d4ba55..0000000 --- a/src/macos/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![deny(unused_imports)] - -mod api; - -use crate::common::api::API; -use api::MacosAPI; - -pub fn init_platform_api() -> impl API { - MacosAPI { } -} \ No newline at end of file diff --git a/src/win32/mod.rs b/src/win32/mod.rs deleted file mode 100644 index b8b61e8..0000000 --- a/src/win32/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![deny(unused_imports)] - -mod api; - -use crate::common::api::API; -use api::WindowsAPI; - -pub fn init_platform_api() -> impl API { - WindowsAPI { } -} \ No newline at end of file diff --git a/x-win-rs/.cargo/config.toml b/x-win-rs/.cargo/config.toml new file mode 100644 index 0000000..522eab3 --- /dev/null +++ b/x-win-rs/.cargo/config.toml @@ -0,0 +1,6 @@ +[target.x86_64-unknown-linux-musl] +rustflags = ["-C", "target-feature=-crt-static", "-lm"] + +[target.aarch64-unknown-linux-musl] +linker = "aarch64-linux-musl-gcc" +rustflags = ["-C", "target-feature=-crt-static", "-lm"] \ No newline at end of file diff --git a/x-win-rs/.gitignore b/x-win-rs/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/x-win-rs/.gitignore @@ -0,0 +1 @@ +/target diff --git a/x-win-rs/Cargo.toml b/x-win-rs/Cargo.toml new file mode 100644 index 0000000..48b3637 --- /dev/null +++ b/x-win-rs/Cargo.toml @@ -0,0 +1,55 @@ +[package] +edition = "2021" +name = "x-win" +version = "0.0.0" +authors = ["BENKHADRA Hocine "] +keywords = ["window", "active", "current", "position", "title", "list", "open"] +description = "This package allows you to retrieve precise information about active and open windows on Windows, MacOS, and Linux. You can obtain the position, size, title, and other memory of windows." +repository = "https://github.com/miniben-90/x-win" +license = "MIT" + +[lib] +name = "x_win" +path = "src/lib.rs" + +[dependencies] +once_cell = "1.19.0" +base64 = "0.22.1" + +[target.'cfg(target_os = "windows")'.dependencies] +windows = { version = "0.58.0", features = [ + "Win32_Foundation", + "Win32_UI_WindowsAndMessaging", + "Win32_System_Threading", + "Win32_Storage_FileSystem", + "Win32_System_ProcessStatus", + "Win32_System_StationsAndDesktops", + "Win32_UI_Input_KeyboardAndMouse", + "Win32_Graphics_Dwm", + "UI_UIAutomation", + "Win32_System_Com", + "Win32_UI_Accessibility", + "Win32_System_Ole", + "Win32_UI_Shell_PropertiesSystem", + "Win32_UI_Shell_Common", + "Win32_System_Variant", + "Win32_Graphics_Gdi", + "Win32_Graphics_Imaging", +] } +png = "0.17.13" + +[target.'cfg(target_os = "linux")'.dependencies] +xcb = { version = "1.4.0" } +x11 = { version = "2.21.0", features = ["xlib"], optional = true } +zbus = { version = "1.9.2" } +serde_json = { version = "1.0.120" } +image = "0.25.1" + +[target.'cfg(target_os = "macos")'.dependencies] +cocoa = "0.25.0" +libc = "0.2.155" +cocoa-foundation = "0.1.2" +core-foundation = "0.9.4" +core-foundation-sys = "0.8.6" +core-graphics = "0.23.2" +objc = "0.2.7" diff --git a/x-win-rs/LICENSE b/x-win-rs/LICENSE new file mode 100644 index 0000000..9cf1062 --- /dev/null +++ b/x-win-rs/LICENSE @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/x-win-rs/README.md b/x-win-rs/README.md new file mode 100644 index 0000000..da63ece --- /dev/null +++ b/x-win-rs/README.md @@ -0,0 +1,239 @@ +# x-win + +[![CI](https://github.com/miniben-90/x-win/actions/workflows/ci-rs.yml/badge.svg)](https://github.com/miniben-90/x-win/actions/workflows/ci-rs.yml) + +This package make it easy to obtain the active window or an array of open windows. It works on Microsoft Windows (10, 11), [Linux (with X server)](#linux), [Linux (with Gnome =< 45)](#GNOME), and [macOS 10.6+](#darwin). + +# How to use x-win + +## Get information about the currently active window + +`examples/get_active_window.rs`: + +```rust +use x_win::{get_active_window, XWinError}; + +fn main() { + match get_active_window() { + Ok(active_window) => { + println!("active window: {:#?}", active_window); + } + Err(XWinError) => { + println!("error occurred while getting the active window"); + } + } +} +``` + +`response`: + +```log +active window: WindowInfo { + id: 23624, + os: "win32", + title: "● README.md - x-win - Visual Studio Code", + position: WindowPosition { + x: -8, + y: -8, + width: 1936, + height: 1048, + is_full_screen: true, + }, + info: ProcessInfo { + process_id: 23624, + path: "C:\\Users\\miniben\\AppData\\Local\\Programs\\Microsoft VS Code\\Code.exe", + name: "Code", + exec_name: "Code", + }, + usage: UsageInfo { + memory: 129138688, + }, + url: "", +} +``` + +## Get a list of open windows with information + +`examples/get_active_window.rs`: + +```rust +use x_win::{get_open_windows, XWinError}; + +fn main() { + match get_open_windows() { + Ok(open_windows) => { + println!("open windows: {:#?}", open_windows); + } + Err(XWinError) => { + println!("error occurred while getting open windows"); + } + } +} +``` + +`response`: + +```log +open windows: [ + WindowInfo { + id: 23624, + os: "win32", + title: "● README.md - x-win - Visual Studio Code", + position: WindowPosition { + x: -8, + y: -8, + width: 1936, + height: 1048, + is_full_screen: true, + }, + info: ProcessInfo { + process_id: 23624, + path: "C:\\Users\\miniben\\AppData\\Local\\Programs\\Microsoft VS Code\\Code.exe", + name: "Code", + exec_name: "Code", + }, + usage: UsageInfo { + memory: 128770048, + }, + url: "", + }, +] +``` + +## Get icon from `WindoInfo` + +`examples/get_window_icon.rs`: + +```rust +use x_win::{get_active_window, get_window_icon, XWinError}; + +fn main() { + match get_active_window() { + Ok(active_window) => match get_window_icon(&active_window) { + Ok(icon_info) => { + println!("icon info: {:#?}", icon_info); + } + Err(XWinError) => { + println!("error occurred while getting the icon info of active window"); + } + }, + Err(XWinError) => { + println!("error occurred while getting the active window"); + } + } +} +``` + +`response`: + +```log +icon info: IconInfo { + data: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAFLElEQVR4Ae3gAZAkSZIkSRKLqpm7R0REZmZmVlVVVVV3d3d3d/fMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMdHd3d3dXV1VVVVVmZkZGRIS7m5kKz0xmV3d1d3dPz8zMzMxMovhP8GKf92uvOj7skV+7bHpwUTG7+99/64c/+mN4Xoj/YC/2eb/2qk8cy69MsNXNZzz0JV8KgGm9bj574Uee9sGPeDeeDfEf6LFf+Jvv/aSlv26CLZ7pka/4SjxQW69bnrvwI0//4Ee8G4D4D/Lgz/7Vj7x1Kl/Dc3nkK70iz8+0Glq54+43EP8BHvRZv/oRz5jia3k+HvlKr8QLkkeru8S/06nP+NWfON/0trwAj3ylV+aFQPw7nPz0X/3xC6m344V4xCu+Mi8E4t/gEZ/xi6fuy/LDl1Kvz7/gEa/4KrwQCODUV//Dt5+X3g9g0/7TG+w3ffLHvPh5no9HfMYvnrp9Kr+/Mo/mRfCIV3pVXgh06qv+4dvOi/fnAQoc3GS/4zM+5sV/iQd4xGf84qnbx/j9lfVo/gUFHzx0u/sMHvuKX8ULhja+6u//+Ai9Es/HafvLz33si38CwIM+9Rdf8e4sPzWkb+BfMA+ecG3ke936BW/yD4/4yYMDXjC09ZV//ysH8Ia8AIv0H19zePYzbz9c/0Sabf4Fx4p/7JqOD3ny573pedubj/iJ/QNeMPSIr/r7U7clv7eGx/CCZEsunA2mgRckYP908Sfd98Vv9k08k+2NR/zE/iEvGAJ4xFf9/anbmn9vjR7DC7N3EY72eW4z+fGnw+9955e8+Z/yALY3HvHj+4e8YIgH2P6yv/uRfXhHXpjVEi6dBycA28GPbqt9yF1f+pYXeC62Fw//sf0jXjDEc9n60r/94QP0TrwwbYLdcxyflp+8+xVv+SW8ALYXD//R/SNeMMRz2fz4n/uhw82T78zGNv+SnfQn733KS30JL4Dt+cN/ZH/JC4Z4gM2P+7kfOkzeGYDFFuyc4l+yCT98DD7srk9+yQs8F9vzh//I/pIXDAHc8Ak/e/LsxO+O5sV4oMUW7JzmX9KJfzhmv/e5T3mpP+cBbM8e9sP7K14wdMMn/OzJs6N/d7RejOenn8PxM4kieCEEe8fEJ+x+ykt9K89ke/awH9pf8YKh2Uf/zC+skzflBejkP9g6dcNn7pbZTxl2+Bcs4IdOFD78rk9+qQu2+4f94N6aFwx1H/XTvz+aV+P52BBfePQ1b/1pAMc//69fbl/66WbfxL+gir/fMu998dNe6u8e9oN7a14wNP/In/qGlfWhPIBgb7v47fa++m1+nQfY+dy/OnEk/e4EL86/QGLveNUnHr/pwd/MC4YAZh/xU1+/Nh8GUMXvb8hvufe1b3uR52Pnc//qxKH0uw1enBfBQx78EF4IxL/Bzuf+1YklfO8Ib86/4CEPfigvBOLfofucv/q+Ed6dF+IhD34oLwTi36l+1l9+3yTenRfgwQ95GC8E4j9A/1l/+b4DfAfPx4Mf8jBekDaMt4n/IN1n/uX7jvAdPJcHP+ShPD9tatNyf+91xX+g+hl/8TYTfBdwjGd68EMexgO1aZqWR0ffe+6jb3k/APEfrH7GX7zMZH4LOFZrx4233AJAG6dptTz63nMffcv78WyI/wyf+ucvfc1113210aNLhMdx+O7zH/OgT+F58Y/bubsQq+kGhwAAAABJRU5ErkJggg==", + height: 32, + width: 32, +} +``` + +## Linux + +Dependencies are required to be installed for development purposes. + +```sh +sudo apt install libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxcb-shm0-dev pkg-config build-essential +``` + +> ⚠️**Warning** +> libc.so.6 is needed + +> ⚠️**Warning** +> Recovery url is not available on linux + +## GNOME + +> Gnome using wayland. + +In order to recover data, you'll need to install and activate an extension designed for systems running GNOME version 41 or newer (as evaluation is disabled from this version onward). + +The extension can be installed using the `x_win::install_extension()` function, which will deposit it in `~/.local/share/gnome-shell/extensions/x-win@miniben90.org`. + +After executing this function, it's vital to **restart the user session** to load the new extension and then proceed to enable it using `x_win::enable_extension()` to be able to use x-win. + +| Gnome Distrib. | Tested | +|---|---| +| Fedora Workstation 39 | ✅ | +| Ubuntu 22.04.4 Desktop | ✅ | +| Debian 12 Desktop | ✅ | + +## Darwin + +> This package can be use only with darwin version 10.6+. + +### Screen recording permission introduced in macOS 10.15 Catalina + +> macOS requires you to grant access for screen recording. If your project does not have it, the title will be an empty value. + +## URLs (Only available for Darwin and Windows Systems) + +It is possible to get URL of browsers window for macOS and Windows. + +### Windows + +| Browser name | Tested | +|---|---| +| firefox | ✅ | +| firefox developer edition | ✅ | +| google chrome | ✅ | +| microsoft edge | ✅ | +| opera software - opera | ✅ | +| opera software - opera GX | ✅ | +| brave | | +| vivaldi | | +| iron | | +| epic | | +| chromium | ✅ | +| ucozmedia | | +| blisk | | +| maxthon | | +| beaker | | +| beaker browser | | + +### macOS + +It will use AppleScript to get informations for chromium browsers and safari + +***For the moment Firefox and firefox developer edition are not supported*** + +| Browser name | Tested | +|---|---| +| Safari | ✅ | +| Safari Technology Preview | | +| google Chrome | ✅ | +| google Chrome beta | | +| google Chrome dev | | +| google Chrome canary | | +| brave Browser | | +| brave Browser beta | | +| brave Browser nightly | | +| microsoft edge | ✅ | +| microsoft edge Beta | | +| microsoft edge Dev | | +| microsoft edge Canary | | +| mighty | | +| ghost browser | | +| bookry wavebox | | +| pushplaylabs sidekick | | +| opera software - Opera | ✅ | +| opera software - OperaNext | | +| opera software - OperaDeveloper | | +| opera software - OperaGX | ✅ | +| Vivaldi | | + +
+ +## Project References + +**Project Inspirations:** + +* [active-win](https://github.com/sindresorhus/active-win) +* [active-win-pos-rs](https://github.com/dimusic/active-win-pos-rs) +* This project was generated with [@napi-rs/cli](https://github.com/napi-rs) diff --git a/x-win-rs/examples/get_active_window.rs b/x-win-rs/examples/get_active_window.rs new file mode 100644 index 0000000..e15b409 --- /dev/null +++ b/x-win-rs/examples/get_active_window.rs @@ -0,0 +1,12 @@ +use x_win::{get_active_window, XWinError}; + +fn main() { + match get_active_window() { + Ok(active_window) => { + println!("active window: {:#?}", active_window); + } + Err(XWinError) => { + println!("error occurred while getting the active window"); + } + } +} diff --git a/x-win-rs/examples/get_open_windows.rs b/x-win-rs/examples/get_open_windows.rs new file mode 100644 index 0000000..24f3a7e --- /dev/null +++ b/x-win-rs/examples/get_open_windows.rs @@ -0,0 +1,12 @@ +use x_win::{get_open_windows, XWinError}; + +fn main() { + match get_open_windows() { + Ok(open_windows) => { + println!("open windows: {:#?}", open_windows); + } + Err(XWinError) => { + println!("error occurred while getting open windows"); + } + } +} diff --git a/x-win-rs/examples/get_window_icon.rs b/x-win-rs/examples/get_window_icon.rs new file mode 100644 index 0000000..35f44e9 --- /dev/null +++ b/x-win-rs/examples/get_window_icon.rs @@ -0,0 +1,17 @@ +use x_win::{get_active_window, get_window_icon, XWinError}; + +fn main() { + match get_active_window() { + Ok(active_window) => match get_window_icon(&active_window) { + Ok(icon_info) => { + println!("icon info: {:#?}", icon_info); + } + Err(XWinError) => { + println!("error occurred while getting the icon info of active window"); + } + }, + Err(XWinError) => { + println!("error occurred while getting the active window"); + } + } +} diff --git a/src/common/api.rs b/x-win-rs/src/common/api.rs similarity index 96% rename from src/common/api.rs rename to x-win-rs/src/common/api.rs index 621283e..c272017 100644 --- a/src/common/api.rs +++ b/x-win-rs/src/common/api.rs @@ -5,7 +5,7 @@ use super::x_win_struct::{ window_position::WindowPosition, }; -pub trait API { +pub trait Api { /** * Return information of current active Window */ @@ -45,7 +45,7 @@ pub fn os_name() -> String { pub fn empty_entity() -> WindowInfo { WindowInfo { id: 0, - os: os_name().to_owned(), + os: os_name(), title: "".to_string(), position: WindowPosition { x: 0, diff --git a/x-win-rs/src/common/mod.rs b/x-win-rs/src/common/mod.rs new file mode 100644 index 0000000..24b1b98 --- /dev/null +++ b/x-win-rs/src/common/mod.rs @@ -0,0 +1,4 @@ +#![deny(unused_imports)] + +pub mod api; +pub mod x_win_struct; diff --git a/x-win-rs/src/common/x_win_struct/icon_info.rs b/x-win-rs/src/common/x_win_struct/icon_info.rs new file mode 100644 index 0000000..bfb9065 --- /dev/null +++ b/x-win-rs/src/common/x_win_struct/icon_info.rs @@ -0,0 +1,22 @@ +#![deny(unused_imports)] + +/** + * Struct to store Icon information + */ +#[derive(Debug, Clone)] +#[repr(C)] +pub struct IconInfo { + pub data: String, + pub height: u32, + pub width: u32, +} + +impl IconInfo { + pub fn new(data: String, height: u32, width: u32) -> Self { + Self { + data, + height, + width, + } + } +} diff --git a/x-win-rs/src/common/x_win_struct/mod.rs b/x-win-rs/src/common/x_win_struct/mod.rs new file mode 100644 index 0000000..9a8aaaa --- /dev/null +++ b/x-win-rs/src/common/x_win_struct/mod.rs @@ -0,0 +1,7 @@ +#![deny(unused_imports)] + +pub mod icon_info; +pub mod process_info; +pub mod usage_info; +pub mod window_info; +pub mod window_position; diff --git a/x-win-rs/src/common/x_win_struct/process_info.rs b/x-win-rs/src/common/x_win_struct/process_info.rs new file mode 100644 index 0000000..931c8d4 --- /dev/null +++ b/x-win-rs/src/common/x_win_struct/process_info.rs @@ -0,0 +1,24 @@ +#![deny(unused_imports)] + +/** + * Struct to store process information of the window + */ +#[derive(Debug, Clone)] +#[repr(C)] +pub struct ProcessInfo { + pub process_id: u32, + pub path: String, + pub name: String, + pub exec_name: String, +} + +impl ProcessInfo { + pub fn new(process_id: u32, path: String, name: String, exec_name: String) -> Self { + Self { + process_id, + path, + name, + exec_name, + } + } +} diff --git a/x-win-rs/src/common/x_win_struct/usage_info.rs b/x-win-rs/src/common/x_win_struct/usage_info.rs new file mode 100644 index 0000000..98633b1 --- /dev/null +++ b/x-win-rs/src/common/x_win_struct/usage_info.rs @@ -0,0 +1,15 @@ +#![deny(unused_imports)] + +/** + * Struct to store usage data of the window + */ +#[derive(Debug, Clone)] +pub struct UsageInfo { + pub memory: u32, +} + +impl UsageInfo { + pub fn new(memory: u32) -> Self { + Self { memory } + } +} diff --git a/x-win-rs/src/common/x_win_struct/window_info.rs b/x-win-rs/src/common/x_win_struct/window_info.rs new file mode 100644 index 0000000..a3a81f4 --- /dev/null +++ b/x-win-rs/src/common/x_win_struct/window_info.rs @@ -0,0 +1,39 @@ +#![deny(unused_imports)] + +use super::{process_info::ProcessInfo, usage_info::UsageInfo, window_position::WindowPosition}; + +/** + * Struct to store all informations of the window + */ +#[derive(Debug, Clone)] +pub struct WindowInfo { + pub id: u32, + pub os: String, + pub title: String, + pub position: WindowPosition, + pub info: ProcessInfo, + pub usage: UsageInfo, + pub url: String, +} + +impl WindowInfo { + pub fn new( + id: u32, + os: String, + title: String, + position: WindowPosition, + info: ProcessInfo, + usage: UsageInfo, + url: String, + ) -> Self { + Self { + id, + os, + title, + position, + info, + usage, + url, + } + } +} diff --git a/x-win-rs/src/common/x_win_struct/window_position.rs b/x-win-rs/src/common/x_win_struct/window_position.rs new file mode 100644 index 0000000..b03d030 --- /dev/null +++ b/x-win-rs/src/common/x_win_struct/window_position.rs @@ -0,0 +1,25 @@ +#![deny(unused_imports)] + +/** + * Struct to store position and size of the window + */ +#[derive(Debug, Clone)] +pub struct WindowPosition { + pub x: i32, + pub y: i32, + pub width: i32, + pub height: i32, + pub is_full_screen: bool, +} + +impl WindowPosition { + pub fn new(x: i32, y: i32, width: i32, height: i32, is_full_screen: bool) -> Self { + Self { + x, + y, + width, + height, + is_full_screen, + } + } +} diff --git a/x-win-rs/src/lib.rs b/x-win-rs/src/lib.rs new file mode 100644 index 0000000..f749afa --- /dev/null +++ b/x-win-rs/src/lib.rs @@ -0,0 +1,216 @@ +#![deny(unsafe_op_in_unsafe_fn)] +//#![deny(clippy::all)] +//#![allow(unused_imports)] + +#[cfg(target_os = "macos")] +#[macro_use] +extern crate objc; + +#[cfg(target_os = "macos")] +#[macro_use] +extern crate core; + +mod common; + +#[cfg(target_os = "windows")] +mod win32; + +#[cfg(target_os = "linux")] +mod linux; + +#[cfg(target_os = "macos")] +mod macos; + +#[cfg(target_os = "windows")] +use win32::init_platform_api; + +#[cfg(target_os = "linux")] +use linux::init_platform_api; + +#[cfg(target_os = "macos")] +use macos::init_platform_api; + +pub use common::{ + api::{empty_entity, os_name}, + x_win_struct::{ + icon_info::IconInfo, process_info::ProcessInfo, usage_info::UsageInfo, window_info::WindowInfo, + window_position::WindowPosition, + }, +}; + +use crate::common::api::Api; + +use std::fmt; + +#[derive(Debug)] +pub struct XWinError; + +impl fmt::Display for XWinError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Oops something got wrong with x-win") + } +} + +impl std::error::Error for XWinError {} + +/** + * Retrieve information the about currently active window. + * Returns an object of `WindowInfo`. + */ +pub fn get_window_icon(window_info: &WindowInfo) -> Result { + let api = init_platform_api(); + Ok(api.get_app_icon(window_info)) +} + +/** + * Retrieve information the about currently active window. + * Returns an object of `WindowInfo`. + */ +pub fn get_active_window() -> Result { + let api = init_platform_api(); + Ok(api.get_active_window()) +} + +/** + * Retrieve information the about currently active window. + * Returns an object of `WindowInfo`. + */ +pub fn get_open_windows() -> Result, XWinError> { + let api = init_platform_api(); + Ok(api.get_open_windows()) +} + +/** + * Install Gnome extensions required for Linux using Gnome > 41. + * This function will write extension files needed to correctly detect working windows with Wayland desktop environment. + * Restart session will be require to install the gnome extension. + */ +pub fn install_extension() -> Result { + #[cfg(not(target_os = "linux"))] + { + Ok(false) + } + #[cfg(target_os = "linux")] + { + Ok(linux::gnome_install_extension()) + } +} + +/** + * Install Gnome extensions required for Linux using Gnome > 41. + * This function will write extension files needed to correctly detect working windows with Wayland desktop environment. + * Restart session will be require to remove the gnome extension. + */ +pub fn uninstall_extension() -> Result { + #[cfg(not(target_os = "linux"))] + { + Ok(false) + } + #[cfg(target_os = "linux")] + { + Ok(linux::gnome_uninstall_extension()) + } +} + +/** + * Enable Gnome extensions required for Linux using Gnome > 41. + * This function will enable extension needed to correctly detect working windows with Wayland desktop environment. + */ +pub fn enable_extension() -> Result { + #[cfg(not(target_os = "linux"))] + { + Ok(false) + } + #[cfg(target_os = "linux")] + { + Ok(linux::gnome_enable_extension()) + } +} + +/** + * Disable Gnome extensions required for Linux using Gnome > 41. + * This function will disable extension needed to correctly detect working windows with Wayland desktop environment. + */ +pub fn disable_extension() -> Result { + #[cfg(not(target_os = "linux"))] + { + Ok(false) + } + #[cfg(target_os = "linux")] + { + Ok(linux::gnome_disable_extension()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn test_osname() -> String { + #[cfg(target_os = "linux")] + { + r#"linux"#.to_owned() + } + #[cfg(target_os = "macos")] + { + r#"darwin"#.to_owned() + } + #[cfg(target_os = "windows")] + { + r#"win32"#.to_owned() + } + } + + fn test_struct(window_info: WindowInfo) -> Result<(), String> { + assert_ne!(window_info.id, 0); + assert_ne!(window_info.title, "".to_owned()); + #[cfg(target_os = "linux")] + assert_eq!(window_info.os, r#"linux"#); + #[cfg(target_os = "macos")] + assert_eq!(window_info.os, r#"darwin"#); + #[cfg(target_os = "windows")] + assert_eq!(window_info.os, r#"win32"#); + Ok(()) + } + + #[test] + fn test_get_active_window() -> Result<(), String> { + let window_info = get_active_window().unwrap(); + test_struct(window_info) + } + + #[test] + fn test_get_open_windows() -> Result<(), String> { + let open_windows = get_open_windows().unwrap(); + assert_ne!(open_windows.len(), 0); + let window_info = open_windows.first().unwrap().to_owned(); + test_struct(window_info) + } + + #[test] + fn test_os_name() -> Result<(), String> { + let os_name = os_name(); + assert_eq!(os_name, test_osname()); + Ok(()) + } + + #[test] + fn test_empty_entity() -> Result<(), String> { + let window_info = empty_entity(); + assert_eq!(window_info.id, 0); + assert_eq!(window_info.title, "".to_owned()); + assert_eq!(window_info.os, test_osname()); + Ok(()) + } + + #[test] + fn test_get_window_icon() -> Result<(), String> { + let window_info: &WindowInfo = &get_active_window().unwrap(); + test_struct(window_info.clone()).unwrap(); + let icon_info = get_window_icon(&window_info).unwrap(); + assert_ne!(icon_info.data, ""); + assert_ne!(icon_info.height, 0); + assert_ne!(icon_info.width, 0); + Ok(()) + } +} diff --git a/src/linux/api.rs b/x-win-rs/src/linux/api.rs similarity index 97% rename from src/linux/api.rs rename to x-win-rs/src/linux/api.rs index 488b94a..c8c00f7 100644 --- a/src/linux/api.rs +++ b/x-win-rs/src/linux/api.rs @@ -12,7 +12,7 @@ use wayland_api::WaylandApi; use x11_api::X11Api; use crate::common::{ - api::API, + api::Api, x_win_struct::{icon_info::IconInfo, window_info::WindowInfo}, }; @@ -28,7 +28,7 @@ pub struct LinuxAPI {} /** * Impl. for windows system */ -impl API for LinuxAPI { +impl Api for LinuxAPI { fn get_active_window(&self) -> WindowInfo { if is_wayland_desktop() { (WaylandApi {}).get_active_window() diff --git a/src/linux/api/common_api.rs b/x-win-rs/src/linux/api/common_api.rs similarity index 95% rename from src/linux/api/common_api.rs rename to x-win-rs/src/linux/api/common_api.rs index 46e77fb..2a5e785 100644 --- a/src/linux/api/common_api.rs +++ b/x-win-rs/src/linux/api/common_api.rs @@ -33,7 +33,7 @@ pub fn get_window_memory_usage(pid: u32) -> u32 { let mut statm_content = String::new(); statm_file.read_to_string(&mut statm_content).unwrap(); let statm_parts: Vec<&str> = statm_content.split(" ").collect(); - return statm_parts[0].parse().unwrap(); + statm_parts[0].parse().unwrap() } /** @@ -44,7 +44,7 @@ pub fn get_window_path_name(pid: u32) -> (String, String) { let path = executable_path.display().to_string(); let name = executable_path.file_name().unwrap(); let name = name.to_string_lossy().to_string(); - return (path, name); + (path, name) } pub fn init_entity() -> WindowInfo { @@ -61,5 +61,5 @@ pub fn get_gnome_version() -> String { return version.to_owned(); } } - "999".to_owned() + "999".into() } diff --git a/src/linux/api/gnome_shell.rs b/x-win-rs/src/linux/api/gnome_shell.rs similarity index 96% rename from src/linux/api/gnome_shell.rs rename to x-win-rs/src/linux/api/gnome_shell.rs index 5b1bc00..4795e0c 100644 --- a/src/linux/api/gnome_shell.rs +++ b/x-win-rs/src/linux/api/gnome_shell.rs @@ -2,7 +2,10 @@ 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 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; @@ -20,7 +23,8 @@ pub const GNOME_XWIN_EXTENSION_META: &str = r#" } "#; -pub const GNOME_XWIN_EXTENSION_FOLDER_PATH: &str = r#".local/share/gnome-shell/extensions/x-win@miniben90.org"#; +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) { @@ -177,7 +181,6 @@ function _get_process_info(pid) { }; }"#; - pub const GNOME_XWIN_EXTENSION_COMMON_SCRIPT: &str = r#" const WaylandInterface = ` @@ -445,4 +448,5 @@ impl GnomeVersion { } } -pub static GNOME_SINGLETON: Lazy> = Lazy::new(|| Mutex::new(GnomeVersion::new())); +pub static GNOME_SINGLETON: Lazy> = + Lazy::new(|| Mutex::new(GnomeVersion::new())); diff --git a/src/linux/api/wayland_api.rs b/x-win-rs/src/linux/api/wayland_api.rs similarity index 95% rename from src/linux/api/wayland_api.rs rename to x-win-rs/src/linux/api/wayland_api.rs index db5f021..2652b61 100644 --- a/src/linux/api/wayland_api.rs +++ b/x-win-rs/src/linux/api/wayland_api.rs @@ -7,7 +7,7 @@ use std::{ use crate::{ common::{ - api::API, + api::Api, x_win_struct::{icon_info::IconInfo, window_info::WindowInfo}, }, linux::api::{ @@ -24,7 +24,7 @@ use super::{ fn gnome_use_eval() -> bool { let gnome_singleton = GNOME_SINGLETON.lock().unwrap(); - let use_eval: bool = gnome_singleton.use_eval.clone(); + let use_eval: bool = gnome_singleton.use_eval; let _ = gnome_singleton.deref(); use_eval } @@ -37,7 +37,7 @@ pub struct WaylandApi {} /** * Impl. for Linux system */ -impl API for WaylandApi { +impl Api for WaylandApi { fn get_active_window(&self) -> WindowInfo { if gnome_use_eval() { wayland_eval_api::get_active_window() diff --git a/src/linux/api/wayland_eval_api.rs b/x-win-rs/src/linux/api/wayland_eval_api.rs similarity index 85% rename from src/linux/api/wayland_eval_api.rs rename to x-win-rs/src/linux/api/wayland_eval_api.rs index d4b07ca..607277a 100644 --- a/src/linux/api/wayland_eval_api.rs +++ b/x-win-rs/src/linux/api/wayland_eval_api.rs @@ -22,7 +22,7 @@ get_active_window(); let response = call_script(&script); if response.ne(&"") { - let response: serde_json::Value = serde_json::from_str(&response.as_str()).unwrap(); + let response: serde_json::Value = serde_json::from_str(response.as_str()).unwrap(); if response.is_object() { return value_to_window_info(&response); } @@ -43,14 +43,14 @@ get_open_windows(); let response = call_script(&script); if !response.is_empty() { - let response: serde_json::Value = serde_json::from_str(&response.as_str()).unwrap(); + let response: serde_json::Value = serde_json::from_str(response.as_str()).unwrap(); if response.is_array() { return response .as_array() .unwrap() .iter() - .map(|val| value_to_window_info(&val)) + .map(value_to_window_info) .collect(); } } @@ -91,7 +91,7 @@ get_icon({}); let response = call_script(&script); if !response.is_empty() { - let response: serde_json::Value = serde_json::from_str(&response.as_str()).unwrap(); + let response: serde_json::Value = serde_json::from_str(response.as_str()).unwrap(); if response.is_object() { return value_to_icon_info(&response); } diff --git a/src/linux/api/wayland_extension_api.rs b/x-win-rs/src/linux/api/wayland_extension_api.rs similarity index 76% rename from src/linux/api/wayland_extension_api.rs rename to x-win-rs/src/linux/api/wayland_extension_api.rs index 56a5b2c..8c4f782 100644 --- a/src/linux/api/wayland_extension_api.rs +++ b/x-win-rs/src/linux/api/wayland_extension_api.rs @@ -5,17 +5,22 @@ use std::{env, fs, io, ops::Deref, path}; use crate::{ common::x_win_struct::{icon_info::IconInfo, window_info::WindowInfo}, linux::api::gnome_shell::{ - value_to_window_info, GNOME45_XWIN_EXTENSION_SCRIPT, GNOME_SINGLETON, GNOME_XWIN_EXTENSION_COMMON_SCRIPT, GNOME_XWIN_EXTENSION_FOLDER_PATH, GNOME_XWIN_EXTENSION_META, GNOME_XWIN_EXTENSION_SCRIPT, GNOME_XWIN_UUID + value_to_window_info, GNOME45_XWIN_EXTENSION_SCRIPT, GNOME_SINGLETON, + GNOME_XWIN_EXTENSION_COMMON_SCRIPT, GNOME_XWIN_EXTENSION_FOLDER_PATH, + GNOME_XWIN_EXTENSION_META, GNOME_XWIN_EXTENSION_SCRIPT, GNOME_XWIN_UUID, }, }; -use super::{common_api::init_entity, gnome_shell::{value_to_icon_info, GNOME_XWIN_GET_ICON_SCRIPT}}; +use super::{ + common_api::init_entity, + gnome_shell::{value_to_icon_info, GNOME_XWIN_GET_ICON_SCRIPT}, +}; pub fn get_active_window() -> WindowInfo { - let response = call_script(&"get_active_window".to_string()); + let response = call_script("get_active_window"); if response.ne(&"") { - let response: serde_json::Value = serde_json::from_str(&response.as_str()).unwrap(); + let response: serde_json::Value = serde_json::from_str(response.as_str()).unwrap(); if response.is_object() { return value_to_window_info(&response); } @@ -25,16 +30,16 @@ pub fn get_active_window() -> WindowInfo { } pub fn get_open_windows() -> Vec { - let response = call_script(&"get_open_windows".to_string()); + let response = call_script("get_open_windows"); if response.ne(&"") { - let response: serde_json::Value = serde_json::from_str(&response.as_str()).unwrap(); + let response: serde_json::Value = serde_json::from_str(response.as_str()).unwrap(); if response.is_array() { return response .as_array() .unwrap() .iter() - .map(|val| value_to_window_info(&val)) + .map(value_to_window_info) .collect(); } } @@ -44,42 +49,40 @@ pub fn get_open_windows() -> Vec { pub fn get_icon(window_info: &WindowInfo) -> IconInfo { if window_info.id.ne(&0) { - let response = call_script_arg(&"get_icon".to_string(), window_info.id); + let response = call_script_arg("get_icon", window_info.id); if response.ne(&"") { - let response: serde_json::Value = serde_json::from_str(&response.as_str()).unwrap(); + let response: serde_json::Value = serde_json::from_str(response.as_str()).unwrap(); if response.is_object() { return value_to_icon_info(&response); } } } - return IconInfo { + IconInfo { data: "".to_owned(), width: 0, height: 0, - }; + } } pub fn install_extension() -> bool { if std::fs::metadata(get_extension_path()).is_ok() { if std::fs::metadata(get_extension_file_path()).is_ok() { - let _t = remove_extension_file().unwrap(); + remove_extension_file().unwrap(); } if std::fs::metadata(get_medata_file_path()).is_ok() { - let _t = remove_metadata_file().unwrap(); - } - } else { - if let Err(create_folder_err) = std::fs::create_dir_all(get_extension_path()) { - panic!( - "Not possible to create extension folder to \"{}\"!\nRaison: {}", - get_extension_path().to_string_lossy(), - create_folder_err.to_string() - ); + remove_metadata_file().unwrap(); } + } else if let Err(create_folder_err) = std::fs::create_dir_all(get_extension_path()) { + panic!( + "Not possible to create extension folder to \"{}\"!\nRaison: {}", + get_extension_path().to_string_lossy(), + create_folder_err + ); } let script: String = { let gnome_singleton = GNOME_SINGLETON.lock().unwrap(); - let version: u32 = gnome_singleton.version.clone(); + let version: u32 = gnome_singleton.version; let _ = gnome_singleton.deref(); let script: String = match version { x if x.lt(&45) => GNOME_XWIN_EXTENSION_SCRIPT.to_string(), @@ -103,7 +106,7 @@ pub fn install_extension() -> bool { if fs::write(get_medata_file_path(), GNOME_XWIN_EXTENSION_META).is_ok() { true } else { - let _t = remove_extension_file().unwrap(); + remove_extension_file().unwrap(); panic!( "Not possible to write \"{}\" file!", get_medata_file_path().to_string_lossy() @@ -190,7 +193,7 @@ fn remove_extension_file() -> Result<(), std::io::Error> { fs::remove_file(get_extension_file_path()) } -fn call_script(method_name: &String) -> String { +fn call_script(method_name: &str) -> String { let connection = Connection::new_session().unwrap(); let response = connection @@ -209,7 +212,7 @@ fn call_script(method_name: &String) -> String { "".to_owned() } -fn call_script_arg(method_name: &String, body: u32) -> String { +fn call_script_arg(method_name: &str, body: u32) -> String { let connection = Connection::new_session().unwrap(); let response = connection diff --git a/src/linux/api/x11_api.rs b/x-win-rs/src/linux/api/x11_api.rs similarity index 88% rename from src/linux/api/x11_api.rs rename to x-win-rs/src/linux/api/x11_api.rs index 3d573bd..d250492 100644 --- a/src/linux/api/x11_api.rs +++ b/x-win-rs/src/linux/api/x11_api.rs @@ -7,7 +7,7 @@ use xcb::{x, Connection, Xid, XidNew}; use crate::{ common::{ - api::API, + api::Api, x_win_struct::{icon_info::IconInfo, window_info::WindowInfo, window_position::WindowPosition}, }, linux::api::common_api::{get_window_memory_usage, get_window_path_name}, @@ -23,7 +23,7 @@ pub struct X11Api {} /** * Impl. for windows system */ -impl API for X11Api { +impl Api for X11Api { fn get_active_window(&self) -> WindowInfo { let conn = connection(); let setup = conn.get_setup(); @@ -31,7 +31,7 @@ impl API for X11Api { let mut result: WindowInfo = init_entity(); let root_window = setup.roots().next(); - if !root_window.is_none() { + if root_window.is_some() { let root_window = root_window.unwrap().root(); let active_window_atom = get_active_window_atom(&conn); if active_window_atom != x::ATOM_NONE { @@ -44,8 +44,8 @@ impl API for X11Api { long_length: 1, }); if let Ok(active_window) = conn.wait_for_reply(active_window) { - let active_window: Option<&x::Window> = active_window.value::().get(0); - if !active_window.is_none() { + let active_window: Option<&x::Window> = active_window.value::().first(); + if active_window.is_some() { let active_window: &x::Window = active_window.unwrap(); result = get_window_information(&conn, active_window); } @@ -63,7 +63,7 @@ impl API for X11Api { let setup = conn.get_setup(); let root_window = setup.roots().next(); - if !root_window.is_none() { + if root_window.is_some() { let root_window = root_window.unwrap().root(); let open_windows_atom = get_client_list_stacking_atom(&conn); @@ -74,7 +74,7 @@ impl API for X11Api { property: open_windows_atom, r#type: x::ATOM_WINDOW, long_offset: 0, - long_length: std::u32::MAX, + long_length: u32::MAX, }); if let Ok(windows_reply) = conn.wait_for_reply(window_list) { let window_list: Vec = windows_reply.value::().to_vec(); @@ -82,10 +82,8 @@ impl API for X11Api { for window in window_list { let window: &x::Window = &window; let result = get_window_information(&conn, window); - if result.id.ne(&0) { - if is_normal_window(&conn, *window) { - results.push(result); - } + if result.id.ne(&0) && is_normal_window(&conn, *window) { + results.push(result); } } } @@ -100,7 +98,7 @@ impl API for X11Api { let setup = conn.get_setup(); let root_window = setup.roots().next(); - if !root_window.is_none() { + if root_window.is_some() { let window = unsafe { XidNew::new(window_info.id) }; let icon_atom = get_window_icon_atom(&conn); if icon_atom != x::ATOM_NONE { @@ -110,7 +108,7 @@ impl API for X11Api { property: icon_atom, r#type: x::ATOM_CARDINAL, long_offset: 0, - long_length: std::u32::MAX, + long_length: u32::MAX, }); if let Ok(icon_reply) = conn.wait_for_reply(icon_cookie) { let icon_data: &[u32] = icon_reply.value::(); @@ -164,28 +162,28 @@ fn connection() -> Connection { * Get window information */ fn get_window_information(conn: &xcb::Connection, window: &x::Window) -> WindowInfo { - let window_pid: u32 = get_window_pid(&conn, *window); + let window_pid: u32 = get_window_pid(conn, *window); let mut window_info: WindowInfo = init_entity(); if window_pid != 0 { let (path, exec_name) = get_window_path_name(window_pid); window_info.id = window.resource_id(); - window_info.title = get_window_title(&conn, *window); - window_info.info.process_id = window_pid.try_into().unwrap(); + window_info.title = get_window_title(conn, *window); + window_info.info.process_id = window_pid; window_info.info.path = path; window_info.info.exec_name = exec_name; - window_info.info.name = get_window_class_name(&conn, *window); + window_info.info.name = get_window_class_name(conn, *window); window_info.usage.memory = get_window_memory_usage(window_pid); - window_info.position = get_window_position(&conn, *window); + window_info.position = get_window_position(conn, *window); } - return window_info; + window_info } /** * Get pid */ fn get_window_pid(conn: &xcb::Connection, window: x::Window) -> u32 { - let window_pid_atom = get_window_pid_atom(&conn); + let window_pid_atom = get_window_pid_atom(conn); if window_pid_atom != x::ATOM_NONE { let window_pid = conn.send_request(&x::GetProperty { delete: false, @@ -196,10 +194,10 @@ fn get_window_pid(conn: &xcb::Connection, window: x::Window) -> u32 { long_length: 1, }); if let Ok(window_pid) = conn.wait_for_reply(window_pid) { - return window_pid.value::().get(0).unwrap_or(&0).to_owned(); + return window_pid.value::().first().unwrap().to_owned(); } } - return 0; + 0 } /** @@ -250,7 +248,7 @@ fn _get_string_response(conn: &xcb::Connection, window: x::Window, property: x:: property, r#type: x::ATOM_NONE, long_offset: 0, - long_length: std::u32::MAX, + long_length: u32::MAX, }); if let Ok(window_title) = conn.wait_for_reply(window_title) { let window_title: &[u8] = window_title.value(); @@ -270,7 +268,7 @@ fn get_window_class_name(conn: &xcb::Connection, window: x::Window) -> String { property: x::ATOM_WM_CLASS, r#type: x::ATOM_STRING, long_offset: 0, - long_length: std::u32::MAX, + long_length: u32::MAX, }); if let Ok(window_class) = conn.wait_for_reply(window_class) { let window_class = window_class.value(); @@ -279,11 +277,11 @@ fn get_window_class_name(conn: &xcb::Connection, window: x::Window) -> String { let mut process_name = window_class .unwrap_or("") .split('\u{0}') - .filter(|str| str.len() > 0) + .filter(|str| !str.is_empty()) .collect::>(); return process_name.pop().unwrap_or("").to_owned(); } - return "".to_owned(); + "".into() } fn get_window_pid_atom(conn: &xcb::Connection) -> x::Atom { @@ -358,8 +356,8 @@ fn get_atom(conn: &xcb::Connection, name: &[u8], only_if_exists: bool) -> x::Ato * Check if the window is a normal type */ fn is_normal_window(conn: &xcb::Connection, window: x::Window) -> bool { - let window_type_atom = get_window_type_atom(&conn); - let type_normal_atom = get_window_type_normal_atom(&conn); + let window_type_atom = get_window_type_atom(conn); + let type_normal_atom = get_window_type_normal_atom(conn); if window_type_atom != x::ATOM_NONE && type_normal_atom != x::ATOM_NONE { let window_state = conn.send_request(&x::GetProperty { delete: false, @@ -367,21 +365,21 @@ fn is_normal_window(conn: &xcb::Connection, window: x::Window) -> bool { property: window_type_atom, r#type: x::ATOM_ATOM, long_offset: 0, - long_length: std::u32::MAX, + long_length: u32::MAX, }); if let Ok(window_state) = conn.wait_for_reply(window_state) { return window_state.value().contains(&type_normal_atom); } } - return false; + false } /** * Check if the window is full screened */ fn is_full_screen_window(conn: &xcb::Connection, window: x::Window) -> bool { - let state_window_atom = get_window_state_atom(&conn); - let state_fullscreen_atom = get_window_state_fullscreen_atom(&conn); + let state_window_atom = get_window_state_atom(conn); + let state_fullscreen_atom = get_window_state_fullscreen_atom(conn); if state_window_atom != x::ATOM_NONE && state_fullscreen_atom != x::ATOM_NONE { let window_state = conn.send_request(&x::GetProperty { delete: false, @@ -389,11 +387,11 @@ fn is_full_screen_window(conn: &xcb::Connection, window: x::Window) -> bool { property: state_window_atom, r#type: x::ATOM_ATOM, long_offset: 0, - long_length: std::u32::MAX, + long_length: u32::MAX, }); if let Ok(window_state) = conn.wait_for_reply(window_state) { return window_state.value().contains(&state_fullscreen_atom); } } - return false; + false } diff --git a/src/linux/mod.rs b/x-win-rs/src/linux/mod.rs similarity index 82% rename from src/linux/mod.rs rename to x-win-rs/src/linux/mod.rs index d1c65e1..da3ce64 100644 --- a/src/linux/mod.rs +++ b/x-win-rs/src/linux/mod.rs @@ -1,14 +1,14 @@ #![deny(unused_imports)] mod api; -use crate::common::api::API; +use crate::common::api::Api; use api::LinuxAPI; use self::api::APIGnome; -pub fn init_platform_api() -> impl API { - LinuxAPI { } +pub fn init_platform_api() -> impl Api { + LinuxAPI {} } pub fn gnome_install_extension() -> bool { diff --git a/src/macos/api.rs b/x-win-rs/src/macos/api.rs similarity index 84% rename from src/macos/api.rs rename to x-win-rs/src/macos/api.rs index ac05f91..e415dd2 100644 --- a/src/macos/api.rs +++ b/x-win-rs/src/macos/api.rs @@ -26,7 +26,7 @@ use core_graphics::window::{ use crate::common::x_win_struct::icon_info::IconInfo; use crate::common::{ - api::{empty_entity, os_name, API}, + api::{empty_entity, os_name, Api}, x_win_struct::{ process_info::ProcessInfo, usage_info::UsageInfo, window_info::WindowInfo, window_position::WindowPosition, @@ -40,10 +40,10 @@ pub struct MacosAPI {} /** * Impl. for Darwin system */ -impl API for MacosAPI { +impl Api for MacosAPI { fn get_active_window(&self) -> WindowInfo { let windows: Vec = get_windows_informations(true); - if windows.len() > 0 { + if !windows.is_empty() { let t: &WindowInfo = windows.first().unwrap(); t.clone() as WindowInfo } else { @@ -79,10 +79,10 @@ impl API for MacosAPI { let byte_slice: &[u8] = std::slice::from_raw_parts(bytes, length); let data = base64::prelude::BASE64_STANDARD.encode(byte_slice); return IconInfo { - data: format!("data:image/png;base64,{}", data).to_owned(), + data: format!("data:image/png;base64,{}", data), width: imagesize.0 as u32, height: imagesize.1 as u32, - } + }; } } } @@ -195,12 +195,12 @@ fn get_windows_informations(only_active: bool) -> Vec { let mut url: String = String::new(); - if is_browser_bundle_id(&bundle_identifier) { + if is_browser_bundle_id(bundle_identifier) { let mut command = format!( "tell app id \"{}\" to get URL of active tab of front window", bundle_identifier ); - if is_from_document(&bundle_identifier) { + if is_from_document(bundle_identifier) { command = format!( "tell app id \"{}\" to get URL of front document", bundle_identifier @@ -241,48 +241,48 @@ fn get_windows_informations(only_active: bool) -> Vec { } } - return windows; + windows } fn is_browser_bundle_id(bundle_id: &str) -> bool { - match bundle_id { + matches!( + bundle_id, "com.apple.Safari" - | "com.apple.SafariTechnologyPreview" - | "com.google.Chrome" - | "com.google.Chrome.beta" - | "com.google.Chrome.dev" - | "com.google.Chrome.canary" - | "org.mozilla.firefox" - | "org.mozilla.firefoxdeveloperedition" - | "com.brave.Browser" - | "com.brave.Browser.beta" - | "com.brave.Browser.nightly" - | "com.microsoft.edgemac" - | "com.microsoft.edgemac.Beta" - | "com.microsoft.edgemac.Dev" - | "com.microsoft.edgemac.Canary" - | "com.mighty.app" - | "com.ghostbrowser.gb1" - | "com.bookry.wavebox" - | "com.pushplaylabs.sidekick" - | "com.operasoftware.Opera" - | "com.operasoftware.OperaNext" - | "com.operasoftware.OperaDeveloper" - | "com.operasoftware.OperaGX" - | "com.vivaldi.Vivaldi" - | "com.kagi.kagimacOS" - | "company.thebrowser.Browser" - | "com.sigmaos.sigmaos.macos" - | "com.SigmaOS.SigmaOS" => true, - _ => false, - } + | "com.apple.SafariTechnologyPreview" + | "com.google.Chrome" + | "com.google.Chrome.beta" + | "com.google.Chrome.dev" + | "com.google.Chrome.canary" + | "org.mozilla.firefox" + | "org.mozilla.firefoxdeveloperedition" + | "com.brave.Browser" + | "com.brave.Browser.beta" + | "com.brave.Browser.nightly" + | "com.microsoft.edgemac" + | "com.microsoft.edgemac.Beta" + | "com.microsoft.edgemac.Dev" + | "com.microsoft.edgemac.Canary" + | "com.mighty.app" + | "com.ghostbrowser.gb1" + | "com.bookry.wavebox" + | "com.pushplaylabs.sidekick" + | "com.operasoftware.Opera" + | "com.operasoftware.OperaNext" + | "com.operasoftware.OperaDeveloper" + | "com.operasoftware.OperaGX" + | "com.vivaldi.Vivaldi" + | "com.kagi.kagimacOS" + | "company.thebrowser.Browser" + | "com.sigmaos.sigmaos.macos" + | "com.SigmaOS.SigmaOS" + ) } fn is_from_document(bundle_id: &str) -> bool { - match bundle_id { - "com.apple.Safari" | "com.apple.SafariTechnologyPreview" | "com.kagi.kagimacOS" => true, - _ => false, - } + matches!( + bundle_id, + "com.apple.Safari" | "com.apple.SafariTechnologyPreview" | "com.kagi.kagimacOS" + ) } // fn is_firefox_browser(bundle_id: &str) -> bool { @@ -294,19 +294,16 @@ fn is_from_document(bundle_id: &str) -> bool { // } fn execute_applescript(script: &str) -> String { - let output = Command::new("osascript").args(&["-e", script]).output(); - if output.is_ok() { - return String::from_utf8_lossy(&output.unwrap().stdout) - .trim() - .to_owned(); + let output = Command::new("osascript").args(["-e", script]).output(); + if let Ok(output) = output { + return String::from_utf8_lossy(&output.stdout).trim().to_owned(); } - return "".to_owned(); + "".into() } fn get_screen_rect() -> NSRect { let screen = unsafe { NSScreen::mainScreen(nil) }; - let frame = unsafe { NSScreen::frame(screen) }; - frame + unsafe { NSScreen::frame(screen) } } fn is_full_screen(window_rect: CGRect, screen_rect: NSRect) -> bool { diff --git a/x-win-rs/src/macos/mod.rs b/x-win-rs/src/macos/mod.rs new file mode 100644 index 0000000..7607750 --- /dev/null +++ b/x-win-rs/src/macos/mod.rs @@ -0,0 +1,10 @@ +#![deny(unused_imports)] + +mod api; + +use crate::common::api::Api; +use api::MacosAPI; + +pub fn init_platform_api() -> impl Api { + MacosAPI {} +} diff --git a/src/win32/api.rs b/x-win-rs/src/win32/api.rs similarity index 93% rename from src/win32/api.rs rename to x-win-rs/src/win32/api.rs index 855c36d..2a54432 100644 --- a/src/win32/api.rs +++ b/x-win-rs/src/win32/api.rs @@ -18,7 +18,7 @@ use windows::{ }; use crate::common::{ - api::{empty_entity, os_name, API}, + api::{empty_entity, os_name, Api}, x_win_struct::{ icon_info::IconInfo, process_info::ProcessInfo, usage_info::UsageInfo, window_info::WindowInfo, window_position::WindowPosition, @@ -71,7 +71,7 @@ pub struct WindowsAPI {} /** * Impl. for windows system */ -impl API for WindowsAPI { +impl Api for WindowsAPI { fn get_active_window(&self) -> WindowInfo { let hwnd = unsafe { GetForegroundWindow() }; get_window_information(hwnd) @@ -142,7 +142,7 @@ impl API for WindowsAPI { lpbmi.bmiHeader.biHeight = -cbitmap.bmHeight; lpbmi.bmiHeader.biPlanes = 1; lpbmi.bmiHeader.biBitCount = 32; - lpbmi.bmiHeader.biCompression = BI_RGB.0 as u32; + lpbmi.bmiHeader.biCompression = BI_RGB.0; let hdc = unsafe { windows::Win32::Graphics::Gdi::CreateCompatibleDC(None) }; let mut buffer: Vec = vec![0u8; (cbitmap.bmHeight * cbitmap.bmWidth * 4) as usize]; @@ -193,19 +193,20 @@ impl API for WindowsAPI { } unsafe { if !phiconlarge.0.is_null() { - let _ = DestroyIcon(phiconlarge).unwrap(); + DestroyIcon(phiconlarge).unwrap(); } if !phiconsmall.0.is_null() { - let _ = DestroyIcon(phiconsmall).unwrap(); + DestroyIcon(phiconsmall).unwrap(); } }; } } - return IconInfo { + + IconInfo { data: "".to_owned(), height: 0, width: 0, - }; + } } } @@ -242,7 +243,8 @@ unsafe extern "system" fn enum_desktop_windows_proc boo } } } - return TRUE; + + TRUE } } @@ -290,7 +292,7 @@ fn is_fullscreen(hwnd: HWND) -> BOOL { return TRUE; } } - return FALSE; + FALSE } /** @@ -298,13 +300,13 @@ fn is_fullscreen(hwnd: HWND) -> BOOL { */ fn open_process_handle(process_id: u32) -> Result { let handle = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, process_id) }; - Ok(handle.map_err(|_| ())?) + handle.map_err(|_| ()) } /** * Method to close opend handle */ -fn close_process_handle(handle: HANDLE) -> () { +fn close_process_handle(handle: HANDLE) { unsafe { let _ = CloseHandle(handle); }; @@ -340,11 +342,9 @@ fn get_rect_window(hwnd: HWND) -> WindowPosition { * Get window title from HWND */ fn get_window_title(hwnd: HWND) -> String { - let title: String; let mut v: Vec = vec![0; 255]; let title_len = unsafe { GetWindowTextW(hwnd, &mut v) }; - title = String::from_utf16_lossy(&v[0..(title_len as usize)]); - title + String::from_utf16_lossy(&v[0..(title_len as usize)]) } /** @@ -369,7 +369,7 @@ fn get_process_path(phlde: HANDLE) -> Result { /** * Get process name with help of the process path */ -fn get_process_name_from_path(process_path: &PathBuf) -> Result { +fn get_process_name_from_path(process_path: &Path) -> Result { let lptstrfilename: windows::core::HSTRING = process_path.as_os_str().into(); let dwlen: u32 = unsafe { GetFileVersionInfoSizeW(&lptstrfilename, Some(std::ptr::null_mut())) }; if dwlen == 0 { @@ -400,13 +400,13 @@ fn get_process_name_from_path(process_path: &PathBuf) -> Result { let lang: &[LangCodePage] = unsafe { std::slice::from_raw_parts(lplpbuffer as *const LangCodePage, 1) }; - if lang.len() == 0 { + if lang.is_empty() { return Err(()); } let mut query_len: u32 = 0; - let lang = lang.get(0).unwrap(); + let lang = lang.first().unwrap(); let lang_code = format!( "\\StringFileInfo\\{:04x}{:04x}\\FileDescription", lang.w_language, lang.w_code_page @@ -440,7 +440,7 @@ fn get_process_name_from_path(process_path: &PathBuf) -> Result { let file_description = String::from_utf16_lossy(file_description); let file_description = file_description.trim_matches(char::from(0)).to_owned(); - return Ok(file_description); + Ok(file_description) } /** @@ -455,19 +455,19 @@ fn get_process_path_and_name(phlde: HANDLE, hwnd: HWND, process_id: u32) -> Proc }; if let Ok(process_path) = get_process_path(phlde) { - process_info.exec_name = process_path + process_path .file_stem() .unwrap_or(std::ffi::OsStr::new("")) .to_str() .unwrap_or("") - .to_owned(); - process_info.path = process_path + .clone_into(&mut process_info.exec_name); + process_path .clone() .into_os_string() .into_string() .unwrap() - .to_owned(); - process_info.name = process_info.exec_name.clone(); + .clone_into(&mut process_info.path); + process_info.exec_name.clone_into(&mut process_info.name); if process_info .exec_name @@ -510,8 +510,8 @@ fn get_window_information(hwnd: HWND) -> WindowInfo { let exec_name = parent_process.exec_name.to_lowercase(); if exec_name.ne(&"searchhost") { let mut url: String = "".to_owned(); - if is_browser(&exec_name.as_str()) { - url = get_browser_url(hwnd, exec_name).to_owned(); + if is_browser(exec_name.as_str()) { + get_browser_url(hwnd, exec_name).clone_into(&mut url); } window_info = WindowInfo { id, @@ -541,10 +541,10 @@ fn get_browser_url(hwnd: HWND, exec_name: String) -> String { let element: IUIAutomationElement = element.unwrap(); /* Chromium part to get url from search bar */ return match &exec_name.to_lowercase() { - x if x.contains(&"firefox") => { + x if x.contains("firefox") => { get_url_from_automation_id(&automation, &element, "urlbar-input".to_owned()) } - x if x.contains(&"msedge") => { + x if x.contains("msedge") => { let mut value = get_url_from_automation_id(&automation, &element, "view_1022".to_owned()); if value.eq(&"") { @@ -651,11 +651,22 @@ fn get_url_for_chromium_from_ctrlk( } fn is_browser(browser_name: &str) -> bool { - match browser_name { - "chrome" | "msedge" | "opera" | "opera_gx" | "brave" | "vivaldi" | "iron" | "epic" - | "chromium" | "ucozmedia" | "blisk" | "maxthon" | "beaker" | "beaker browser" | "firefox" => { - true - } - _ => false, - } + matches!( + browser_name, + "chrome" + | "msedge" + | "opera" + | "opera_gx" + | "brave" + | "vivaldi" + | "iron" + | "epic" + | "chromium" + | "ucozmedia" + | "blisk" + | "maxthon" + | "beaker" + | "beaker browser" + | "firefox" + ) } diff --git a/x-win-rs/src/win32/mod.rs b/x-win-rs/src/win32/mod.rs new file mode 100644 index 0000000..0915009 --- /dev/null +++ b/x-win-rs/src/win32/mod.rs @@ -0,0 +1,10 @@ +#![deny(unused_imports)] + +mod api; + +use crate::common::api::Api; +use api::WindowsAPI; + +pub fn init_platform_api() -> impl Api { + WindowsAPI {} +} diff --git a/yarn.lock b/yarn.lock index f97cc6b..2252311 100644 --- a/yarn.lock +++ b/yarn.lock @@ -30,6 +30,7 @@ __metadata: dependencies: "@napi-rs/cli": "npm:^2.18.3" ava: "npm:^6.1.3" + husky: "npm:^9.1.4" languageName: unknown linkType: soft @@ -759,6 +760,15 @@ __metadata: languageName: node linkType: hard +"husky@npm:^9.1.4": + version: 9.1.4 + resolution: "husky@npm:9.1.4" + bin: + husky: bin.js + checksum: 10c0/f5185003bef9ad9ec3f40e821963e4c12409b993fdcab89e3d660bed7d8c9d8bfd399f05222e27e0ead6589601fb1bb08d1a589c51751a4ab0547ead3429b8de + languageName: node + linkType: hard + "ignore-by-default@npm:^2.1.0": version: 2.1.0 resolution: "ignore-by-default@npm:2.1.0"