Skip to main content

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::*, ToolButton, Widget};
30use glib::Propagation;
31use gtk_test::{self, focus, mouse_move, run_loop, wait_for_draw};
32use relm::StreamHandle;
33
34// TODO: should remove the signal after wait()?
35// FIXME: remove when it's in gtk-test.
36macro_rules! gtk_observer_new {
37    ($widget:expr, $signal_name:ident, |$e1:pat_param $(,$e:pat_param)*|) => {{
38        let observer = gtk_test::Observer::new();
39        let res = (*observer.get_inner()).clone();
40        $widget.$signal_name(move |$e1 $(,$e:expr)*| {
41            *res.borrow_mut() = true;
42        });
43        observer
44    }};
45    ($widget:expr, $signal_name:ident, |$e1:pat_param $(,$e:pat_param)*| $block:block) => {{
46        let observer = gtk_test::Observer::new();
47        let res = (*observer.get_inner()).clone();
48        $widget.$signal_name(move |$e1 $(,$e)*| {
49            *res.borrow_mut() = true;
50            $block
51        });
52        observer
53    }}
54}
55
56pub struct Observer<MSG> {
57    result: Rc<RefCell<Option<MSG>>>,
58}
59
60impl<MSG: Clone + 'static> Observer<MSG> {
61    pub fn new<F: Fn(&MSG) -> bool + 'static>(stream: StreamHandle<MSG>, predicate: F) -> Self {
62        let result = Rc::new(RefCell::new(None));
63        let res = result.clone();
64        stream.observe(move |msg| {
65            if predicate(msg) {
66                *res.borrow_mut() = Some(msg.clone());
67            }
68        });
69        Self {
70            result,
71        }
72    }
73
74    pub fn wait(&self) -> MSG {
75        loop {
76            if let Ok(ref result) = self.result.try_borrow() {
77                if result.is_some() {
78                    break;
79                }
80            }
81            gtk_test::run_loop();
82        }
83        self.result.borrow_mut().take()
84            .expect("Message to take")
85    }
86}
87
88#[macro_export]
89macro_rules! relm_observer_new {
90    ($component:expr, $pat:pat) => {
91        $crate::Observer::new($component.stream(), |msg|
92            if let $pat = msg {
93                true
94            }
95            else {
96                false
97            }
98        )
99    };
100}
101
102#[macro_export]
103macro_rules! relm_observer_wait {
104    (let $($variant:ident)::*($name1:ident, $name2:ident $(,$rest:ident)*) = $observer:expr) => {
105        let ($name1, $name2 $(, $rest)*) = {
106            let msg = $observer.wait();
107            if let $($variant)::*($name1, $name2 $(, $rest)*) = msg {
108                ($name1, $name2 $(, $rest)*)
109            }
110            else {
111                panic!("Wrong message type.");
112            }
113        };
114    };
115    (let $($variant:ident)::*($name:ident) = $observer:expr) => {
116        let $name = {
117            let msg = $observer.wait();
118            if let $($variant)::*($name) = msg {
119                $name
120            }
121            else {
122                panic!("Wrong message type.");
123            }
124        };
125    };
126    (let $($variant:ident)::* = $observer:expr) => {
127        let () = {
128            let msg = $observer.wait();
129            if let $($variant)::* = msg {
130                ()
131            }
132            else {
133                panic!("Wrong message type.");
134            }
135        };
136    };
137}
138
139// FIXME: remove when it's in gtk-test.
140pub fn click<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt + IsA<W>>(widget: &W) {
141    wait_for_draw(widget, || {
142        let observer =
143            if let Ok(tool_button) = widget.clone().dynamic_cast::<ToolButton>() {
144                gtk_observer_new!(tool_button, connect_clicked, |_|)
145            }
146            else {
147                gtk_observer_new!(widget, connect_button_press_event, |_, _| {
148                    Propagation::Proceed
149                })
150            };
151        let allocation = widget.allocation();
152        mouse_move(widget, allocation.width() / 2, allocation.height() / 2);
153        let mut enigo = Enigo::new();
154        enigo.mouse_click(MouseButton::Left);
155        observer.wait();
156
157        wait_for_relm_events();
158    });
159}
160
161pub fn mouse_move_to<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt + IsA<W>>(widget: &W) {
162    wait_for_draw(widget, || {
163        let allocation = widget.allocation();
164        mouse_move(widget, allocation.width() / 2, allocation.height() / 2);
165
166        wait_for_relm_events();
167    });
168}
169
170pub fn double_click<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W) {
171    wait_for_draw(widget, || {
172        let observer = gtk_observer_new!(widget, connect_button_release_event, |_, _| {
173            Propagation::Proceed
174        });
175        let allocation = widget.allocation();
176        mouse_move(widget, allocation.width() / 2, allocation.height() / 2);
177        let mut enigo = Enigo::new();
178        enigo.mouse_click(MouseButton::Left);
179        observer.wait();
180
181        let observer = gtk_observer_new!(widget, connect_button_release_event, |_, _| {
182            Propagation::Proceed
183        });
184        enigo.mouse_click(MouseButton::Left);
185        observer.wait();
186
187        wait_for_relm_events();
188    });
189}
190
191// FIXME: don't wait the observer for modifier keys like shift?
192pub fn key_press<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W, key: Key) {
193    wait_for_draw(widget, || {
194        let observer = gtk_observer_new!(widget, connect_key_press_event, |_, _| {
195            Propagation::Proceed
196        });
197        focus(widget);
198        let mut enigo = Enigo::new();
199        enigo.key_down(gdk_key_to_enigo_key(key));
200        observer.wait();
201
202        wait_for_relm_events();
203    });
204}
205
206pub fn key_release<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W, key: Key) {
207    wait_for_draw(widget, || {
208        let observer = gtk_observer_new!(widget, connect_key_release_event, |_, _| {
209            Propagation::Proceed
210        });
211        focus(widget);
212        let mut enigo = Enigo::new();
213        enigo.key_up(gdk_key_to_enigo_key(key));
214        observer.wait();
215
216        wait_for_relm_events();
217    });
218}
219
220pub fn enter_key<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W, key: Key) {
221    wait_for_draw(widget, || {
222        let observer = gtk_observer_new!(widget, connect_key_release_event, |_, _| {
223            Propagation::Proceed
224        });
225        focus(widget);
226        let mut enigo = Enigo::new();
227        enigo.key_click(gdk_key_to_enigo_key(key));
228        observer.wait();
229
230        wait_for_relm_events();
231    });
232}
233
234pub fn enter_keys<W: Clone + IsA<Object> + IsA<Widget> + WidgetExt>(widget: &W, text: &str) {
235    wait_for_draw(widget, || {
236        focus(widget);
237        let mut enigo = Enigo::new();
238        for char in text.chars() {
239            let observer = gtk_observer_new!(widget, connect_key_release_event, |_, _| {
240                Propagation::Proceed
241            });
242            enigo.key_sequence(&char.to_string());
243            observer.wait();
244        }
245
246        wait_for_relm_events();
247    });
248}
249
250fn wait_for_relm_events() {
251    gtk_test::wait(1);
252    while gtk::events_pending() {
253        run_loop();
254        gtk_test::wait(1);
255    }
256}
257
258fn gdk_key_to_enigo_key(key: Key) -> enigo::Key {
259    use enigo::Key::*;
260    match key {
261        key::Return => Return,
262        key::Tab => Tab,
263        key::space => Space,
264        key::BackSpace => Backspace,
265        key::Escape => Escape,
266        key::Super_L | key::Super_R => Meta,
267        key::Control_L | key::Control_R => Control,
268        key::Shift_L | key::Shift_R => Shift,
269        key::Shift_Lock => CapsLock,
270        key::Alt_L | key::Alt_R => Alt,
271        key::Option => Option,
272        key::End => End,
273        key::Home => Home,
274        key::Page_Down => PageDown,
275        key::Page_Up => PageUp,
276        key::leftarrow => LeftArrow,
277        key::rightarrow => RightArrow,
278        key::downarrow => DownArrow,
279        key::uparrow => UpArrow,
280        key::F1 => F1,
281        key::F2 => F2,
282        key::F3 => F3,
283        key::F4 => F4,
284        key::F5 => F5,
285        key::F6 => F6,
286        key::F7 => F7,
287        key::F8 => F8,
288        key::F9 => F9,
289        key::F10 => F10,
290        key::F11 => F11,
291        key::F12 => F12,
292        _ => {
293            if let Some(char) = key.to_unicode() {
294                Layout(char)
295            }
296            else {
297                Raw(*key as u16)
298            }
299        },
300    }
301}