From e2e54e545aa02768871f6f9877250cfc85d4d88b Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Sun, 5 Apr 2015 19:03:48 +0300 Subject: [PATCH 01/12] Bump the version to 0.2.0-dev The master branch follows the nightly Rust builds. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5b3d70d..09d9720 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "grust" -version = "0.0.11-dev" +version = "0.2.0-dev" authors = ["Mikhail Zabaluev "] license = "LGPL-2.1+" readme = "README.md" From 57b8b0df6ed529ada72a678ee6fba9c5233e7d53 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Mon, 1 Jun 2015 00:47:58 +0300 Subject: [PATCH 02/12] boxed: update to recent nightly Rust --- src/boxed.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/boxed.rs b/src/boxed.rs index b7f6cc0..ffce443 100644 --- a/src/boxed.rs +++ b/src/boxed.rs @@ -41,11 +41,9 @@ extern "C" fn box_copy(raw: gpointer) -> gpointer { let boxed: Box = unsafe { Box::from_raw(raw as *mut T) }; 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 + box_into_raw(boxed); + box_into_raw(copy) as gpointer } extern "C" fn box_free(raw: gpointer) { From dad22d2b7ca1bc0ba9eceb75ca0487464d19e886 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Wed, 3 Jun 2015 20:43:34 +0300 Subject: [PATCH 03/12] boxed: 'static type bound for register_box_type --- src/boxed.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/boxed.rs b/src/boxed.rs index ffce443..a705e12 100644 --- a/src/boxed.rs +++ b/src/boxed.rs @@ -51,7 +51,9 @@ extern "C" fn box_free(raw: gpointer) { mem::drop(boxed); } -pub fn register_box_type(name: &str) -> GType where T: Clone + Send { +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(), From 79d71f87afe2e3d2348066f8eac27a9200e9d144 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Thu, 11 Jun 2015 11:22:55 +0300 Subject: [PATCH 04/12] Require glib-2_0-sys 0.1.1 For GSource and g_main_context_invoke* --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 09d9720..608bb2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" From 4075514509a1b81fa78da68bc64298ee9a38804b Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Thu, 2 Jul 2015 03:53:32 +0300 Subject: [PATCH 05/12] Added utility functions for working with boxes passed as pointers --- src/boxed.rs | 31 +++++++++++++++++++------------ src/util.rs | 24 +++++++++++++++++++++++- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/boxed.rs b/src/boxed.rs index a705e12..7f7f10e 100644 --- a/src/boxed.rs +++ b/src/boxed.rs @@ -18,10 +18,10 @@ use gtype::GType; use types::gpointer; +use util::{box_free, box_from_pointer, box_into_pointer}; use gobject as ffi; -use std::boxed::into_raw as box_into_raw; use std::ffi::CString; use std::mem; @@ -36,19 +36,26 @@ pub fn type_of() -> GType where T: BoxedType ::get_type() } -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 as *mut T) }; + let boxed: Box = box_from_pointer(raw); let copy: Box = boxed.clone(); // Prevent the original value from being dropped - box_into_raw(boxed); - box_into_raw(copy) as gpointer + mem::forget(boxed); + box_into_pointer(copy) } -extern "C" fn box_free(raw: gpointer) { - let boxed: Box = unsafe { Box::from_raw(raw as *mut T) }; - mem::drop(boxed); +unsafe fn into_boxed_copy_func(callback: unsafe extern "C" fn(gpointer) -> gpointer) + -> ffi::GBoxedCopyFunc +{ + mem::transmute(callback) +} + +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 @@ -57,8 +64,8 @@ pub fn register_box_type(name: &str) -> GType 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) } @@ -75,10 +82,10 @@ impl BoxedType for Box where T: BoxRegistered { } unsafe fn from_ptr(raw: gpointer) -> Box { - Box::from_raw(raw as *mut T) + box_from_pointer(raw) } unsafe fn into_ptr(self) -> gpointer { - box_into_raw(self) as gpointer + box_into_pointer(self) } } 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) } +} From f3182627bc5c701ccd7eccc7397c106dacac7d37 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Thu, 2 Jul 2015 04:17:09 +0300 Subject: [PATCH 06/12] mainloop: Add infrastructure for source callbacks Callback types are constructed from closures typed appropriately for the callback signature. All callbacks should provide a transformation into the RawCallback structure which is used to provide the callback data for the C APIs. --- src/mainloop.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/src/mainloop.rs b/src/mainloop.rs index d4f9327..17ba6cf 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 { From 3fe856acb395a5ce63bf948f23d9ea9757a6578e Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Thu, 2 Jul 2015 05:02:13 +0300 Subject: [PATCH 07/12] mainloop: Added MainContext methods invoke and invoke_full These provide idiomatic support for g_main_context_invoke*(). --- src/mainloop.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/mainloop.rs b/src/mainloop.rs index 17ba6cf..c37e4a2 100644 --- a/src/mainloop.rs +++ b/src/mainloop.rs @@ -132,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 { From d4cb2663a99c2b21825ff992909a10b4b29a4164 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Thu, 2 Jul 2015 05:04:57 +0300 Subject: [PATCH 08/12] mainloop: support for GSource Generic types Source and AttachedSource provide high-level representation of the various mainloop sources. --- src/mainloop.rs | 105 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/src/mainloop.rs b/src/mainloop.rs index c37e4a2..9888ad3 100644 --- a/src/mainloop.rs +++ b/src/mainloop.rs @@ -160,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 From f6ed591d55533e56a8e33a16056cc0b6cd798b4c Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Thu, 2 Jul 2015 05:09:09 +0300 Subject: [PATCH 09/12] Tests for MainContext::invoke* and sources --- tests/test-mainloop.rs | 167 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 tests/test-mainloop.rs 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); +} From 0e937b1e360119389aa38f6c0bff5635c5a924ee Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Thu, 2 Jul 2015 03:48:57 +0300 Subject: [PATCH 10/12] C structure layout for Value A Value slice should be interchangeable with an array of GValue. --- src/value.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/value.rs b/src/value.rs index f357958..d3f1078 100644 --- a/src/value.rs +++ b/src/value.rs @@ -38,6 +38,7 @@ use std::fmt; use std::mem; use std::ops::Deref; +#[repr(C)] #[unsafe_no_drop_flag] pub struct Value(ffi::GValue); From 22e5628b82411f6b9ee15100d07d2a916ee6eabd Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Thu, 2 Jul 2015 03:50:43 +0300 Subject: [PATCH 11/12] Use transmute instead of copy_lifetime std::mem::copy_lifetime has become deprecated. --- src/value.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/value.rs b/src/value.rs index d3f1078..4da73cd 100644 --- a/src/value.rs +++ b/src/value.rs @@ -274,7 +274,7 @@ impl Value { if p.is_null() { return None; } - Some(unsafe { mem::copy_lifetime(self, &*p) }) + Some(unsafe { mem::transmute(&*p) }) } pub fn set_object(&mut self, val: &T) @@ -316,7 +316,7 @@ impl Value { } unsafe { let boxed: T = BoxedType::from_ptr(p); - let ret = Some(mem::copy_lifetime(self, &*boxed)); + let ret = Some(mem::transmute(&*boxed)); // Lose the box by "subliming" it back into the raw pointer let q = boxed.into_ptr(); From 3067651e28f7489754894cc18a140fc689cb3730 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Thu, 2 Jul 2015 03:56:32 +0300 Subject: [PATCH 12/12] Drop unused features --- src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 971b545..3ea93d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,8 +21,6 @@ #![allow(unstable_features)] -#![feature(alloc)] -#![feature(core)] #![feature(unsafe_no_drop_flag)] extern crate libc;