-
-
Save GuillaumeGomez/473b68f3d4c0922037a0c5de202bc470 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extern crate enigo; | |
extern crate gdk; | |
extern crate glib_sys; | |
extern crate gtk; | |
use enigo::{ | |
Enigo, | |
KeyboardControllable, | |
MouseButton, | |
MouseControllable, | |
}; | |
use gdk::{WindowExt, keyval_to_unicode}; | |
use gdk::enums::key::{self, Key}; | |
use gtk::{ | |
Bin, | |
BinExt, | |
Cast, | |
Container, | |
ContainerExt, | |
Continue, | |
EditableExt, | |
Entry, | |
IsA, | |
Object, | |
StaticType, | |
Widget, | |
WidgetExt, | |
Window, | |
test_widget_wait_for_draw, | |
}; | |
#[macro_export] | |
macro_rules! assert_label { | |
($widget:expr, $string:expr) => { | |
use LabelExt; | |
assert_eq!($widget.get_label().expect("get label"), $string.to_string()); | |
}; | |
} | |
#[macro_export] | |
macro_rules! assert_text { | |
($widget:expr, $string:expr) => { | |
assert_eq!($widget.get_text().expect("get text"), $string.to_string()); | |
}; | |
} | |
/// Simulate a click on a widget. | |
pub fn click<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W) { | |
wait_for_draw(widget, || { | |
let allocation = widget.get_allocation(); | |
mouse_move(widget, allocation.width / 2, allocation.height / 2); | |
let mut enigo = Enigo::new(); | |
enigo.mouse_click(MouseButton::Left); | |
run_loop(); | |
}); | |
} | |
/// Simulate a double-click on a widget. | |
pub fn double_click<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W) { | |
click(widget); | |
click(widget); | |
} | |
/// Move the mouse relative to the widget position. | |
pub fn mouse_move<W: IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W, x: i32, y: i32) { | |
wait_for_draw(widget, || { | |
let toplevel_window = widget.get_toplevel().and_then(|toplevel| toplevel.get_window()); | |
if let (Some(toplevel), Some(toplevel_window)) = (widget.get_toplevel(), toplevel_window) { | |
let (_, window_x, window_y) = toplevel_window.get_origin(); | |
if let Some((x, y)) = widget.translate_coordinates(&toplevel, x, y) { | |
let x = window_x + x; | |
let y = window_y + y; | |
let mut enigo = Enigo::new(); | |
enigo.mouse_move_to(x, y); | |
run_loop(); | |
} | |
} | |
}); | |
} | |
pub fn mouse_press<W: IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W) { | |
wait_for_draw(widget, || { | |
let allocation = widget.get_allocation(); | |
mouse_move(widget, allocation.width / 2, allocation.height / 2); | |
let mut enigo = Enigo::new(); | |
enigo.mouse_down(MouseButton::Left); | |
run_loop(); | |
}); | |
} | |
pub fn mouse_release<W: IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W) { | |
wait_for_draw(widget, || { | |
let allocation = widget.get_allocation(); | |
mouse_move(widget, allocation.width / 2, allocation.height / 2); | |
let mut enigo = Enigo::new(); | |
enigo.mouse_up(MouseButton::Left); | |
run_loop(); | |
}); | |
} | |
pub fn enter_key<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W, key: Key) { | |
wait_for_draw(widget, || { | |
focus(widget); | |
let mut enigo = Enigo::new(); | |
enigo.key_click(gdk_key_to_enigo_key(key)); | |
run_loop(); | |
}); | |
} | |
pub fn enter_keys<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W, text: &str) { | |
wait_for_draw(widget, || { | |
focus(widget); | |
let mut enigo = Enigo::new(); | |
for char in text.chars() { | |
enigo.key_sequence(&char.to_string()); | |
run_loop(); | |
} | |
}); | |
} | |
pub fn find_child_by_name<C: IsA<Widget>, W: Clone + IsA<Object> + IsA<Widget>>(parent: &W, name: &str) -> Option<C> { | |
find_widget_by_name(parent, name) | |
.and_then(|widget| widget.downcast().ok()) | |
} | |
pub fn find_widget_by_name<W: Clone + IsA<Object> + IsA<Widget>>(parent: &W, name: &str) -> Option<Widget> { | |
if let Ok(container) = parent.clone().dynamic_cast::<Container>() { | |
for child in container.get_children() { | |
if let Some(string) = child.get_name() { | |
if string == name { | |
return Some(child); | |
} | |
} | |
if let Some(widget) = find_widget_by_name(&child, name) { | |
return Some(widget); | |
} | |
} | |
} | |
else if let Ok(bin) = parent.clone().dynamic_cast::<Bin>() { | |
if let Some(child) = bin.get_child() { | |
if let Some(string) = child.get_name() { | |
if string == name { | |
return Some(child); | |
} | |
} | |
if let Some(widget) = find_widget_by_name(&child, name) { | |
return Some(widget); | |
} | |
} | |
} | |
None | |
} | |
pub fn focus<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W) { | |
wait_for_draw(widget, || { | |
if !widget.has_focus() { | |
widget.grab_focus(); | |
if let Ok(entry) = widget.clone().dynamic_cast::<Entry>() { | |
// Hack to make it work on Travis. | |
// Should use grab_focus_without_selecting() instead. | |
entry.set_position(-1); | |
} | |
} | |
}); | |
} | |
pub fn key_press<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W, key: Key) { | |
wait_for_draw(widget, || { | |
focus(widget); | |
let mut enigo = Enigo::new(); | |
enigo.key_down(gdk_key_to_enigo_key(key)); | |
run_loop(); | |
}); | |
} | |
pub fn key_release<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W, key: Key) { | |
wait_for_draw(widget, || { | |
focus(widget); | |
let mut enigo = Enigo::new(); | |
enigo.key_up(gdk_key_to_enigo_key(key)); | |
run_loop(); | |
}); | |
} | |
/// Wait for events the specified amount the milliseconds. | |
pub fn wait(ms: u32) { | |
gtk::timeout_add(ms, || { | |
gtk::main_quit(); | |
Continue(false) | |
}); | |
gtk::main(); | |
} | |
pub fn run_loop() { | |
unsafe { glib_sys::g_usleep(1000) }; | |
while gtk::events_pending() { | |
gtk::main_iteration(); | |
} | |
} | |
pub fn wait_for_draw<W: IsA<Object> + IsA<Widget> + WidgetExt, F: FnOnce()>(widget: &W, callback: F) { | |
if widget.get_ancestor(Window::static_type()).is_none() { | |
return; | |
} | |
test_widget_wait_for_draw(widget); | |
callback(); | |
} | |
fn gdk_key_to_enigo_key(key: Key) -> enigo::Key { | |
use enigo::Key::*; | |
match key { | |
key::Return => Return, | |
key::Tab => Tab, | |
key::space => Space, | |
key::BackSpace => Backspace, | |
key::Escape => Escape, | |
key::Super_L | key::Super_R => Super, | |
key::Control_L | key::Control_R => Control, | |
key::Shift_L | key::Shift_R => Shift, | |
key::Shift_Lock => CapsLock, | |
key::Alt_L | key::Alt_R => Alt, | |
key::Option => Option, | |
key::Home => Home, | |
key::Page_Down => PageDown, | |
key::Page_Up => PageUp, | |
key::leftarrow => LeftArrow, | |
key::rightarrow => RightArrow, | |
key::downarrow => DownArrow, | |
key::uparrow => UpArrow, | |
key::F1 => F1, | |
key::F2 => F2, | |
key::F3 => F3, | |
key::F4 => F4, | |
key::F5 => F5, | |
key::F6 => F6, | |
key::F7 => F7, | |
key::F8 => F8, | |
key::F9 => F9, | |
key::F10 => F10, | |
key::F11 => F11, | |
key::F12 => F12, | |
_ => { | |
if let Some(char) = keyval_to_unicode(key) { | |
Layout(char) | |
} | |
else { | |
Raw(key as u16) | |
} | |
}, | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment