relm_test/
lib.rs

1/*
2 * Copyright (c) 2018 Boucher, Antoni <bouanto@zoho.com>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy of
5 * this software and associated documentation files (the "Software"), to deal in
6 * the Software without restriction, including without limitation the rights to
7 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8 * the Software, and to permit persons to whom the Software is furnished to do so,
9 * subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in all
12 * copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 */
21
22use std::cell::RefCell;
23use std::rc::Rc;
24
25use enigo::{Enigo, KeyboardControllable, MouseButton, MouseControllable};
26use gdk::keys::Key;
27use gdk::keys::constants as key;
28use glib::{IsA, Object, object::Cast};
29use gtk::{prelude::*, Inhibit, ToolButton, Widget};
30use gtk_test::{self, focus, mouse_move, run_loop, wait_for_draw};
31use relm::StreamHandle;
32
33// TODO: should remove the signal after wait()?
34// FIXME: remove when it's in gtk-test.
35macro_rules! gtk_observer_new {
36    ($widget:expr, $signal_name:ident, |$e1:pat $(,$e:pat)*|) => {{
37        let observer = gtk_test::Observer::new();
38        let res = (*observer.get_inner()).clone();
39        $widget.$signal_name(move |$e1 $(,$e:expr)*| {
40            *res.borrow_mut() = true;
41        });
42        observer
43    }};
44    ($widget:expr, $signal_name:ident, |$e1:pat $(,$e:pat)*| $block:block) => {{
45        let observer = gtk_test::Observer::new();
46        let res = (*observer.get_inner()).clone();
47        $widget.$signal_name(move |$e1 $(,$e)*| {
48            *res.borrow_mut() = true;
49            $block
50        });
51        observer
52    }}
53}
54
55pub struct Observer<MSG> {
56    result: Rc<RefCell<Option<MSG>>>,
57}
58
59impl<MSG: Clone + 'static> Observer<MSG> {
60    pub fn new<F: Fn(&MSG) -> bool + 'static>(stream: StreamHandle<MSG>, predicate: F) -> Self {
61        let result = Rc::new(RefCell::new(None));
62        let res = result.clone();
63        stream.observe(move |msg| {
64            if predicate(msg) {
65                *res.borrow_mut() = Some(msg.clone());
66            }
67        });
68        Self {
69            result,
70        }
71    }
72
73    pub fn wait(&self) -> MSG {
74        loop {
75            if let Ok(ref result) = self.result.try_borrow() {
76                if result.is_some() {
77                    break;
78                }
79            }
80            gtk_test::run_loop();
81        }
82        self.result.borrow_mut().take()
83            .expect("Message to take")
84    }
85}
86
87#[macro_export]
88macro_rules! relm_observer_new {
89    ($component:expr, $pat:pat) => {
90        $crate::Observer::new($component.stream(), |msg|
91            if let $pat = msg {
92                true
93            }
94            else {
95                false
96            }
97        );
98    };
99}
100
101#[macro_export]
102macro_rules! relm_observer_wait {
103    (let $($variant:ident)::*($name1:ident, $name2:ident $(,$rest:ident)*) = $observer:expr) => {
104        let ($name1, $name2 $(, $rest)*) = {
105            let msg = $observer.wait();
106            if let $($variant)::*($name1, $name2 $(, $rest)*) = msg {
107                ($name1, $name2 $(, $rest)*)
108            }
109            else {
110                panic!("Wrong message type.");
111            }
112        };
113    };
114    (let $($variant:ident)::*($name:ident) = $observer:expr) => {
115        let $name = {
116            let msg = $observer.wait();
117            if let $($variant)::*($name) = msg {
118                $name
119            }
120            else {
121                panic!("Wrong message type.");
122            }
123        };
124    };
125    (let $($variant:ident)::* = $observer:expr) => {
126        let () = {
127            let msg = $observer.wait();
128            if let $($variant)::* = msg {
129                ()
130            }
131            else {
132                panic!("Wrong message type.");
133            }
134        };
135    };
136}
137
138// FIXME: remove when it's in gtk-test.
139pub fn click<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt + IsA<W>>(widget: &W) {
140    wait_for_draw(widget, || {
141        let observer =
142            if let Ok(tool_button) = widget.clone().dynamic_cast::<ToolButton>() {
143                gtk_observer_new!(tool_button, connect_clicked, |_|)
144            }
145            else {
146                gtk_observer_new!(widget, connect_button_press_event, |_, _| {
147                    Inhibit(false)
148                })
149            };
150        let allocation = widget.allocation();
151        mouse_move(widget, allocation.width() / 2, allocation.height() / 2);
152        let mut enigo = Enigo::new();
153        enigo.mouse_click(MouseButton::Left);
154        observer.wait();
155
156        wait_for_relm_events();
157    });
158}
159
160pub fn mouse_move_to<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt + IsA<W>>(widget: &W) {
161    wait_for_draw(widget, || {
162        let allocation = widget.allocation();
163        mouse_move(widget, allocation.width() / 2, allocation.height() / 2);
164
165        wait_for_relm_events();
166    });
167}
168
169pub fn double_click<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W) {
170    wait_for_draw(widget, || {
171        let observer = gtk_observer_new!(widget, connect_button_release_event, |_, _| {
172            Inhibit(false)
173        });
174        let allocation = widget.allocation();
175        mouse_move(widget, allocation.width() / 2, allocation.height() / 2);
176        let mut enigo = Enigo::new();
177        enigo.mouse_click(MouseButton::Left);
178        observer.wait();
179
180        let observer = gtk_observer_new!(widget, connect_button_release_event, |_, _| {
181            Inhibit(false)
182        });
183        enigo.mouse_click(MouseButton::Left);
184        observer.wait();
185
186        wait_for_relm_events();
187    });
188}
189
190// FIXME: don't wait the observer for modifier keys like shift?
191pub fn key_press<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W, key: Key) {
192    wait_for_draw(widget, || {
193        let observer = gtk_observer_new!(widget, connect_key_press_event, |_, _| {
194            Inhibit(false)
195        });
196        focus(widget);
197        let mut enigo = Enigo::new();
198        enigo.key_down(gdk_key_to_enigo_key(key));
199        observer.wait();
200
201        wait_for_relm_events();
202    });
203}
204
205pub fn key_release<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W, key: Key) {
206    wait_for_draw(widget, || {
207        let observer = gtk_observer_new!(widget, connect_key_release_event, |_, _| {
208            Inhibit(false)
209        });
210        focus(widget);
211        let mut enigo = Enigo::new();
212        enigo.key_up(gdk_key_to_enigo_key(key));
213        observer.wait();
214
215        wait_for_relm_events();
216    });
217}
218
219pub fn enter_key<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W, key: Key) {
220    wait_for_draw(widget, || {
221        let observer = gtk_observer_new!(widget, connect_key_release_event, |_, _| {
222            Inhibit(false)
223        });
224        focus(widget);
225        let mut enigo = Enigo::new();
226        enigo.key_click(gdk_key_to_enigo_key(key));
227        observer.wait();
228
229        wait_for_relm_events();
230    });
231}
232
233pub fn enter_keys<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W, text: &str) {
234    wait_for_draw(widget, || {
235        focus(widget);
236        let mut enigo = Enigo::new();
237        for char in text.chars() {
238            let observer = gtk_observer_new!(widget, connect_key_release_event, |_, _| {
239                Inhibit(false)
240            });
241            enigo.key_sequence(&char.to_string());
242            observer.wait();
243        }
244
245        wait_for_relm_events();
246    });
247}
248
249fn wait_for_relm_events() {
250    gtk_test::wait(1);
251    while gtk::events_pending() {
252        run_loop();
253        gtk_test::wait(1);
254    }
255}
256
257fn gdk_key_to_enigo_key(key: Key) -> enigo::Key {
258    use enigo::Key::*;
259    match key {
260        key::Return => Return,
261        key::Tab => Tab,
262        key::space => Space,
263        key::BackSpace => Backspace,
264        key::Escape => Escape,
265        key::Super_L | key::Super_R => Meta,
266        key::Control_L | key::Control_R => Control,
267        key::Shift_L | key::Shift_R => Shift,
268        key::Shift_Lock => CapsLock,
269        key::Alt_L | key::Alt_R => Alt,
270        key::Option => Option,
271        key::End => End,
272        key::Home => Home,
273        key::Page_Down => PageDown,
274        key::Page_Up => PageUp,
275        key::leftarrow => LeftArrow,
276        key::rightarrow => RightArrow,
277        key::downarrow => DownArrow,
278        key::uparrow => UpArrow,
279        key::F1 => F1,
280        key::F2 => F2,
281        key::F3 => F3,
282        key::F4 => F4,
283        key::F5 => F5,
284        key::F6 => F6,
285        key::F7 => F7,
286        key::F8 => F8,
287        key::F9 => F9,
288        key::F10 => F10,
289        key::F11 => F11,
290        key::F12 => F12,
291        _ => {
292            if let Some(char) = key.to_unicode() {
293                Layout(char)
294            }
295            else {
296                Raw(*key as u16)
297            }
298        },
299    }
300}