Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add focused window provider #157

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
654 changes: 416 additions & 238 deletions Cargo.lock

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Through the `zebar` NPM package, Zebar exposes various system information via re
- [cpu](#CPU)
- [date](#Date)
- [disk](#Disk)
- [focusedWindow](#focused-window)
- [glazewm](#GlazeWM)
- [host](#Host)
- [ip](#IP)
Expand Down Expand Up @@ -195,6 +196,23 @@ No config options.
| `iecValue` | Bytes converted in according to the IEC standard. 1024 bytes in a kibibyte. | `number` |
| `iecUnit` | Unit of the converted bytes in according to the IEC standard. KiB, MiB, ... | `string` |



## Focused Window

#### Config

No config options.

#### Outputs

| Variable | Description | Return type | Supported OS |
| --------------------- | ----------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `title` | Title of the current focused window. | `string` | <img src="https://github.com/glzr-io/zebar/assets/34844898/568e90c8-cd32-49a5-a17f-ab233d41f1aa" alt="microsoft icon" width="24"> |
| `icon` | Icon of the current focused window, encoded as base64 | `string` | <img src="https://github.com/glzr-io/zebar/assets/34844898/568e90c8-cd32-49a5-a17f-ab233d41f1aa" alt="microsoft icon" width="24"> |



### GlazeWM

#### Config
Expand Down
10 changes: 5 additions & 5 deletions examples/boilerplate-solid-ts/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Zebar</title>
<script type="module" crossorigin src="./assets/index-CsvkC0F_.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-DhTpoJOY.css">
</head>
<body>
<p>
Boilerplate for SolidJS with TypeScript. Run <code>npm i</code> and
<code>npm run dev</code> in the <code>solid-ts</code> directory to
run this example.
</p>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
43 changes: 41 additions & 2 deletions examples/boilerplate-solid-ts/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/* @refresh reload */
import './index.css';
import { render } from 'solid-js/web';
import { createStore } from 'solid-js/store';
import * as zebar from 'zebar';
import { createSignal, createEffect } from 'solid-js';

const providers = zebar.createProviderGroup({
audio: { type: 'audio' },
cpu: { type: 'cpu' },
focusedWindow: { type: 'focusedWindow' },
battery: { type: 'battery' },
memory: { type: 'memory' },
weather: { type: 'weather' },
Expand All @@ -17,8 +18,42 @@ render(() => <App />, document.getElementById('root')!);

function App() {
const [output, setOutput] = createStore(providers.outputMap);
const [iconUrl, setIconUrl] = createSignal<string | null>(null);
const [lastTitle, setLastTitle] = createSignal<string | null>(null);

providers.onOutput(outputMap => setOutput(outputMap));
providers.onOutput(outputMap => {
setOutput(outputMap);

// Only process icon if window title has changed
if (outputMap.focusedWindow?.title !== lastTitle()) {
setLastTitle(outputMap.focusedWindow?.title || null);

const icon = outputMap.focusedWindow?.icon;
if (icon) {
try {
const binary = atob(icon);
const array = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
array[i] = binary.charCodeAt(i);
}
const blob = new Blob([array], { type: 'image/png' });

// Clean up old URL if it exists
if (iconUrl()) {
URL.revokeObjectURL(iconUrl()!);
}

const url = URL.createObjectURL(blob);
setIconUrl(url);
} catch (error) {
console.error('Error decoding icon data:', error);
setIconUrl(null);
}
} else {
setIconUrl(null);
}
}
});

return (
<div class="app">
Expand All @@ -31,6 +66,10 @@ function App() {
{output.media?.session?.artist}
</div>
<div class="chip">CPU usage: {output.cpu?.usage}</div>
{iconUrl() && (
<img height="20" width="20" src={iconUrl()!} alt="icon" />
)}
<div class="chip">Focused window: {output.focusedWindow?.title}</div>
<div class="chip">
Battery charge: {output.battery?.chargePercent}
</div>
Expand Down
9 changes: 9 additions & 0 deletions packages/client-api/src/providers/create-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import type {
DateProviderConfig,
DateProvider,
} from './date/date-provider-types';
import { createFocusedWindowProvider } from './focused-window/create-focused-window-provider';
import type {
FocusedWindowProviderConfig,
FocusedWindowProvider,
} from './focused-window/focused-window-provider-types';
import { createGlazeWmProvider } from './glazewm/create-glazewm-provider';
import type {
GlazeWmProviderConfig,
Expand Down Expand Up @@ -71,6 +76,7 @@ export interface ProviderConfigMap {
battery: BatteryProviderConfig;
cpu: CpuProviderConfig;
date: DateProviderConfig;
focusedWindow: FocusedWindowProviderConfig;
glazewm: GlazeWmProviderConfig;
host: HostProviderConfig;
ip: IpProviderConfig;
Expand All @@ -88,6 +94,7 @@ export interface ProviderMap {
battery: BatteryProvider;
cpu: CpuProvider;
date: DateProvider;
focusedWindow: FocusedWindowProvider;
glazewm: GlazeWmProvider;
host: HostProvider;
ip: IpProvider;
Expand Down Expand Up @@ -129,6 +136,8 @@ export function createProvider<T extends ProviderConfig>(
return createCpuProvider(config) as any;
case 'date':
return createDateProvider(config) as any;
case 'focusedWindow':
return createFocusedWindowProvider(config) as any;
case 'glazewm':
return createGlazeWmProvider(config) as any;
case 'host':
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { z } from 'zod';
import { createBaseProvider } from '../create-base-provider';
import { onProviderEmit } from '~/desktop';
import type {
FocusedWindowOutput,
FocusedWindowProvider,
FocusedWindowProviderConfig,
} from './focused-window-provider-types';

const focusedWindowProviderConfigSchema = z.object({
type: z.literal('focusedWindow'),
});

export function createFocusedWindowProvider(
config: FocusedWindowProviderConfig,
): FocusedWindowProvider {
const mergedConfig = focusedWindowProviderConfigSchema.parse(config);

return createBaseProvider(mergedConfig, async queue => {
return onProviderEmit<FocusedWindowOutput>(
mergedConfig,
({ result }) => {
if ('error' in result) {
queue.error(result.error);
} else {
queue.output(result.output);
}
},
);
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Provider } from '../create-base-provider';

export interface FocusedWindowProviderConfig {
type: 'focusedWindow';
}

export interface FocusedWindowOutput {
title: string;
icon: string;
}

export type FocusedWindowProvider = Provider<
FocusedWindowProviderConfig,
FocusedWindowOutput
>;
1 change: 1 addition & 0 deletions packages/client-api/src/providers/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './battery/battery-provider-types';
export * from './cpu/cpu-provider-types';
export * from './date/date-provider-types';
export * from './focused-window/focused-window-provider-types';
export * from './glazewm/glazewm-provider-types';
export * from './host/host-provider-types';
export * from './ip/ip-provider-types';
Expand Down
6 changes: 5 additions & 1 deletion packages/desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ tauri-build = { version = "2.0", features = [] }
[dependencies]
anyhow = "1"
async-trait = "0.1"
base64 = "0.22.1"
clap = { version = "4", features = ["derive"] }
crossbeam = "0.8"
image = "0.24"
reqwest = { version = "0.11", features = ["json"] }
tauri = { version = "2.0", features = [
"devtools",
Expand All @@ -37,7 +39,6 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
netdev = "0.24"
regex = "1"

[target.'cfg(target_os = "windows")'.dependencies]
komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.28" }
windows-core = "0.58"
Expand All @@ -50,6 +51,9 @@ windows = { version = "0.58", features = [
"Win32_Media",
"Win32_Media_Audio",
"Win32_Media_Audio_Endpoints",
"Win32_Storage",
"Win32_Storage_Packaging",
"Win32_Storage_Packaging_Appx",
"Win32_System_Console",
"Win32_System_SystemServices",
"Win32_UI_Input_KeyboardAndMouse",
Expand Down
Loading
Loading