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: language provider (windows only) #85

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ And in the Zebar config (if using the default generated one), replace the GlazeW
height: 30px;
color: #ffffffe6;
border: none;
border-radius: 2px;
border-radius: 2px;
}

.workspace.active {
Expand Down Expand Up @@ -452,6 +452,20 @@ Self provider doesn't take any config options.
| `parsedConfig` | Parsed config for this element. | `WindowConfig \| GroupConfig \| TemplateConfig` | <img src="https://github.com/glzr-io/zebar/assets/34844898/568e90c8-cd32-49a5-a17f-ab233d41f1aa" alt="microsoft icon" width="24"><img src="https://github.com/glzr-io/zebar/assets/34844898/005a0760-da9d-460e-b533-9b2aba7f5c03" alt="apple icon" width="24"><img src="https://github.com/glzr-io/zebar/assets/34844898/1c5d91b1-879f-42a6-945e-912a11daebb4" alt="linux icon" width="24"> |
| `globalConfig` | Global user config. | `GlobalConfig` | <img src="https://github.com/glzr-io/zebar/assets/34844898/568e90c8-cd32-49a5-a17f-ab233d41f1aa" alt="microsoft icon" width="24"><img src="https://github.com/glzr-io/zebar/assets/34844898/005a0760-da9d-460e-b533-9b2aba7f5c03" alt="apple icon" width="24"><img src="https://github.com/glzr-io/zebar/assets/34844898/1c5d91b1-879f-42a6-945e-912a11daebb4" alt="linux icon" width="24"> |

### Language (Windows only)

### Provider config

| Option | Description | Option type | Default value |
| ------------------ | -------------------------------------------------- | ----------- | ------------- |
| `refresh_interval` | How often this provider refreshes in milliseconds. | `number` | `5000` |

### Variables

| Variable | Description | Return type | Supported OS |
| ------------- | ------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `language` | Current language, for example 'en-US' | `string` | <img src="https://github.com/glzr-io/zebar/assets/34844898/568e90c8-cd32-49a5-a17f-ab233d41f1aa" alt="microsoft icon" width="24">

## Util

## Functions
Expand Down
3 changes: 3 additions & 0 deletions packages/client-api/src/providers/create-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { createNetworkProvider } from './network/create-network-provider';
import { createSelfProvider } from './self/create-self-provider';
import { createUtilProvider } from './util/create-util-provider';
import { createWeatherProvider } from './weather/create-weather-provider';
import { createLanguageProvider } from './language/create-language-provider';
import { ProviderType, type ProviderConfig } from '~/user-config';
import type { ElementContext } from '~/element-context.model';
import type { PickPartial } from '~/utils';
Expand Down Expand Up @@ -52,6 +53,8 @@ export async function createProvider(
return createUtilProvider(config, owner);
case ProviderType.WEATHER:
return createWeatherProvider(config, owner);
case ProviderType.LANGUAGE:
return createLanguageProvider(config, owner);
default:
throw new Error('Not a supported provider type.');
}
Expand Down
1 change: 1 addition & 0 deletions packages/client-api/src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './weather/create-weather-provider';
export * from './create-provider-listener';
export * from './create-provider';
export * from './get-element-providers';
export * from './language/create-language-provider';
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { runWithOwner, type Owner, createEffect } from 'solid-js';

import type { LanguageProviderConfig } from '~/user-config';
import { createProviderListener } from '../create-provider-listener';
import { createStore } from 'solid-js/store';

export interface LanguageVariables {
language: string;
}

export async function createLanguageProvider(
config: LanguageProviderConfig,
owner: Owner,
) {
const providerListener = await createProviderListener<
LanguageProviderConfig,
LanguageVariables
>(config, owner);

const [languageVariables, setLanguageVariables] = createStore(
await getVariables(),
);

runWithOwner(owner, () => {
createEffect(async () => setLanguageVariables(await getVariables()));
});

async function getVariables() {
const state = providerListener();
return { language: state.language };
}

return {
get language() {
return languageVariables.language;
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
SelfProviderConfigSchema,
UtilProviderConfigSchema,
WeatherProviderConfigSchema,
LanguageProviderConfigSchema,
} from './providers';

export const ProviderConfigSchema = z.union([
Expand All @@ -31,6 +32,7 @@ export const ProviderConfigSchema = z.union([
SelfProviderConfigSchema,
UtilProviderConfigSchema,
WeatherProviderConfigSchema,
LanguageProviderConfigSchema,
]);

export type ProviderConfig = Prettify<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum ProviderType {
SELF = 'self',
UTIL = 'util',
WEATHER = 'weather',
LANGUAGE = 'language',
}

export const ProviderTypeSchema = z.nativeEnum(ProviderType);
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './network-provider-config.model';
export * from './self-provider-config.model';
export * from './util-provider-config.model';
export * from './weather-provider-config.model';
export * from './language-provider-config.model';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { z } from 'zod';

import { ProviderType } from '../provider-type.model';

export const LanguageProviderConfigSchema = z.object({
type: z.literal(ProviderType.LANGUAGE),

refresh_interval: z.coerce.number().default(5 * 1000),
});

export type LanguageProviderConfig = z.infer<
typeof LanguageProviderConfigSchema
>;
2 changes: 1 addition & 1 deletion packages/desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ regex = "1"

[target.'cfg(target_os = "windows")'.dependencies]
komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.28" }
windows = { version = "0.57", features = [] }
windows = { version = "0.57", features = ["Win32_UI_WindowsAndMessaging", "Win32_UI_Input_KeyboardAndMouse", "Win32_Globalization", "Win32_System_SystemServices"] }

[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.25"
Expand Down
4 changes: 4 additions & 0 deletions packages/desktop/src/providers/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use serde::Deserialize;

#[cfg(windows)]
use super::komorebi::KomorebiProviderConfig;
#[cfg(windows)]
use super::language::LanguageProviderConfig;
use super::{
battery::BatteryProviderConfig, cpu::CpuProviderConfig,
host::HostProviderConfig, ip::IpProviderConfig,
Expand All @@ -21,4 +23,6 @@ pub enum ProviderConfig {
Memory(MemoryProviderConfig),
Network(NetworkProviderConfig),
Weather(WeatherProviderConfig),
#[cfg(windows)]
Language(LanguageProviderConfig),
}
5 changes: 5 additions & 0 deletions packages/desktop/src/providers/language/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use serde::Deserialize;

#[derive(Deserialize, Debug)]
#[serde(tag = "type", rename = "inputlanguage")]
pub struct LanguageProviderConfig {}
7 changes: 7 additions & 0 deletions packages/desktop/src/providers/language/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod config;
mod provider;
mod variables;

pub use config::*;
pub use provider::*;
pub use variables::*;
108 changes: 108 additions & 0 deletions packages/desktop/src/providers/language/provider.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use std::{sync::Arc, time::Duration};

use async_trait::async_trait;
use tokio::{
sync::mpsc::Sender,
task::{self, AbortHandle},
time::sleep,
};
use windows::Win32::{
Globalization::{LCIDToLocaleName, LOCALE_ALLOW_NEUTRAL_NAMES},
System::SystemServices::LOCALE_NAME_MAX_LENGTH,
UI::{
Input::KeyboardAndMouse::GetKeyboardLayout,
WindowsAndMessaging::{GetForegroundWindow, GetWindowThreadProcessId},
},
};

use super::{LanguageProviderConfig, LanguageVariables};
use crate::providers::{
provider::Provider,
provider_ref::{ProviderOutput, VariablesResult},
variables::ProviderVariables,
};

pub struct LanguageProvider {
pub config: Arc<LanguageProviderConfig>,
abort_handle: Option<AbortHandle>,
}

impl LanguageProvider {
pub fn new(config: LanguageProviderConfig) -> LanguageProvider {
LanguageProvider {
config: Arc::new(config),
abort_handle: None,
}
}
}

#[async_trait]
impl Provider for LanguageProvider {
async fn on_start(
&mut self,
config_hash: &str,
emit_output_tx: Sender<ProviderOutput>,
) {
let config_hash = config_hash.to_string();
let task_handle = task::spawn(async move {
let mut previous = 0;
loop {
sleep(Duration::from_millis(150)).await;

let keyboard_layout = unsafe {
GetKeyboardLayout(GetWindowThreadProcessId(
GetForegroundWindow(),
None,
))
};
let lang_id = (keyboard_layout.0 as u32) & 0xffff;

if lang_id == previous {
continue;
}
previous = lang_id;

let mut locale_name = [0; LOCALE_NAME_MAX_LENGTH as usize];

let result = unsafe {
LCIDToLocaleName(
lang_id,
Some(&mut locale_name),
LOCALE_ALLOW_NEUTRAL_NAMES,
)
};

if result > 0 {
let language_name =
String::from_utf16_lossy(&locale_name[..result as usize]);
_ = emit_output_tx
.send(ProviderOutput {
config_hash: config_hash.clone(),
variables: VariablesResult::Data(
ProviderVariables::Language(LanguageVariables {
language: language_name,
}),
),
})
.await;
}
}
});

self.abort_handle = Some(task_handle.abort_handle());
_ = task_handle.await;
}

async fn on_refresh(
&mut self,
_config_hash: &str,
_emit_output_tx: Sender<ProviderOutput>,
) {
}

async fn on_stop(&mut self) {}

fn min_refresh_interval(&self) -> Option<Duration> {
None
}
}
7 changes: 7 additions & 0 deletions packages/desktop/src/providers/language/variables.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use serde::Serialize;

#[derive(Serialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct LanguageVariables {
pub language: String,
}
2 changes: 2 additions & 0 deletions packages/desktop/src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ pub mod host;
pub mod ip;
#[cfg(windows)]
pub mod komorebi;
#[cfg(windows)]
pub mod language;
pub mod memory;
pub mod network;
pub mod provider;
Expand Down
6 changes: 6 additions & 0 deletions packages/desktop/src/providers/provider_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use tracing::info;

#[cfg(windows)]
use super::komorebi::KomorebiProvider;
#[cfg(windows)]
use super::language::LanguageProvider;
use super::{
battery::BatteryProvider, config::ProviderConfig, cpu::CpuProvider,
host::HostProvider, ip::IpProvider, memory::MemoryProvider,
Expand Down Expand Up @@ -169,6 +171,10 @@ impl ProviderRef {
ProviderConfig::Weather(config) => {
Box::new(WeatherProvider::new(config))
}
#[cfg(windows)]
ProviderConfig::Language(config) => {
Box::new(LanguageProvider::new(config))
}
#[allow(unreachable_patterns)]
_ => bail!("Provider not supported on this operating system."),
};
Expand Down
4 changes: 4 additions & 0 deletions packages/desktop/src/providers/variables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use serde::Serialize;

#[cfg(windows)]
use super::komorebi::KomorebiVariables;
#[cfg(windows)]
use super::language::LanguageVariables;
use super::{
battery::BatteryVariables, cpu::CpuVariables, host::HostVariables,
ip::IpVariables, memory::MemoryVariables, network::NetworkVariables,
Expand All @@ -20,4 +22,6 @@ pub enum ProviderVariables {
Memory(MemoryVariables),
Network(NetworkVariables),
Weather(WeatherVariables),
#[cfg(windows)]
Language(LanguageVariables),
}