Skip to content
Draft
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 src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ pub enum SetConfigFields {
ToDefault,
ToggleHotkey(String),
ClipboardHotkey(String),
HyperkeyHotkey(String),
SetPosition(Position),
PlaceHolder(String),
SearchUrl(String),
Expand Down
6 changes: 3 additions & 3 deletions src/app/menubar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ fn get_image() -> DynamicImage {

fn init_event_handler(sender: ExtSender, shortcut: Shortcut) {
let runtime = Runtime::new().unwrap();
let shortcut = shortcut.clone();
let shortcut = shortcut;

MenuEvent::set_event_handler(Some(move |x: MenuEvent| {
let shortcut = shortcut.clone();
let shortcut = shortcut;
let sender = sender.clone();
let sender = sender.0.clone();
info!("Menubar event called: {}", x.id.0);
Expand All @@ -113,7 +113,7 @@ fn init_event_handler(sender: ExtSender, shortcut: Shortcut) {
runtime.spawn(async move {
sender
.clone()
.try_send(Message::KeyPressed(shortcut.clone()))
.try_send(Message::KeyPressed(shortcut))
.unwrap();
});
}
Expand Down
24 changes: 24 additions & 0 deletions src/app/pages/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,29 @@ fn general_tab(config: Box<Config>, theme: crate::config::Theme) -> Column<'stat
theme.clone(),
);

let theme_clone = theme.clone();
let hyperkey = settings_row_with_reset(
settings_item_row([
settings_hint_text(
theme.clone(),
"Hyperkey hotkey",
Some("Simulate CMD+SHIFT+CTRL+ALT with another hotkey"),
),
Space::new().width(Length::Fill).into(),
text_input(
"Hyperkey Hotkey",
&config.hyperkey_hotkey.clone().unwrap_or("".to_string()),
)
.on_input(|input| Message::SetConfig(SetConfigFields::HyperkeyHotkey(input.clone())))
.on_submit(Message::WriteConfig)
.width(Length::Fixed(SETTINGS_INPUT_WIDTH))
.style(move |_, _| settings_text_input_item_style(&theme_clone))
.into(),
]),
ResetField::ToggleHotkey,
theme.clone(),
);

let theme_clone = theme.clone();
let placeholder_setting = settings_row_with_reset(
settings_item_row([
Expand Down Expand Up @@ -412,6 +435,7 @@ fn general_tab(config: Box<Config>, theme: crate::config::Theme) -> Column<'stat
Column::from_iter([
hotkey,
cb_hotkey,
hyperkey,
placeholder_setting,
search,
debounce,
Expand Down
6 changes: 5 additions & 1 deletion src/app/tile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,16 @@ pub struct Hotkeys {
pub handle: Option<EventTapHandle>,
pub toggle: Shortcut,
pub clipboard_hotkey: Shortcut,
pub hyperkey: Option<Shortcut>,
pub shells: HashMap<Shortcut, Shelly>,
}

impl Hotkeys {
pub fn all_hotkeys(&self) -> Vec<Shortcut> {
let mut a = vec![self.toggle.clone(), self.clipboard_hotkey.clone()];
let mut a = vec![self.toggle, self.clipboard_hotkey];
if let Some(hyperkey_shortcut) = self.hyperkey {
a.push(hyperkey_shortcut);
}
a.extend(
self.shells
.keys()
Expand Down
23 changes: 23 additions & 0 deletions src/app/tile/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ use crate::debounce::DebouncePolicy;
use crate::platform::macos::events::Event;
use crate::platform::macos::launching::Shortcut;
use crate::platform::macos::launching::global_handler;
use crate::platform::macos::send_hyperkey_event;
use crate::platform::macos::screen_with_mouse;
use crate::platform::macos::{start_at_login, stop_at_login};
use crate::quit::get_open_apps;
Expand Down Expand Up @@ -380,6 +381,16 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task<Message> {
tile.hotkeys.clipboard_hotkey = hotkey
}

if let Some(hyperkey) = &new_config
.hyperkey_hotkey
.as_ref()
.and_then(|x| Shortcut::parse(x).ok())
{
tile.hotkeys.hyperkey = Some(*hyperkey)
} else {
tile.hotkeys.hyperkey = None
}

if let Ok(hotkey) = Shortcut::parse(&new_config.toggle_hotkey) {
tile.hotkeys.toggle = hotkey
}
Expand Down Expand Up @@ -433,9 +444,15 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task<Message> {
)));
}

let is_hyperkey_hotkey = Some(shortcut) == tile.hotkeys.hyperkey;
let is_clipboard_hotkey = shortcut == tile.hotkeys.clipboard_hotkey;
let is_open_hotkey = shortcut == tile.hotkeys.toggle;

if is_hyperkey_hotkey {
send_hyperkey_event();
return Task::none();
}

let clipboard_page_task = if is_clipboard_hotkey {
info!("Switching to clipboard page");
Task::done(Message::SwitchToPage(Page::ClipboardHistory))
Expand Down Expand Up @@ -832,6 +849,11 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task<Message> {
match config.clone() {
SetConfigFields::ToggleHotkey(hk) => final_config.toggle_hotkey = hk,
SetConfigFields::ClipboardHotkey(hk) => final_config.clipboard_hotkey = hk,
SetConfigFields::HyperkeyHotkey(key) => {
if !key.trim().is_empty() {
final_config.hyperkey_hotkey = Some(key);
}
}
SetConfigFields::ClipboardHistory(cbhist) => final_config.cbhist = cbhist,
SetConfigFields::Modes(Editable::Create((key, value))) => {
final_config.modes.insert(key, value);
Expand Down Expand Up @@ -1438,6 +1460,7 @@ mod tests {
hotkeys: Hotkeys {
toggle: Shortcut::parse("alt+space").unwrap(),
clipboard_hotkey: Shortcut::parse("cmd+shift+c").unwrap(),
hyperkey: None,
shells: HashMap::new(),
handle: None,
},
Expand Down
2 changes: 2 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ impl std::fmt::Display for Position {
pub struct Config {
pub toggle_hotkey: String,
pub clipboard_hotkey: String,
pub hyperkey_hotkey: Option<String>,
pub buffer_rules: Buffer,
pub event_duration: u32,
pub main_page: MainPage,
Expand Down Expand Up @@ -122,6 +123,7 @@ impl Default for Config {
Self {
toggle_hotkey: "ALT+SPACE".to_string(),
clipboard_hotkey: "SUPER+SHIFT+C".to_string(),
hyperkey_hotkey: None,
buffer_rules: Buffer::default(),
theme: Theme::default(),
start_at_login: true,
Expand Down
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ fn main() -> iced::Result {
toggle: show_hide,
clipboard_hotkey: cbhist,
shells: shell_map,
hyperkey: config
.hyperkey_hotkey
.as_ref()
.and_then(|x| Shortcut::parse(x).ok()),
handle: None,
};

Expand Down
2 changes: 1 addition & 1 deletion src/platform/macos/launching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ pub fn global_handler(sender: ExtSender, targets: Vec<Shortcut>) -> Result<Event
})
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq, Copy, Eq, Hash)]
pub struct Shortcut {
pub key_code: Option<u16>,
pub mods: Option<usize>,
Expand Down
29 changes: 29 additions & 0 deletions src/platform/macos/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,35 @@ pub fn simulate_paste(pid: libc::pid_t) {
}
}

pub fn send_hyperkey_event() {
use objc2_core_graphics::{
CGEvent, CGEventFlags, CGEventSource, CGEventSourceStateID, CGEventTapLocation,
};

let source = CGEventSource::new(CGEventSourceStateID::HIDSystemState);
let source_ref = source.as_deref();

// Use a keycode that won't interfere - 0xFF or a null keycode
// Alternatively use a specific key like F18 (0x4F) if you want a real key
let keycode: u16 = 0; // kVK_ANSI_A as placeholder, or use your target key

let hyper_flags = CGEventFlags::MaskCommand
| CGEventFlags::MaskAlternate // OPT
| CGEventFlags::MaskShift
| CGEventFlags::MaskControl;

// Key down
if let Some(keydown) = CGEvent::new_keyboard_event(source_ref, keycode, true) {
CGEvent::set_flags(Some(&keydown), hyper_flags);
CGEvent::post(CGEventTapLocation::HIDEventTap, Some(&keydown));
}

// Key up
if let Some(keyup) = CGEvent::new_keyboard_event(source_ref, keycode, false) {
CGEvent::set_flags(Some(&keyup), hyper_flags);
CGEvent::post(CGEventTapLocation::HIDEventTap, Some(&keyup));
}
}
/// This is the function that transforms the process to a UI element, and hides the dock icon
///
/// see mostly <https://github.com/electron/electron/blob/e181fd040f72becd135db1fa977622b81da21643/shell/browser/browser_mac.mm#L512C1-L532C2>
Expand Down
Loading