diff --git a/Cargo.toml b/Cargo.toml index 4df2301..608bb2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "grust" -version = "0.1.3" +version = "0.2.0-dev" authors = ["Mikhail Zabaluev "] license = "LGPL-2.1+" readme = "README.md" @@ -15,6 +15,6 @@ generated crates. repository = "https://github.com/gi-rust/grust.git" [dependencies] -glib-2_0-sys = "0.1.0" +glib-2_0-sys = "0.1.1" gobject-2_0-sys = "0.1.0" libc = "0.1" diff --git a/src/boxed.rs b/src/boxed.rs index 217e67f..7f7f10e 100644 --- a/src/boxed.rs +++ b/src/boxed.rs @@ -18,6 +18,7 @@ use gtype::GType; use types::gpointer; +use util::{box_free, box_from_pointer, box_into_pointer}; use gobject as ffi; @@ -35,37 +36,36 @@ pub fn type_of() -> GType where T: BoxedType ::get_type() } -unsafe fn box_from_raw(ptr: gpointer) -> Box { - mem::transmute(ptr) -} - -unsafe fn box_into_raw(b: Box) -> gpointer { - mem::transmute(b) -} - -extern "C" fn box_copy(raw: gpointer) -> gpointer +unsafe extern "C" fn box_copy(raw: gpointer) -> gpointer where T: Clone { - let boxed: Box = unsafe { box_from_raw(raw) }; + let boxed: Box = box_from_pointer(raw); let copy: Box = boxed.clone(); - unsafe { - // Prevent the original value from being dropped - box_into_raw(boxed); - box_into_raw(copy) as gpointer - } + // Prevent the original value from being dropped + mem::forget(boxed); + box_into_pointer(copy) } -extern "C" fn box_free(raw: gpointer) { - let boxed: Box = unsafe { box_from_raw(raw) }; - mem::drop(boxed); +unsafe fn into_boxed_copy_func(callback: unsafe extern "C" fn(gpointer) -> gpointer) + -> ffi::GBoxedCopyFunc +{ + mem::transmute(callback) } -pub fn register_box_type(name: &str) -> GType where T: Clone + Send { +unsafe fn into_boxed_free_func(callback: unsafe extern "C" fn(gpointer)) + -> ffi::GBoxedFreeFunc +{ + mem::transmute(callback) +} + +pub fn register_box_type(name: &str) -> GType + where T: Clone + Send + 'static +{ let c_name = CString::new(name).unwrap(); let raw = unsafe { ffi::g_boxed_type_register_static(c_name.as_ptr(), - box_copy::, - box_free::) + into_boxed_copy_func(box_copy::), + into_boxed_free_func(box_free::)) }; assert!(raw != 0, "failed to register type \"{}\"", name); unsafe { GType::from_raw(raw) } @@ -82,10 +82,10 @@ impl BoxedType for Box where T: BoxRegistered { } unsafe fn from_ptr(raw: gpointer) -> Box { - box_from_raw(raw) + box_from_pointer(raw) } unsafe fn into_ptr(self) -> gpointer { - box_into_raw(self) as gpointer + box_into_pointer(self) } } diff --git a/src/mainloop.rs b/src/mainloop.rs index d4f9327..9888ad3 100644 --- a/src/mainloop.rs +++ b/src/mainloop.rs @@ -1,6 +1,6 @@ // This file is part of Grust, GObject introspection bindings for Rust // -// Copyright (C) 2014 Mikhail Zabaluev +// Copyright (C) 2014, 2015 Mikhail Zabaluev // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public @@ -17,12 +17,103 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA use refcount::{Refcount, Ref}; -use types::FALSE; +use types::{FALSE, TRUE}; +use types::{gboolean, gint, gpointer, guint}; +use util::{box_free, box_from_pointer, box_into_pointer, into_destroy_notify}; use wrap; use wrap::Wrapper; use glib as ffi; use gobject; +use std::convert; +use std::marker; +use std::mem; + +pub const PRIORITY_DEFAULT : gint = ffi::G_PRIORITY_DEFAULT; +pub const PRIORITY_DEFAULT_IDLE : gint = ffi::G_PRIORITY_DEFAULT_IDLE; +pub const PRIORITY_HIGH : gint = ffi::G_PRIORITY_HIGH; +pub const PRIORITY_HIGH_IDLE : gint = ffi::G_PRIORITY_HIGH_IDLE; +pub const PRIORITY_LOW : gint = ffi::G_PRIORITY_LOW; + +pub enum CallbackResult { Remove, Continue } +pub use self::CallbackResult::*; + +pub struct RawCallback { + func: ffi::GSourceFunc, + data: gpointer, + destroy: ffi::GDestroyNotify +} + +impl Drop for RawCallback { + fn drop(&mut self) { + (self.destroy)(self.data); + } +} + +unsafe fn into_source_func(func: unsafe extern "C" fn(gpointer) -> gboolean) + -> ffi::GSourceFunc +{ + mem::transmute(func) +} + +unsafe extern "C" fn source_func(callback_data: gpointer) -> gboolean + where F: FnMut() -> CallbackResult +{ + let mut callback: Box = box_from_pointer(callback_data); + let res = callback(); + mem::forget(callback); + match res { + Remove => FALSE, + Continue => TRUE + } +} + +unsafe extern "C" fn source_once_func(callback_data: gpointer) -> gboolean + where F: FnOnce() +{ + let mut holder: Box> = box_from_pointer(callback_data); + let callback = holder.take().expect("a callback closure expected"); + mem::forget(holder); + callback(); + FALSE +} + +pub struct SourceCallback(RawCallback); + +impl Into for SourceCallback { + #[inline] + fn into(self) -> RawCallback { + self.0 + } +} + +impl SourceCallback { + pub fn new(closure: F) -> Self + where F: Send + 'static, F: FnMut() -> CallbackResult + { + let boxed_closure = Box::new(closure); + SourceCallback(unsafe { + RawCallback { + func: into_source_func(source_func::), + data: box_into_pointer(boxed_closure), + destroy: into_destroy_notify(box_free::) + } + }) + } + + pub fn once(closure: F) -> Self + where F: Send + 'static, F: FnOnce() + { + let holder = Box::new(Some(closure)); + SourceCallback(unsafe { + RawCallback { + func: into_source_func(source_once_func::), + data: box_into_pointer(holder), + destroy: into_destroy_notify(box_free::>) + } + }) + } +} #[repr(C)] pub struct MainContext { @@ -41,6 +132,19 @@ impl MainContext { wrap::from_raw(ffi::g_main_context_default()) } } + + pub fn invoke(&self, callback: SourceCallback) { + self.invoke_full(PRIORITY_DEFAULT, callback) + } + + pub fn invoke_full(&self, priority: gint, callback: SourceCallback) { + let raw: RawCallback = callback.into(); + unsafe { + ffi::g_main_context_invoke_full(self.as_mut_ptr(), + priority, raw.func, raw.data, Some(raw.destroy)); + } + mem::forget(raw); + } } impl Refcount for MainContext { @@ -56,6 +160,111 @@ impl Refcount for MainContext { g_impl_boxed_type_for_ref!(MainContext, gobject::g_main_context_get_type); +#[repr(C)] +pub struct Source { + raw: ffi::GSource, + phantom_data: marker::PhantomData +} + +#[repr(C)] +pub struct AttachedSource { + raw: ffi::GSource, + phantom_data: marker::PhantomData +} + +unsafe impl Send for Source where C: Into { } + +unsafe impl Send for AttachedSource where C: Into { } +unsafe impl Sync for AttachedSource where C: Into { } + +macro_rules! common_source_impls { + ($name:ident) => { + unsafe impl Wrapper for $name { + type Raw = ffi::GSource; + } + + impl Refcount for $name { + unsafe fn inc_ref(&self) { + ffi::g_source_ref(self.as_mut_ptr()); + } + unsafe fn dec_ref(&self) { + ffi::g_source_unref(self.as_mut_ptr()); + } + } + } +} + +common_source_impls!(Source); +common_source_impls!(AttachedSource); + +impl Source where C: Into { + pub fn set_callback(&self, callback: C) + { + let raw: RawCallback = callback.into(); + unsafe { + ffi::g_source_set_callback(self.as_mut_ptr(), + raw.func, raw.data, Some(raw.destroy)); + } + mem::forget(raw); + } + + pub fn set_priority(&self, priority: gint) { + unsafe { + ffi::g_source_set_priority(self.as_mut_ptr(), priority); + } + } +} + +impl Ref> { + pub fn attach(self, ctx: &MainContext) -> Ref> { + unsafe { + let source_ptr = self.as_mut_ptr(); + ffi::g_source_attach(source_ptr, ctx.as_mut_ptr()); + mem::forget(self); + Ref::from_raw(source_ptr) + } + } +} + +impl AttachedSource { + #[inline] + pub fn as_source(&self) -> &Source { + unsafe { wrap::from_raw(self.as_ptr()) } + } + + pub fn destroy(&self) { + unsafe { ffi::g_source_destroy(self.as_mut_ptr()) } + } +} + +impl convert::AsRef> for AttachedSource { + #[inline] + fn as_ref(&self) -> &Source { + self.as_source() + } +} + +pub fn idle_source_new() -> Ref { + unsafe { + let source = ffi::g_idle_source_new(); + Ref::from_raw(source) + } +} + +pub fn timeout_source_new(interval: guint) -> Ref { + unsafe { + let source = ffi::g_timeout_source_new(interval); + Ref::from_raw(source) + } +} + +pub fn timeout_source_new_seconds(interval: guint) -> Ref { + unsafe { + let source = ffi::g_timeout_source_new_seconds(interval); + Ref::from_raw(source) + } +} + #[repr(C)] pub struct MainLoop { raw: ffi::GMainLoop diff --git a/src/util.rs b/src/util.rs index c2cea71..08c0961 100644 --- a/src/util.rs +++ b/src/util.rs @@ -16,11 +16,14 @@ // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -use types::{gboolean,FALSE}; +use types::{gboolean, gpointer, FALSE}; + +use glib; use std::ascii; use std::ascii::AsciiExt; use std::borrow::Cow; +use std::mem; use std::str; #[inline] @@ -39,3 +42,22 @@ pub fn escape_bytestring<'a>(s: &'a [u8]) -> Cow<'a, str> { let string = unsafe { String::from_utf8_unchecked(acc) }; string.into() } + +pub unsafe extern "C" fn box_free(raw: gpointer) { + let b: Box = mem::transmute(raw); + mem::drop(b); +} + +pub unsafe fn into_destroy_notify(func: unsafe extern "C" fn(gpointer)) + -> glib::GDestroyNotify +{ + mem::transmute(func) +} + +pub unsafe fn box_from_pointer(p: gpointer) -> Box { + mem::transmute(p) +} + +pub fn box_into_pointer(b: Box) -> gpointer { + unsafe { mem::transmute(b) } +} diff --git a/tests/test-mainloop.rs b/tests/test-mainloop.rs new file mode 100644 index 0000000..8d55237 --- /dev/null +++ b/tests/test-mainloop.rs @@ -0,0 +1,167 @@ +// This file is part of Grust, GObject introspection bindings for Rust +// +// Copyright (C) 2015 Mikhail Zabaluev +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +extern crate grust; + +use grust::mainloop; +use grust::mainloop::{LoopRunner, Source, SourceCallback}; +use grust::mainloop::CallbackResult::{Continue, Remove}; + +use std::sync::mpsc; +use std::thread; + +#[test] +fn test_invoke_once() { + let runner = LoopRunner::new(); + runner.run_after(|mainloop| { + const THREAD_NAME: &'static str = "invoker"; + thread::Builder::new().name(THREAD_NAME.to_string()).spawn(move || { + let mlc = mainloop.clone(); + let ctx = mainloop.get_context(); + ctx.invoke(SourceCallback::once(move || { + assert!(thread::current().name() != Some(THREAD_NAME)); + mlc.quit(); + })); + }).unwrap(); + }); +} + +#[test] +fn test_invoke() { + let runner = LoopRunner::new(); + runner.run_after(|mainloop| { + const THREAD_NAME: &'static str = "invoker"; + thread::Builder::new().name(THREAD_NAME.to_string()).spawn(move || { + let mlc = mainloop.clone(); + let mut count = 0; + let ctx = mainloop.get_context(); + ctx.invoke(SourceCallback::new(move || { + assert!(thread::current().name() != Some(THREAD_NAME)); + count += 1; + if count < 2 { + Continue + } else { + mlc.quit(); + Remove + } + })); + }).unwrap(); + }); +} + +#[test] +fn test_idle_source() { + let runner = LoopRunner::new(); + runner.run_after(|ml| { + let source = mainloop::idle_source_new(); + let mlc = ml.clone(); + let mut count = 0; + source.set_callback(SourceCallback::new(move || { + assert!(count <= 2); + count += 1; + if count < 2 { + Continue + } else { + mlc.quit(); + Remove + } + })); + source.attach(ml.get_context()); + }); +} + +#[test] +fn test_one_time_callback() { + let runner = LoopRunner::new(); + runner.run_after(|ml| { + let source = mainloop::idle_source_new(); + let mlc = ml.clone(); + source.set_callback(SourceCallback::once(move || { + mlc.quit(); + })); + source.attach(ml.get_context()); + }); +} + +#[test] +fn test_timeout_source() { + let runner = LoopRunner::new(); + runner.run_after(|ml| { + let source = mainloop::timeout_source_new(10); + let mlc = ml.clone(); + source.set_callback(SourceCallback::once(move || { + mlc.quit(); + })); + source.attach(ml.get_context()); + }); +} + +#[test] +fn test_priority() { + let (tx, rx) = mpsc::channel(); + let runner = LoopRunner::new(); + runner.run_after(|ml| { + let source1 = mainloop::idle_source_new(); + source1.set_priority(mainloop::PRIORITY_DEFAULT); + let mut count = 0; + source1.set_callback(SourceCallback::new(move || { + tx.send(()).unwrap(); + count += 1; + if count == 1 { + Remove + } else { + Continue + } + })); + let source2 = mainloop::idle_source_new(); + let mlc = ml.clone(); + source2.set_callback(SourceCallback::once(move || { + mlc.quit(); + })); + let ctx = ml.get_context(); + source1.attach(ctx); + source2.attach(ctx); + }); + assert_eq!(rx.iter().count(), 1); +} + +#[test] +fn test_attached_source() { + let (tx, rx) = mpsc::channel(); + let runner = LoopRunner::new(); + runner.run_after(|ml| { + let ctx = ml.get_context(); + let source1 = mainloop::idle_source_new(); + let attached = source1.attach(ctx); + let attached_source: &Source = attached.as_ref(); + attached_source.set_priority(mainloop::PRIORITY_DEFAULT); + let atc = attached.clone(); + attached.as_source().set_callback(SourceCallback::new(move || { + tx.send(()).unwrap(); + atc.destroy(); + Continue + })); + let mlc = ml.clone(); + let source2 = mainloop::idle_source_new(); + source2.set_callback(SourceCallback::once(move || { + mlc.quit(); + })); + source2.attach(ctx); + }); + assert_eq!(rx.iter().count(), 1); +}