gdk_pixbuf/
pixbuf.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{future::Future, io::Read, mem, path::Path, pin::Pin, ptr, slice};
4
5use glib::{prelude::*, translate::*, Error};
6use libc::{c_uchar, c_void};
7
8use crate::{ffi, Colorspace, Pixbuf, PixbufFormat};
9
10impl Pixbuf {
11    #[doc(alias = "gdk_pixbuf_new_from_data")]
12    pub fn from_mut_slice<T: AsMut<[u8]>>(
13        data: T,
14        colorspace: Colorspace,
15        has_alpha: bool,
16        bits_per_sample: i32,
17        width: i32,
18        height: i32,
19        row_stride: i32,
20    ) -> Pixbuf {
21        unsafe extern "C" fn destroy<T: AsMut<[u8]>>(_: *mut c_uchar, data: *mut c_void) {
22            let _data: Box<T> = Box::from_raw(data as *mut T); // the data will be destroyed now
23        }
24        assert!(width > 0, "width must be greater than 0");
25        assert!(height > 0, "height must be greater than 0");
26        assert!(row_stride > 0, "row_stride must be greater than 0");
27        assert_eq!(
28            bits_per_sample, 8,
29            "bits_per_sample == 8 is the only supported value"
30        );
31
32        let width = width as usize;
33        let height = height as usize;
34        let row_stride = row_stride as usize;
35        let bits_per_sample = bits_per_sample as usize;
36
37        let n_channels = if has_alpha { 4 } else { 3 };
38        let last_row_len = width * ((n_channels * bits_per_sample + 7) / 8);
39
40        let mut data: Box<T> = Box::new(data);
41
42        let ptr = {
43            let data: &mut [u8] = (*data).as_mut();
44            assert!(
45                data.len() >= ((height - 1) * row_stride + last_row_len),
46                "data.len() must fit the width, height, and row_stride"
47            );
48            data.as_mut_ptr()
49        };
50
51        unsafe {
52            from_glib_full(ffi::gdk_pixbuf_new_from_data(
53                ptr,
54                colorspace.into_glib(),
55                has_alpha.into_glib(),
56                bits_per_sample as i32,
57                width as i32,
58                height as i32,
59                row_stride as i32,
60                Some(destroy::<T>),
61                Box::into_raw(data) as *mut _,
62            ))
63        }
64    }
65
66    // rustdoc-stripper-ignore-next
67    /// Creates a `Pixbuf` from a type implementing `Read` (like `File`).
68    ///
69    /// ```no_run
70    /// use std::fs::File;
71    /// use gdk_pixbuf::Pixbuf;
72    ///
73    /// let f = File::open("some_file.png").expect("failed to open image");
74    /// let pixbuf = Pixbuf::from_read(f).expect("failed to load image");
75    /// ```
76    pub fn from_read<R: Read + Send + 'static>(r: R) -> Result<Pixbuf, Error> {
77        Pixbuf::from_stream(&gio::ReadInputStream::new(r), None::<&gio::Cancellable>)
78    }
79
80    #[doc(alias = "gdk_pixbuf_new_from_stream_async")]
81    pub fn from_stream_async<
82        P: IsA<gio::InputStream>,
83        Q: IsA<gio::Cancellable>,
84        R: FnOnce(Result<Pixbuf, Error>) + 'static,
85    >(
86        stream: &P,
87        cancellable: Option<&Q>,
88        callback: R,
89    ) {
90        let main_context = glib::MainContext::ref_thread_default();
91        let is_main_context_owner = main_context.is_owner();
92        let has_acquired_main_context = (!is_main_context_owner)
93            .then(|| main_context.acquire().ok())
94            .flatten();
95        assert!(
96            is_main_context_owner || has_acquired_main_context.is_some(),
97            "Async operations only allowed if the thread is owning the MainContext"
98        );
99
100        let cancellable = cancellable.map(|p| p.as_ref());
101        let user_data: Box<glib::thread_guard::ThreadGuard<R>> =
102            Box::new(glib::thread_guard::ThreadGuard::new(callback));
103        unsafe extern "C" fn from_stream_async_trampoline<
104            R: FnOnce(Result<Pixbuf, Error>) + 'static,
105        >(
106            _source_object: *mut glib::gobject_ffi::GObject,
107            res: *mut gio::ffi::GAsyncResult,
108            user_data: glib::ffi::gpointer,
109        ) {
110            let mut error = ptr::null_mut();
111            let ptr = ffi::gdk_pixbuf_new_from_stream_finish(res, &mut error);
112            let result = if error.is_null() {
113                Ok(from_glib_full(ptr))
114            } else {
115                Err(from_glib_full(error))
116            };
117            let callback: Box<glib::thread_guard::ThreadGuard<R>> =
118                Box::from_raw(user_data as *mut _);
119            let callback = callback.into_inner();
120            callback(result);
121        }
122        let callback = from_stream_async_trampoline::<R>;
123        unsafe {
124            ffi::gdk_pixbuf_new_from_stream_async(
125                stream.as_ref().to_glib_none().0,
126                cancellable.to_glib_none().0,
127                Some(callback),
128                Box::into_raw(user_data) as *mut _,
129            );
130        }
131    }
132
133    pub fn from_stream_future<P: IsA<gio::InputStream> + Clone + 'static>(
134        stream: &P,
135    ) -> Pin<Box<dyn Future<Output = Result<Pixbuf, Error>> + 'static>> {
136        let stream = stream.clone();
137        Box::pin(gio::GioFuture::new(&(), move |_obj, cancellable, send| {
138            Self::from_stream_async(&stream, Some(cancellable), move |res| {
139                send.resolve(res);
140            });
141        }))
142    }
143
144    #[doc(alias = "gdk_pixbuf_new_from_stream_at_scale_async")]
145    pub fn from_stream_at_scale_async<
146        P: IsA<gio::InputStream>,
147        Q: IsA<gio::Cancellable>,
148        R: FnOnce(Result<Pixbuf, Error>) + 'static,
149    >(
150        stream: &P,
151        width: i32,
152        height: i32,
153        preserve_aspect_ratio: bool,
154        cancellable: Option<&Q>,
155        callback: R,
156    ) {
157        let main_context = glib::MainContext::ref_thread_default();
158        let is_main_context_owner = main_context.is_owner();
159        let has_acquired_main_context = (!is_main_context_owner)
160            .then(|| main_context.acquire().ok())
161            .flatten();
162        assert!(
163            is_main_context_owner || has_acquired_main_context.is_some(),
164            "Async operations only allowed if the thread is owning the MainContext"
165        );
166
167        let cancellable = cancellable.map(|p| p.as_ref());
168        let user_data: Box<glib::thread_guard::ThreadGuard<R>> =
169            Box::new(glib::thread_guard::ThreadGuard::new(callback));
170        unsafe extern "C" fn from_stream_at_scale_async_trampoline<
171            R: FnOnce(Result<Pixbuf, Error>) + 'static,
172        >(
173            _source_object: *mut glib::gobject_ffi::GObject,
174            res: *mut gio::ffi::GAsyncResult,
175            user_data: glib::ffi::gpointer,
176        ) {
177            let mut error = ptr::null_mut();
178            let ptr = ffi::gdk_pixbuf_new_from_stream_finish(res, &mut error);
179            let result = if error.is_null() {
180                Ok(from_glib_full(ptr))
181            } else {
182                Err(from_glib_full(error))
183            };
184            let callback: Box<glib::thread_guard::ThreadGuard<R>> =
185                Box::from_raw(user_data as *mut _);
186            let callback = callback.into_inner();
187            callback(result);
188        }
189        let callback = from_stream_at_scale_async_trampoline::<R>;
190        unsafe {
191            ffi::gdk_pixbuf_new_from_stream_at_scale_async(
192                stream.as_ref().to_glib_none().0,
193                width,
194                height,
195                preserve_aspect_ratio.into_glib(),
196                cancellable.to_glib_none().0,
197                Some(callback),
198                Box::into_raw(user_data) as *mut _,
199            );
200        }
201    }
202
203    pub fn from_stream_at_scale_future<P: IsA<gio::InputStream> + Clone + 'static>(
204        stream: &P,
205        width: i32,
206        height: i32,
207        preserve_aspect_ratio: bool,
208    ) -> Pin<Box<dyn Future<Output = Result<Pixbuf, Error>> + 'static>> {
209        let stream = stream.clone();
210        Box::pin(gio::GioFuture::new(&(), move |_obj, cancellable, send| {
211            Self::from_stream_at_scale_async(
212                &stream,
213                width,
214                height,
215                preserve_aspect_ratio,
216                Some(cancellable),
217                move |res| {
218                    send.resolve(res);
219                },
220            );
221        }))
222    }
223
224    // rustdoc-stripper-ignore-next
225    /// Returns a mutable slice to the pixbuf's pixel data.
226    ///
227    /// This function will cause an implicit copy if the pixbuf was created from read-only data.
228    ///
229    /// Please see the section on [image data](#image-data) for information about how the pixel
230    /// data is stored in memory.
231    ///
232    /// # Safety
233    /// No other reference to this pixbuf's data must exist when this method is called.
234    ///
235    /// Until you drop the returned reference, you must not call any methods on the pixbuf which may read
236    /// or write to the data.
237    #[allow(clippy::mut_from_ref)]
238    #[allow(clippy::missing_safety_doc)]
239    #[doc(alias = "gdk_pixbuf_get_pixels_with_length")]
240    #[doc(alias = "get_pixels")]
241    pub unsafe fn pixels(&self) -> &mut [u8] {
242        let mut len = 0;
243        let ptr = ffi::gdk_pixbuf_get_pixels_with_length(self.to_glib_none().0, &mut len);
244        if len == 0 {
245            return &mut [];
246        }
247        slice::from_raw_parts_mut(ptr, len as usize)
248    }
249
250    pub fn put_pixel(&self, x: u32, y: u32, red: u8, green: u8, blue: u8, alpha: u8) {
251        assert!(
252            x < self.width() as u32,
253            "x must be less than the pixbuf's width"
254        );
255        assert!(
256            y < self.height() as u32,
257            "y must be less than the pixbuf's height"
258        );
259
260        unsafe {
261            let x = x as usize;
262            let y = y as usize;
263            let n_channels = self.n_channels() as usize;
264            assert!(n_channels == 3 || n_channels == 4);
265            let rowstride = self.rowstride() as usize;
266            let pixels = self.pixels();
267            let pos = y * rowstride + x * n_channels;
268
269            pixels[pos] = red;
270            pixels[pos + 1] = green;
271            pixels[pos + 2] = blue;
272            if n_channels == 4 {
273                pixels[pos + 3] = alpha;
274            }
275        }
276    }
277
278    /// Parses an image file far enough to determine its format and size.
279    /// ## `filename`
280    /// The name of the file to identify.
281    ///
282    /// # Returns
283    ///
284    /// A [`PixbufFormat`][crate::PixbufFormat] describing
285    ///   the image format of the file
286    ///
287    /// ## `width`
288    /// Return location for the width of the image
289    ///
290    /// ## `height`
291    /// Return location for the height of the image
292    #[doc(alias = "gdk_pixbuf_get_file_info")]
293    #[doc(alias = "get_file_info")]
294    pub fn file_info<T: AsRef<Path>>(filename: T) -> Option<(PixbufFormat, i32, i32)> {
295        unsafe {
296            let mut width = mem::MaybeUninit::uninit();
297            let mut height = mem::MaybeUninit::uninit();
298            let ret = ffi::gdk_pixbuf_get_file_info(
299                filename.as_ref().to_glib_none().0,
300                width.as_mut_ptr(),
301                height.as_mut_ptr(),
302            );
303            if !ret.is_null() {
304                Some((
305                    from_glib_none(ret),
306                    width.assume_init(),
307                    height.assume_init(),
308                ))
309            } else {
310                None
311            }
312        }
313    }
314
315    /// Asynchronously parses an image file far enough to determine its
316    /// format and size.
317    ///
318    /// For more details see gdk_pixbuf_get_file_info(), which is the synchronous
319    /// version of this function.
320    ///
321    /// When the operation is finished, @callback will be called in the
322    /// main thread. You can then call gdk_pixbuf_get_file_info_finish() to
323    /// get the result of the operation.
324    /// ## `filename`
325    /// The name of the file to identify
326    /// ## `cancellable`
327    /// optional `GCancellable` object, `NULL` to ignore
328    /// ## `callback`
329    /// a `GAsyncReadyCallback` to call when the file info is available
330    #[doc(alias = "gdk_pixbuf_get_file_info_async")]
331    #[doc(alias = "get_file_info_async")]
332    pub fn file_info_async<
333        P: IsA<gio::Cancellable>,
334        Q: FnOnce(Result<Option<(PixbufFormat, i32, i32)>, Error>) + 'static,
335        T: AsRef<Path>,
336    >(
337        filename: T,
338        cancellable: Option<&P>,
339        callback: Q,
340    ) {
341        let main_context = glib::MainContext::ref_thread_default();
342        let is_main_context_owner = main_context.is_owner();
343        let has_acquired_main_context = (!is_main_context_owner)
344            .then(|| main_context.acquire().ok())
345            .flatten();
346        assert!(
347            is_main_context_owner || has_acquired_main_context.is_some(),
348            "Async operations only allowed if the thread is owning the MainContext"
349        );
350
351        let cancellable = cancellable.map(|p| p.as_ref());
352        let user_data: Box<glib::thread_guard::ThreadGuard<Q>> =
353            Box::new(glib::thread_guard::ThreadGuard::new(callback));
354        unsafe extern "C" fn get_file_info_async_trampoline<
355            Q: FnOnce(Result<Option<(PixbufFormat, i32, i32)>, Error>) + 'static,
356        >(
357            _source_object: *mut glib::gobject_ffi::GObject,
358            res: *mut gio::ffi::GAsyncResult,
359            user_data: glib::ffi::gpointer,
360        ) {
361            let mut error = ptr::null_mut();
362            let mut width = mem::MaybeUninit::uninit();
363            let mut height = mem::MaybeUninit::uninit();
364            let ret = ffi::gdk_pixbuf_get_file_info_finish(
365                res,
366                width.as_mut_ptr(),
367                height.as_mut_ptr(),
368                &mut error,
369            );
370            let result = if !error.is_null() {
371                Err(from_glib_full(error))
372            } else if ret.is_null() {
373                Ok(None)
374            } else {
375                Ok(Some((
376                    from_glib_none(ret),
377                    width.assume_init(),
378                    height.assume_init(),
379                )))
380            };
381            let callback: Box<glib::thread_guard::ThreadGuard<Q>> =
382                Box::from_raw(user_data as *mut _);
383            let callback = callback.into_inner();
384            callback(result);
385        }
386        let callback = get_file_info_async_trampoline::<Q>;
387        unsafe {
388            ffi::gdk_pixbuf_get_file_info_async(
389                filename.as_ref().to_glib_none().0,
390                cancellable.to_glib_none().0,
391                Some(callback),
392                Box::into_raw(user_data) as *mut _,
393            );
394        }
395    }
396
397    #[allow(clippy::type_complexity)]
398    #[doc(alias = "get_file_info_async")]
399    pub fn file_info_future<T: AsRef<Path> + Clone + 'static>(
400        filename: T,
401    ) -> Pin<Box<dyn Future<Output = Result<Option<(PixbufFormat, i32, i32)>, Error>> + 'static>>
402    {
403        Box::pin(gio::GioFuture::new(&(), move |_obj, cancellable, send| {
404            Self::file_info_async(filename, Some(cancellable), move |res| {
405                send.resolve(res);
406            });
407        }))
408    }
409
410    /// Vector version of `gdk_pixbuf_save_to_buffer()`.
411    ///
412    /// Saves pixbuf to a new buffer in format @type_, which is currently "jpeg",
413    /// "tiff", "png", "ico" or "bmp".
414    ///
415    /// See `GdkPixbuf::Pixbuf::save_to_buffer()` for more details.
416    /// ## `type_`
417    /// name of file format.
418    /// ## `option_keys`
419    /// name of options to set
420    /// ## `option_values`
421    /// values for named options
422    ///
423    /// # Returns
424    ///
425    /// whether an error was set
426    ///
427    /// ## `buffer`
428    ///
429    ///   location to receive a pointer to the new buffer.
430    #[doc(alias = "gdk_pixbuf_save_to_bufferv")]
431    pub fn save_to_bufferv(&self, type_: &str, options: &[(&str, &str)]) -> Result<Vec<u8>, Error> {
432        unsafe {
433            let mut buffer = ptr::null_mut();
434            let mut buffer_size = mem::MaybeUninit::uninit();
435            let mut error = ptr::null_mut();
436            let option_keys: Vec<&str> = options.iter().map(|o| o.0).collect();
437            let option_values: Vec<&str> = options.iter().map(|o| o.1).collect();
438            let _ = ffi::gdk_pixbuf_save_to_bufferv(
439                self.to_glib_none().0,
440                &mut buffer,
441                buffer_size.as_mut_ptr(),
442                type_.to_glib_none().0,
443                option_keys.to_glib_none().0,
444                option_values.to_glib_none().0,
445                &mut error,
446            );
447            if error.is_null() {
448                Ok(FromGlibContainer::from_glib_full_num(
449                    buffer,
450                    buffer_size.assume_init() as _,
451                ))
452            } else {
453                Err(from_glib_full(error))
454            }
455        }
456    }
457
458    /// Saves `pixbuf` to an output stream.
459    ///
460    /// Supported file formats are currently "jpeg", "tiff", "png", "ico" or
461    /// "bmp".
462    ///
463    /// See `GdkPixbuf::Pixbuf::save_to_stream()` for more details.
464    /// ## `stream`
465    /// a `GOutputStream` to save the pixbuf to
466    /// ## `type_`
467    /// name of file format
468    /// ## `option_keys`
469    /// name of options to set
470    /// ## `option_values`
471    /// values for named options
472    /// ## `cancellable`
473    /// optional `GCancellable` object, `NULL` to ignore
474    ///
475    /// # Returns
476    ///
477    /// `TRUE` if the pixbuf was saved successfully, `FALSE` if an
478    ///   error was set.
479    #[doc(alias = "gdk_pixbuf_save_to_streamv")]
480    pub fn save_to_streamv<P: IsA<gio::OutputStream>, Q: IsA<gio::Cancellable>>(
481        &self,
482        stream: &P,
483        type_: &str,
484        options: &[(&str, &str)],
485        cancellable: Option<&Q>,
486    ) -> Result<(), Error> {
487        let cancellable = cancellable.map(|p| p.as_ref());
488        unsafe {
489            let mut error = ptr::null_mut();
490            let option_keys: Vec<&str> = options.iter().map(|o| o.0).collect();
491            let option_values: Vec<&str> = options.iter().map(|o| o.1).collect();
492            let _ = ffi::gdk_pixbuf_save_to_streamv(
493                self.to_glib_none().0,
494                stream.as_ref().to_glib_none().0,
495                type_.to_glib_none().0,
496                option_keys.to_glib_none().0,
497                option_values.to_glib_none().0,
498                cancellable.to_glib_none().0,
499                &mut error,
500            );
501            if error.is_null() {
502                Ok(())
503            } else {
504                Err(from_glib_full(error))
505            }
506        }
507    }
508
509    /// Saves `pixbuf` to an output stream asynchronously.
510    ///
511    /// For more details see gdk_pixbuf_save_to_streamv(), which is the synchronous
512    /// version of this function.
513    ///
514    /// When the operation is finished, `callback` will be called in the main thread.
515    ///
516    /// You can then call gdk_pixbuf_save_to_stream_finish() to get the result of
517    /// the operation.
518    /// ## `stream`
519    /// a `GOutputStream` to which to save the pixbuf
520    /// ## `type_`
521    /// name of file format
522    /// ## `option_keys`
523    /// name of options to set
524    /// ## `option_values`
525    /// values for named options
526    /// ## `cancellable`
527    /// optional `GCancellable` object, `NULL` to ignore
528    /// ## `callback`
529    /// a `GAsyncReadyCallback` to call when the pixbuf is saved
530    #[doc(alias = "gdk_pixbuf_save_to_streamv_async")]
531    pub fn save_to_streamv_async<
532        P: IsA<gio::OutputStream>,
533        Q: IsA<gio::Cancellable>,
534        R: FnOnce(Result<(), Error>) + 'static,
535    >(
536        &self,
537        stream: &P,
538        type_: &str,
539        options: &[(&str, &str)],
540        cancellable: Option<&Q>,
541        callback: R,
542    ) {
543        let main_context = glib::MainContext::ref_thread_default();
544        let is_main_context_owner = main_context.is_owner();
545        let has_acquired_main_context = (!is_main_context_owner)
546            .then(|| main_context.acquire().ok())
547            .flatten();
548        assert!(
549            is_main_context_owner || has_acquired_main_context.is_some(),
550            "Async operations only allowed if the thread is owning the MainContext"
551        );
552
553        let cancellable = cancellable.map(|p| p.as_ref());
554        let user_data: Box<glib::thread_guard::ThreadGuard<R>> =
555            Box::new(glib::thread_guard::ThreadGuard::new(callback));
556        unsafe extern "C" fn save_to_streamv_async_trampoline<
557            R: FnOnce(Result<(), Error>) + 'static,
558        >(
559            _source_object: *mut glib::gobject_ffi::GObject,
560            res: *mut gio::ffi::GAsyncResult,
561            user_data: glib::ffi::gpointer,
562        ) {
563            let mut error = ptr::null_mut();
564            let _ = ffi::gdk_pixbuf_save_to_stream_finish(res, &mut error);
565            let result = if error.is_null() {
566                Ok(())
567            } else {
568                Err(from_glib_full(error))
569            };
570            let callback: Box<glib::thread_guard::ThreadGuard<R>> =
571                Box::from_raw(user_data as *mut _);
572            let callback = callback.into_inner();
573            callback(result);
574        }
575        let callback = save_to_streamv_async_trampoline::<R>;
576        unsafe {
577            let option_keys: Vec<&str> = options.iter().map(|o| o.0).collect();
578            let option_values: Vec<&str> = options.iter().map(|o| o.1).collect();
579            ffi::gdk_pixbuf_save_to_streamv_async(
580                self.to_glib_none().0,
581                stream.as_ref().to_glib_none().0,
582                type_.to_glib_none().0,
583                option_keys.to_glib_none().0,
584                option_values.to_glib_none().0,
585                cancellable.to_glib_none().0,
586                Some(callback),
587                Box::into_raw(user_data) as *mut _,
588            );
589        }
590    }
591
592    pub fn save_to_streamv_future<P: IsA<gio::OutputStream> + Clone + 'static>(
593        &self,
594        stream: &P,
595        type_: &str,
596        options: &[(&str, &str)],
597    ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + 'static>> {
598        let stream = stream.clone();
599        let type_ = String::from(type_);
600        let options = options
601            .iter()
602            .map(|&(k, v)| (String::from(k), String::from(v)))
603            .collect::<Vec<(String, String)>>();
604        Box::pin(gio::GioFuture::new(self, move |obj, cancellable, send| {
605            let options = options
606                .iter()
607                .map(|(k, v)| (k.as_str(), v.as_str()))
608                .collect::<Vec<(&str, &str)>>();
609
610            obj.save_to_streamv_async(
611                &stream,
612                &type_,
613                options.as_slice(),
614                Some(cancellable),
615                move |res| {
616                    send.resolve(res);
617                },
618            );
619        }))
620    }
621
622    /// Vector version of `gdk_pixbuf_save()`.
623    ///
624    /// Saves pixbuf to a file in `type`, which is currently "jpeg", "png", "tiff", "ico" or "bmp".
625    ///
626    /// If @error is set, `FALSE` will be returned.
627    ///
628    /// See `GdkPixbuf::Pixbuf::save()` for more details.
629    /// ## `filename`
630    /// name of file to save.
631    /// ## `type_`
632    /// name of file format.
633    /// ## `option_keys`
634    /// name of options to set
635    /// ## `option_values`
636    /// values for named options
637    ///
638    /// # Returns
639    ///
640    /// whether an error was set
641    #[doc(alias = "gdk_pixbuf_savev")]
642    pub fn savev<T: AsRef<Path>>(
643        &self,
644        filename: T,
645        type_: &str,
646        options: &[(&str, &str)],
647    ) -> Result<(), Error> {
648        unsafe {
649            let mut error = ptr::null_mut();
650            let option_keys: Vec<&str> = options.iter().map(|o| o.0).collect();
651            let option_values: Vec<&str> = options.iter().map(|o| o.1).collect();
652            let _ = ffi::gdk_pixbuf_savev(
653                self.to_glib_none().0,
654                filename.as_ref().to_glib_none().0,
655                type_.to_glib_none().0,
656                option_keys.to_glib_none().0,
657                option_values.to_glib_none().0,
658                &mut error,
659            );
660            if error.is_null() {
661                Ok(())
662            } else {
663                Err(from_glib_full(error))
664            }
665        }
666    }
667}