Skip to content
This repository has been archived by the owner on Sep 25, 2024. It is now read-only.

Commit

Permalink
Merge pull request #5 from ptazithos/feature/per-window-configuration
Browse files Browse the repository at this point in the history
feature/per-window-configuration
  • Loading branch information
ptazithos authored Jul 16, 2024
2 parents 35c0349 + 4535f28 commit 4bb1465
Show file tree
Hide file tree
Showing 22 changed files with 399 additions and 157 deletions.
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,17 @@ scoop install https://github.com/ptazithos/glazewm-extra/releases/download/v0.1.
The config file located at "${UserFolder}/.config/glazewm-extra.toml". The default config is as follows:

```toml
[translucent_window]
enable = true
alpha = 220
[[window_rules]]
command = "set title false"
match_process_name = ".*"

[[focused_window_rules]]
command = "set translucent 255"
match_process_name = ".*"

[title_bar]
# True for hiding all titlebars
enable = true
[[unfocused_window_rules]]
command = "set translucent 220"
match_process_name = ".*"
```

## License
Expand Down
6 changes: 5 additions & 1 deletion app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@ edition = "2021"
tauri-build = { version = "1", features = [] }

[dependencies]
tauri = { version = "1", features = [ "global-shortcut-all", "window-all",
tauri = { version = "1", features = [
"global-shortcut-all",
"window-all",
"process-exit",
"system-tray",
"shell-open",
] }
windows = { version = "0.58.0", features = [
"Win32_UI_WindowsAndMessaging",
"Win32_Graphics_Gdi",
"Win32_System_ProcessStatus",
"Win32_System_Threading",
] }
tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
Expand Down
59 changes: 29 additions & 30 deletions app/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,45 @@ use std::fs;
use tauri::{App, Manager};

#[derive(Debug, Serialize, Deserialize)]
pub struct AppConfig {
pub translucent_window: Option<TranslucentWindowConfig>,
pub title_bar: Option<TitleBarConfig>,
}

impl Default for AppConfig {
fn default() -> Self {
Self {
translucent_window: Some(TranslucentWindowConfig::default()),
title_bar: Some(TitleBarConfig::default()),
}
}
pub struct WindowRule {
pub command: String,
pub match_process_name: Option<String>,
pub match_class_name: Option<String>,
pub match_title: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct TranslucentWindowConfig {
pub enable: Option<bool>,
pub alpha: Option<u8>,
pub struct AppConfig {
window_rules: Vec<WindowRule>,
focused_window_rules: Vec<WindowRule>,
unfocused_window_rules: Vec<WindowRule>,
}

impl Default for TranslucentWindowConfig {
impl Default for AppConfig {
fn default() -> Self {
Self {
enable: Some(true),
alpha: Some(220),
AppConfig {
window_rules: vec![WindowRule {
command: "set title false".to_string(),
match_process_name: Some(".*".to_string()),
match_class_name: None,
match_title: None,
}],
focused_window_rules: vec![WindowRule {
command: "set translucent 255".to_string(),
match_process_name: Some(".*".to_string()),
match_class_name: None,
match_title: None,
}],
unfocused_window_rules: vec![WindowRule {
command: "set translucent 220".to_string(),
match_process_name: Some(".*".to_string()),
match_class_name: None,
match_title: None,
}],
}
}
}

#[derive(Debug, Serialize, Deserialize)]
pub struct TitleBarConfig {
pub enable: Option<bool>,
}

impl Default for TitleBarConfig {
fn default() -> Self {
Self { enable: Some(true) }
}
}

pub fn setup_store(app: &mut App) {
let mut config_path = dirs::home_dir().unwrap();
config_path.push(".config");
Expand Down
4 changes: 3 additions & 1 deletion app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ fn main() {
.invoke_handler(tauri::generate_handler![
windows::set_window_alpha,
windows::set_window_titlebar,
windows::get_window_name,
windows::get_window_title,
windows::get_window_class,
windows::get_window_process_name,
windows::capture_window,
config::get_app_config
])
.run(tauri::generate_context!())
Expand Down
49 changes: 44 additions & 5 deletions app/src/windows.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
use std::ffi::c_void;

use windows::Win32::{
Foundation::{COLORREF, HWND, RECT},
Foundation::{CloseHandle, COLORREF, HWND, RECT},
Graphics::Gdi::{
BitBlt, CreateCompatibleBitmap, CreateCompatibleDC, DeleteDC, DeleteObject, GetDC,
GetDIBits, ReleaseDC, SelectObject, BITMAPINFO, BITMAPINFOHEADER, CAPTUREBLT,
DIB_RGB_COLORS, RGBQUAD, SRCCOPY,
},
System::{
ProcessStatus::GetProcessImageFileNameW,
Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ},
},
UI::WindowsAndMessaging::{
GetClassNameW, GetClientRect, GetWindowLongPtrW, GetWindowRect, GetWindowTextLengthW,
GetWindowTextW, SetLayeredWindowAttributes, SetWindowLongPtrW, SetWindowPos, GWL_EXSTYLE,
GWL_STYLE, HWND_DESKTOP, HWND_TOP, LWA_ALPHA, SWP_FRAMECHANGED, SWP_NOMOVE, WS_CAPTION,
WS_EX_LAYERED,
GetWindowTextW, GetWindowThreadProcessId, SetLayeredWindowAttributes, SetWindowLongPtrW,
SetWindowPos, GWL_EXSTYLE, GWL_STYLE, HWND_DESKTOP, HWND_TOP, LWA_ALPHA, SWP_FRAMECHANGED,
SWP_NOMOVE, WS_CAPTION, WS_EX_LAYERED,
},
};

Expand Down Expand Up @@ -60,7 +64,7 @@ pub fn set_window_titlebar(raw_handle: isize, titlebar: bool) {
}

#[tauri::command]
pub fn get_window_name(raw_handle: isize) -> Option<String> {
pub fn get_window_title(raw_handle: isize) -> Option<String> {
let handle = HWND(raw_handle as *mut c_void);
unsafe {
let length = GetWindowTextLengthW(handle);
Expand Down Expand Up @@ -96,6 +100,41 @@ pub fn get_window_class(raw_handle: isize) -> Option<String> {
}
}

#[tauri::command]
pub fn get_window_process_name(raw_handle: isize) -> Option<String> {
let handle = HWND(raw_handle as *mut c_void);
unsafe {
let mut process_id: u32 = 0;
GetWindowThreadProcessId(handle, Some(&mut process_id));

if process_id == 0 {
return None;
}

let process_handle = OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
false,
process_id,
);

if process_handle.is_err() {
return None;
}

let mut buffer = vec![0u16; 260];

let length = GetProcessImageFileNameW(process_handle.clone().unwrap(), &mut buffer);

if length == 0 {
return None;
}

let _ = CloseHandle(process_handle.unwrap());

return Some(String::from_utf16_lossy(&buffer[..length as usize]));
}
}

#[tauri::command]
pub fn capture_window(raw_handle: isize) {
let handle = HWND(raw_handle as *mut c_void);
Expand Down
2 changes: 1 addition & 1 deletion app/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
},
"package": {
"productName": "glazewm-extra",
"version": "0.1.4"
"version": "0.1.5"
},
"tauri": {
"allowlist": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ts-demo",
"private": true,
"version": "0.1.4",
"version": "0.1.5",
"scripts": {
"dev": "rsbuild dev",
"build": "rsbuild build",
Expand Down
13 changes: 6 additions & 7 deletions src/daemon/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import React, { useEffect, useLayoutEffect, useState } from "react";
import ReactDOM from "react-dom/client";
import { invoke } from "@tauri-apps/api/tauri";

import TitleService from "./services/titile";
import AlphaService from "./services/alpha";
import ManageService from "./services/manage";
import FocusService from "./services/focus";
import CleanUpService from "./services/cleanup";

import type { AppConfig } from "../ipc/utils";
import { type AppConfig, getAppConfig } from "../native";

const DaemonApp = () => {
const [appConfig, setAppConfig] = useState<AppConfig | null>(null);

useEffect(() => {
const init = async () => {
const appConfig = await invoke<AppConfig>("get_app_config");
const appConfig = await getAppConfig();
setAppConfig(appConfig);
};

Expand All @@ -22,8 +21,8 @@ const DaemonApp = () => {

return appConfig ? (
<>
<TitleService config={appConfig} />
<AlphaService config={appConfig} />
<ManageService config={appConfig} />
<FocusService config={appConfig} />
<CleanUpService />
</>
) : (
Expand Down
40 changes: 0 additions & 40 deletions src/daemon/services/alpha.tsx

This file was deleted.

51 changes: 51 additions & 0 deletions src/daemon/services/focus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { invoke } from "@tauri-apps/api";
import {
type FocusChangedPayload,
subscribeFocusChanged,
} from "../../ipc/subscribe";
import { useEffect } from "react";
import type { AppConfig } from "../../native";
import { getWindows } from "../../ipc/command";
import { getWindowInfo } from "../../native";
import type { Optional } from "../../utils";

const FocusService = (props: { config: AppConfig }) => {
const focusedRules = props.config.focusedWindowsRules;
const unfocusedRules = props.config.unfocusedWindowsRules;

useEffect(() => {
const setWindowStyle = async (payload: Optional<FocusChangedPayload>) => {
const focused = payload?.data?.focusedContainer?.handle;

const windows = await getWindows();
for (const window of windows) {
const hwnd = window?.handle;
if (hwnd) {
if (hwnd === focused) {
const info = await getWindowInfo(hwnd);
for (const rule of focusedRules) {
rule.apply(info);
}
} else {
const info = await getWindowInfo(hwnd);
for (const rule of unfocusedRules) {
rule.apply(info);
}
}
}
}
};

setWindowStyle({});

const handle = subscribeFocusChanged(setWindowStyle);

return () => {
handle.close();
};
}, [focusedRules, unfocusedRules]);

return <></>;
};

export default FocusService;
43 changes: 43 additions & 0 deletions src/daemon/services/manage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useEffect } from "react";
import { getWindowInfo, type AppConfig } from "../../native";
import { getWindows } from "../../ipc/command";
import { subscribeWindowManaged } from "../../ipc/subscribe";

const ManageService = (props: { config: AppConfig }) => {
const rules = props.config.windowRules;

useEffect(() => {
const setWindowsTitleBar = async () => {
const windows = await getWindows();
for (const window of windows) {
if (!window?.handle) continue;
const info = await getWindowInfo(window.handle);
for (const rule of rules) {
rule.apply(info);
}
}
};

setWindowsTitleBar();

const handle = subscribeWindowManaged(async (payload) => {
setTimeout(async () => {
const hwnd = payload?.data?.managedWindow?.handle;
if (hwnd) {
const info = await getWindowInfo(hwnd);
for (const rule of rules) {
rule.apply(info);
}
}
}, 200);
});

return () => {
handle.close();
};
}, [rules]);

return <></>;
};

export default ManageService;
Loading

0 comments on commit 4bb1465

Please sign in to comment.