Skip to content

Commit

Permalink
Add support volume level tracking for MacOS (#6)
Browse files Browse the repository at this point in the history
Co-authored-by: vadimsuhanov <vadim.sukhanov@tele2.ru>
  • Loading branch information
suhanovv and vadimsuhanov authored Oct 1, 2024
1 parent 93d08a6 commit 3f2a500
Show file tree
Hide file tree
Showing 8 changed files with 525 additions and 96 deletions.
268 changes: 216 additions & 52 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ mpris = "2.0.1"
core-foundation = "0.10"
core-foundation-sys = "0.8.7"
libc = "0.2"
objc2 = {version = "0.5.2", features = ["default"]}
objc2-foundation = { version = "0.2.2", features = ["all"] }
block2 = "0.5.1"
coreaudio-sys = "0.2.16"
coreaudio-rs = "0.12.1"

[target.'cfg(target_os = "windows")'.dependencies]
[dependencies.windows]
Expand Down
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Application is written in Rust which gives easy access to HID libraries, low-lev
| | Windows | Linux | macos |
| ------------ | ------------------ | ------------------------------- |--------------------|
| Time | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Volume | :heavy_check_mark: | :heavy_check_mark: (PulseAudio) | |
| Volume | :heavy_check_mark: | :heavy_check_mark: (PulseAudio) | :heavy_check_mark: |
| Input layout | :heavy_check_mark: | :heavy_check_mark: (X11) | :heavy_check_mark: |
| Media info | :heavy_check_mark: | :heavy_check_mark: (D-Bus) | |

Expand Down Expand Up @@ -58,7 +58,24 @@ When you verified that the application works with your keyboard, you can use `qm
3. Start `qmk-hid-host`, add it to autorun if needed

### MacOS
1. Start `qmk-hid-host`, add it to autorun if needed
1. Download `qmk-hid-host`
2. Modify `qmk-hid-host.json`
3. Add your layouts, for example:
```
"layouts": [
"ABC", "Russian"
],
```
if you don't know what layout are installed in you system, run qmk-hid-host with the layouts listed above, change lang and look at terminal output:
```
INFO qmk_hid_host::providers::layout::macos: new layout: 'ABC', layout list: ["ABC", "Russian"]
INFO qmk_hid_host::providers::layout::macos: new layout: 'Russian', layout list: ["ABC", "Russian"]
```
"new layout:" is what you need
4. start `qmk-hid-host` from directory where your `qmk-hid-host.json` is located
5. If you `qmk-hid-host` stuck at `Waiting for keyboard...` there are two common mistakes:
1. You're wrong with productId in your config
2. Close Vial app and try again

## Development

Expand Down
3 changes: 2 additions & 1 deletion src/data_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ pub enum DataType {
#[cfg(target_os = "macos")]
pub enum DataType {
Time = 0xAA, // random value that does not conflict with VIA/VIAL, must match firmware
Layout = 0xAC,
Volume,
Layout,
}
64 changes: 46 additions & 18 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,51 @@ mod data_type;
mod keyboard;
mod providers;

use std::thread;
use tokio::sync::{broadcast, mpsc};
use config::get_config;
use keyboard::Keyboard;
#[cfg(not(target_os = "macos"))]
use providers::{_base::Provider, layout::LayoutProvider, time::TimeProvider, media::MediaProvider, volume::VolumeProvider};
use providers::{_base::Provider, layout::LayoutProvider, time::TimeProvider, media::MediaProvider, volume::VolumeProvider};


#[cfg(target_os = "macos")]
use providers::{_base::Provider, layout::LayoutProvider, time::TimeProvider};
use {
providers::{_base::Provider, layout::LayoutProvider, time::TimeProvider, volume::VolumeProvider},
core_foundation_sys::runloop::CFRunLoopRun,
};

fn main() {
let env_filter = tracing_subscriber::EnvFilter::builder()
.with_default_directive(tracing::level_filters::LevelFilter::INFO.into())
.from_env_lossy();
let tracing_subscriber = tracing_subscriber::fmt().with_env_filter(env_filter).finish();
let _ = tracing::subscriber::set_global_default(tracing_subscriber);
#[cfg(target_os = "macos")]
fn run(layouts: Vec<String>, data_sender: mpsc::Sender<Vec<u8>>, connected_sender: broadcast::Sender<bool>) {
let mut is_connected = false;
let mut connected_receiver = connected_sender.subscribe();

let config = get_config();
thread::spawn(move || {
let providers: Vec<Box<dyn Provider>> = vec![
TimeProvider::new(data_sender.clone(), connected_sender.clone()),
LayoutProvider::new(data_sender.clone(), connected_sender.clone(), layouts),
VolumeProvider::new(data_sender.clone(), connected_sender.clone()),
];

let keyboard = Keyboard::new(config.device, config.reconnect_delay);
let (connected_sender, data_sender) = keyboard.connect();
loop {
if let Ok(connected) = connected_receiver.blocking_recv() {
if !is_connected && connected {
providers.iter().for_each(|p| p.start());
}

#[cfg(target_os = "macos")]
let providers: Vec<Box<dyn Provider>> = vec![
TimeProvider::new(data_sender.clone(), connected_sender.clone()),
LayoutProvider::new(data_sender.clone(), connected_sender.clone(), config.layouts),
];
is_connected = connected;
}
}
});
unsafe { CFRunLoopRun(); }
}

#[cfg(not(target_os = "macos"))]
#[cfg(not(target_os = "macos"))]
fn run(layouts: Vec<String>, data_sender: mpsc::Sender<Vec<u8>>, connected_sender: broadcast::Sender<bool>) {
let providers: Vec<Box<dyn Provider>> = vec![
TimeProvider::new(data_sender.clone(), connected_sender.clone()),
VolumeProvider::new(data_sender.clone(), connected_sender.clone()),
LayoutProvider::new(data_sender.clone(), connected_sender.clone(), config.layouts),
LayoutProvider::new(data_sender.clone(), connected_sender.clone(), layouts),
MediaProvider::new(data_sender.clone(), connected_sender.clone()),
];

Expand All @@ -55,3 +69,17 @@ fn main() {
}
}
}

fn main() {
let env_filter = tracing_subscriber::EnvFilter::builder()
.with_default_directive(tracing::level_filters::LevelFilter::INFO.into())
.from_env_lossy();
let tracing_subscriber = tracing_subscriber::fmt().with_env_filter(env_filter).finish();
let _ = tracing::subscriber::set_global_default(tracing_subscriber);
let config = get_config();

let keyboard = Keyboard::new(config.device, config.reconnect_delay);
let (connected_sender, data_sender) = keyboard.connect();

run(config.layouts, data_sender, connected_sender);
}
32 changes: 9 additions & 23 deletions src/providers/layout/macos.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
use crate::data_type::DataType;
use core_foundation::base::{CFRelease, TCFType};
use core_foundation::string::{CFString, CFStringRef};
use core_foundation_sys::runloop::{kCFRunLoopDefaultMode, CFRunLoopRunInMode};
use libc::c_void;
use std::sync::{Arc, Mutex};
use core_foundation_sys::base::Boolean;
use core_foundation_sys::date::CFTimeInterval;
use tokio::sync::{broadcast, mpsc};

use super::super::_base::Provider;

#[link(name = "Carbon", kind = "framework")]
extern "C" {
fn TISCopyCurrentKeyboardLayoutInputSource() -> *mut c_void;
fn TISGetInputSourceProperty(input_source: *mut c_void, key: CFStringRef) -> *mut CFStringRef;
}

fn get_keyboard_layout() -> Option<String> {
unsafe {

Expand Down Expand Up @@ -77,33 +73,23 @@ impl Provider for LayoutProvider {
let connected_sender = self.connected_sender.clone();
let mut synced_layout = "".to_string();

let is_connected = Arc::new(Mutex::new(true));
let is_connected_ref = is_connected.clone();
std::thread::spawn(move || {
let mut connected_receiver = connected_sender.subscribe();
loop {
if !connected_receiver.try_recv().unwrap_or(true) {
let mut is_connected = is_connected_ref.lock().unwrap();
*is_connected = false;
break;
}

if let Some(layout) = get_keyboard_layout() {
let lang = layout.split('.').last().unwrap().to_string();
if synced_layout != lang {
synced_layout = lang;
send_data(&synced_layout, &layouts, &data_sender);
}
}
std::thread::sleep(std::time::Duration::from_millis(100));
}}
);
loop {
if !*(is_connected.lock().unwrap()) {
break;
}
if let Some(layout) = get_keyboard_layout() {
let lang = layout.split('.').last().unwrap().to_string();
if synced_layout != lang {
synced_layout = lang;
send_data(&synced_layout, &layouts, &data_sender);
}
}
unsafe {CFRunLoopRunInMode(kCFRunLoopDefaultMode, CFTimeInterval::from(1), Boolean::from(true));}
}

tracing::info!("Layout Provider stopped");
}
}
6 changes: 6 additions & 0 deletions src/providers/volume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ mod windows;

#[cfg(target_os = "windows")]
pub use self::windows::VolumeProvider;

#[cfg(target_os = "macos")]
mod macos;

#[cfg(target_os = "macos")]
pub use self::macos::VolumeProvider;
Loading

0 comments on commit 3f2a500

Please sign in to comment.