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(refactor): refactor to allow generic usage of komokana #65

Merged
merged 4 commits into from
Jan 1, 2024
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 8 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ dirs = "5"
env_logger = "0.10"
json_dotpath = "1"
log = "0.4"
miow = "0.5"
parking_lot = "0.12"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
once_cell = "1"

[target.'cfg(target_os = "windows")'.dependencies]
miow = "0.5"
windows = { version = "0.52", features = ["Win32_UI_Input_KeyboardAndMouse"], optional = true }

[dependencies.windows]
version = "0.52"
features = ["Win32_UI_Input_KeyboardAndMouse"]
[features]
#[cfg(target_os = "windows")]
virtual_keys = [ "dep:windows" ]
1 change: 1 addition & 0 deletions src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub type Configuration = Vec<Entry>;
pub struct Entry {
pub exe: String,
pub target_layer: String,
pub strategy: Option<Strategy>,
pub title_overrides: Option<Vec<TitleOverride>>,
pub virtual_key_overrides: Option<Vec<VirtualKeyOverride>>,
pub virtual_key_ignores: Option<Vec<i32>>,
Expand Down
107 changes: 107 additions & 0 deletions src/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use crate::configuration::Strategy;
#[cfg(feature = "virtual_keys")]
use crate::Komokana;
#[cfg(feature = "virtual_keys")]
use crate::Provider;
use crate::{CONFIG, DEFAULT_LAYER, KANATA};
use color_eyre::Result;
use serde_json::json;
use std::io::Write;

#[derive(Debug, Copy, Clone)]
pub enum Event {
Show,
FocusChange,
}

pub fn handle_event(event: Event, exe: &str, title: Option<&str>) -> Result<()> {
let target = calculate_target(
event,
exe,
title,
if matches!(event, Event::FocusChange) {
Option::from(DEFAULT_LAYER.get().unwrap().as_ref())
} else {
None
},
);

if let Some(target) = target {
let stream = &mut KANATA.get().unwrap().get_stream();
let mut stream = stream.lock();
let request = json!({
"ChangeLayer": {
"new": target,
}
});

stream.write_all(request.to_string().as_bytes())?;
log::debug!("request sent: {request}");
};

Ok(())
}

fn calculate_target(
event: Event,
exe: &str,
title: Option<&str>,
default: Option<&str>,
) -> Option<String> {
let configuration = CONFIG.get().unwrap();
let mut new_layer = default;
for entry in configuration {
let entry_strategy = entry.strategy.clone().unwrap_or(Strategy::Equals);
if matches_with_strategy(exe, &entry.exe, &entry_strategy) {
if matches!(event, Event::FocusChange) {
new_layer = Option::from(entry.target_layer.as_str());
}

if let Some(title) = title {
if let Some(title_overrides) = &entry.title_overrides {
for title_override in title_overrides {
if matches_with_strategy( title, &title_override.title, &title_override.strategy) {
new_layer = Option::from(title_override.target_layer.as_str());
}
}

// This acts like a default target layer within the application
// which defaults back to the entry's main target layer
if new_layer.is_none() {
new_layer = Option::from(entry.target_layer.as_str());
}
}
}

#[cfg(feature = "virtual_keys")]
if matches!(event, Event::FocusChange) {
if let Some(virtual_key_overrides) = &entry.virtual_key_overrides {
for virtual_key_override in virtual_key_overrides {
if Komokana::get_key_state(virtual_key_override.virtual_key_code) < 0 {
new_layer = Option::from(virtual_key_override.targer_layer.as_str());
}
}
}

if let Some(virtual_key_ignores) = &entry.virtual_key_ignores {
for virtual_key in virtual_key_ignores {
if Komokana::get_key_state(*virtual_key) < 0 {
new_layer = None;
}
}
}
}
}
}

new_layer.and_then(|new_layer| Option::from(new_layer.to_string()))
}

fn matches_with_strategy(title: &str, config_title: &str, strategy: &Strategy) -> bool {
match strategy {
Strategy::StartsWith => title.starts_with(config_title),
Strategy::EndsWith => title.ends_with(config_title),
Strategy::Contains => title.contains(config_title),
Strategy::Equals => title.eq(config_title),
}
}
95 changes: 95 additions & 0 deletions src/kanata.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use color_eyre::Result;
use json_dotpath::DotPaths;
use parking_lot::Mutex;
use std::io::Read;
use std::net::TcpStream;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use std::time::Duration;
use crate::TMPFILE;

pub static KANATA_DISCONNECTED: AtomicBool = AtomicBool::new(false);

#[derive(Debug)]
pub struct Kanata {
stream: Arc<Mutex<TcpStream>>,
port: i32,
}

impl Kanata {
pub fn new(port: i32) -> Result<Self> {
Ok(Self {
stream: Arc::new(Mutex::new(Self::connect_to_kanata(port)?)),
port,
})
}

pub fn get_stream(&self) -> Arc<Mutex<TcpStream>> {
self.stream.clone()
}

fn connect_to_kanata(port: i32) -> Result<TcpStream> {
Ok(TcpStream::connect(format!("localhost:{port}"))?)
}

fn re_establish_connection(&self) -> Result<TcpStream> {
KANATA_DISCONNECTED.store(true, Ordering::SeqCst);
log::warn!("kanata tcp server is no longer running");

let mut result = Self::connect_to_kanata(self.port);
while result.is_err() {
log::warn!("kanata tcp server is not running, retrying connection in 5 seconds");
std::thread::sleep(Duration::from_secs(5));
result = Self::connect_to_kanata(self.port);
}

log::info!("reconnected to kanata on read thread");
KANATA_DISCONNECTED.store(false, Ordering::SeqCst);
result
}

pub fn spawn_kanata_listener(&'static self) {
let stream_read = self.get_stream();
let tmpfile = TMPFILE.get().unwrap().to_owned();
log::info!("listening");

std::thread::spawn(move || -> Result<()> {
let mut reader = stream_read.lock();
let mut read_stream = reader.try_clone()?;

loop {
let mut buf = vec![0; 1024];
match read_stream.read(&mut buf) {
Ok(bytes_read) => {
let data = String::from_utf8(buf[0..bytes_read].to_vec())?;
if data == "\n" {
continue;
}

let notification: serde_json::Value = serde_json::from_str(&data)?;

if notification.dot_has("LayerChange.new") {
if let Some(new) = notification.dot_get::<String>("LayerChange.new")? {
log::info!("current layer: {new}");
if tmpfile {
let mut tmp = std::env::temp_dir();
tmp.push("kanata_layer");
std::fs::write(tmp, new)?;
}
}
}
}
Err(error) => {
// Connection reset
if error.raw_os_error().expect("could not get raw os error") == 10054 {
*reader = self.re_establish_connection()?;
read_stream = reader.try_clone()?;
}
}
}
}
});
}
}
22 changes: 22 additions & 0 deletions src/linux.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use color_eyre::eyre::Result;
use std::path::PathBuf;
use crate::Provider;

pub struct Komokana {
}

impl Provider for Komokana {
fn init() -> Result<Self>
where
Self: Sized {
todo!()
}

fn listen(self) {
todo!()
}

fn resolve_config_path(config: &str) -> Result<PathBuf> {
todo!()
}
}
Loading
Loading