1use std::{
2 ffi::{CStr, CString},
3 os::raw::{c_char, c_int, c_ulonglong, c_void},
4 path::Path,
5 ptr::null_mut,
6 sync::atomic::{AtomicBool, Ordering},
7};
8
9use crate::bindings::{LibreOfficeKit, LibreOfficeKitClass, LibreOfficeKitDocument};
10use dlopen2::wrapper::{Container, WrapperApi};
11use once_cell::sync::OnceCell;
12use parking_lot::Mutex;
13
14use crate::{error::OfficeError, urls::DocUrl};
15
16static LOK_CONTAINER: OnceCell<Container<LibreOfficeApi>> = OnceCell::new();
18
19pub(crate) static GLOBAL_OFFICE_LOCK: AtomicBool = AtomicBool::new(false);
23
24pub type CallbackData = *mut Box<dyn FnMut(c_int, *const c_char)>;
26
27#[cfg(target_os = "windows")]
28const TARGET_LIB: &str = "sofficeapp.dll";
29#[cfg(target_os = "windows")]
30const TARGET_MERGED_LIB: &str = "mergedlo.dll";
31
32#[cfg(target_os = "linux")]
33const TARGET_LIB: &str = "libsofficeapp.so";
34#[cfg(target_os = "linux")]
35const TARGET_MERGED_LIB: &str = "libmergedlo.so";
36
37#[cfg(target_os = "macos")]
38const TARGET_LIB: &str = "libsofficeapp.dylib";
39#[cfg(target_os = "macos")]
40const TARGET_MERGED_LIB: &str = "libmergedlo.dylib";
41
42#[derive(WrapperApi)]
43struct LibreOfficeApi {
44 lok_preinit: Option<
46 fn(
47 install_path: *const std::os::raw::c_char,
48 user_profile_url: *const std::os::raw::c_char,
49 ) -> std::os::raw::c_int,
50 >,
51
52 libreofficekit_hook:
53 Option<fn(install_path: *const std::os::raw::c_char) -> *mut LibreOfficeKit>,
54
55 libreofficekit_hook_2: Option<
56 fn(
57 install_path: *const std::os::raw::c_char,
58 user_profile_url: *const std::os::raw::c_char,
59 ) -> *mut LibreOfficeKit,
60 >,
61}
62
63fn lok_open(install_path: &Path) -> Result<Container<LibreOfficeApi>, OfficeError> {
65 if let Ok(path) = std::env::var("PATH") {
67 let install_path = install_path.to_string_lossy();
68 let install_path = install_path.as_ref();
69
70 if !path.contains(install_path) {
71 std::env::set_var("PATH", format!("{};{}", install_path, path));
72 }
73 }
74
75 let target_lib_path = install_path.join(TARGET_LIB);
76 if target_lib_path.exists() {
77 let err = match unsafe { Container::load(&target_lib_path) } {
79 Ok(value) => return Ok(value),
80 Err(err) => err,
81 };
82
83 if std::fs::File::open(target_lib_path)
86 .and_then(|file| file.metadata())
87 .is_ok_and(|value| value.len() > 100)
88 {
89 return Err(OfficeError::LoadLibrary(err));
90 }
91 }
92
93 let target_merged_lib_path = install_path.join(TARGET_MERGED_LIB);
94 if target_merged_lib_path.exists() {
95 let err = match unsafe { Container::load_with_flags(target_merged_lib_path, Some(2)) } {
97 Ok(value) => return Ok(value),
98 Err(err) => err,
99 };
100
101 return Err(OfficeError::LoadLibrary(err));
102 }
103
104 Err(OfficeError::MissingLibrary)
105}
106
107fn lok_init(install_path: &Path) -> Result<*mut LibreOfficeKit, OfficeError> {
108 let container = LOK_CONTAINER.get_or_try_init(|| lok_open(install_path))?;
110
111 let lok_hook = container
113 .libreofficekit_hook
114 .ok_or(OfficeError::MissingLibraryHook)?;
115
116 let install_path = install_path.to_str().ok_or(OfficeError::InvalidPath)?;
117 let install_path = CString::new(install_path)?;
118
119 let lok = lok_hook(install_path.as_ptr());
120
121 Ok(lok)
122}
123
124pub struct OfficeRaw {
126 this: *mut LibreOfficeKit,
128 class: *mut LibreOfficeKitClass,
130 callback_data: Mutex<CallbackData>,
132}
133
134impl OfficeRaw {
135 pub unsafe fn init(install_path: &Path) -> Result<Self, OfficeError> {
137 let lok = lok_init(install_path)?;
138
139 if lok.is_null() {
140 return Err(OfficeError::UnknownInit);
141 }
142
143 let lok_class = (*lok).pClass;
144
145 let instance = Self {
146 this: lok,
147 class: lok_class,
148 callback_data: Mutex::new(null_mut()),
149 };
150
151 Ok(instance)
152 }
153
154 pub unsafe fn get_filter_types(&self) -> Result<CString, OfficeError> {
156 let get_filter_types = (*self.class)
157 .getFilterTypes
158 .ok_or(OfficeError::MissingFunction("getFilterTypes"))?;
159
160 let value = get_filter_types(self.this);
161
162 if let Some(error) = self.get_error() {
163 return Err(OfficeError::OfficeError(error));
164 }
165
166 Ok(CString::from_raw(value))
167 }
168
169 pub unsafe fn get_version_info(&self) -> Result<CString, OfficeError> {
171 let get_version_info = (*self.class)
172 .getVersionInfo
173 .ok_or(OfficeError::MissingFunction("getVersionInfo"))?;
174
175 let value = get_version_info(self.this);
176
177 if let Some(error) = self.get_error() {
178 return Err(OfficeError::OfficeError(error));
179 }
180
181 Ok(CString::from_raw(value))
182 }
183
184 pub unsafe fn dump_state(&self) -> Result<CString, OfficeError> {
186 let mut state: *mut c_char = null_mut();
187 let dump_state = (*self.class)
188 .dumpState
189 .ok_or(OfficeError::MissingFunction("dumpState"))?;
190 dump_state(self.this, std::ptr::null(), &mut state);
191
192 if let Some(error) = self.get_error() {
193 return Err(OfficeError::OfficeError(error));
194 }
195
196 Ok(CString::from_raw(state))
197 }
198
199 pub unsafe fn trim_memory(&self, target: c_int) -> Result<(), OfficeError> {
201 let trim_memory = (*self.class)
202 .trimMemory
203 .ok_or(OfficeError::MissingFunction("trimMemory"))?;
204 trim_memory(self.this, target);
205
206 if let Some(error) = self.get_error() {
208 return Err(OfficeError::OfficeError(error));
209 }
210
211 Ok(())
212 }
213
214 pub unsafe fn set_option(
216 &self,
217 option: *const c_char,
218 value: *const c_char,
219 ) -> Result<(), OfficeError> {
220 let set_option = (*self.class)
221 .setOption
222 .ok_or(OfficeError::MissingFunction("setOption"))?;
223 set_option(self.this, option, value);
224
225 if let Some(error) = self.get_error() {
227 return Err(OfficeError::OfficeError(error));
228 }
229
230 Ok(())
231 }
232
233 pub unsafe fn sign_document(
235 &self,
236 url: &DocUrl,
237 certificate: *const u8,
238 certificate_len: i32,
239 private_key: *const u8,
240 private_key_len: i32,
241 ) -> Result<bool, OfficeError> {
242 let sign_document = (*self.class)
243 .signDocument
244 .ok_or(OfficeError::MissingFunction("signDocument"))?;
245 let result = sign_document(
246 self.this,
247 url.as_ptr(),
248 certificate,
249 certificate_len,
250 private_key,
251 private_key_len,
252 );
253
254 Ok(result)
255 }
256
257 pub unsafe fn document_load(&self, url: &DocUrl) -> Result<DocumentRaw, OfficeError> {
259 let document_load = (*self.class)
260 .documentLoad
261 .ok_or(OfficeError::MissingFunction("documentLoad"))?;
262 let this = document_load(self.this, url.as_ptr());
263
264 if let Some(error) = self.get_error() {
266 return Err(OfficeError::OfficeError(error));
267 }
268
269 debug_assert!(!this.is_null());
270
271 Ok(DocumentRaw { this })
272 }
273
274 pub unsafe fn document_load_with_options(
276 &self,
277 url: &DocUrl,
278 options: *const c_char,
279 ) -> Result<DocumentRaw, OfficeError> {
280 let document_load_with_options = (*self.class)
281 .documentLoadWithOptions
282 .ok_or(OfficeError::MissingFunction("documentLoadWithOptions"))?;
283 let this = document_load_with_options(self.this, url.as_ptr(), options);
284
285 if let Some(error) = self.get_error() {
287 return Err(OfficeError::OfficeError(error));
288 }
289
290 debug_assert!(!this.is_null());
291
292 Ok(DocumentRaw { this })
293 }
294
295 pub unsafe fn set_document_password(
300 &self,
301 url: &DocUrl,
302 password: *const c_char,
303 ) -> Result<(), OfficeError> {
304 let set_document_password = (*self.class)
305 .setDocumentPassword
306 .ok_or(OfficeError::MissingFunction("setDocumentPassword"))?;
307
308 set_document_password(self.this, url.as_ptr(), password);
309
310 if let Some(error) = self.get_error() {
312 return Err(OfficeError::OfficeError(error));
313 }
314
315 Ok(())
316 }
317
318 pub unsafe fn set_optional_features(&self, features: u64) -> Result<(), OfficeError> {
320 let set_optional_features = (*self.class)
321 .setOptionalFeatures
322 .ok_or(OfficeError::MissingFunction("setOptionalFeatures"))?;
323 set_optional_features(self.this, features);
324
325 if let Some(error) = self.get_error() {
327 return Err(OfficeError::OfficeError(error));
328 }
329
330 Ok(())
331 }
332
333 pub unsafe fn send_dialog_event(
334 &self,
335 window_id: c_ulonglong,
336 arguments: *const c_char,
337 ) -> Result<(), OfficeError> {
338 let send_dialog_event = (*self.class)
339 .sendDialogEvent
340 .ok_or(OfficeError::MissingFunction("sendDialogEvent"))?;
341
342 send_dialog_event(self.this, window_id, arguments);
343
344 if let Some(error) = self.get_error() {
346 return Err(OfficeError::OfficeError(error));
347 }
348
349 Ok(())
350 }
351
352 pub unsafe fn run_macro(&self, url: *const c_char) -> Result<bool, OfficeError> {
353 let run_macro = (*self.class)
354 .runMacro
355 .ok_or(OfficeError::MissingFunction("runMacro"))?;
356
357 let result = run_macro(self.this, url);
358
359 if result == 0 {
360 if let Some(error) = self.get_error() {
362 return Err(OfficeError::OfficeError(error));
363 }
364 }
365
366 Ok(result != 0)
367 }
368
369 pub unsafe fn clear_callback(&self) -> Result<(), OfficeError> {
371 let register_callback = (*self.class)
372 .registerCallback
373 .ok_or(OfficeError::MissingFunction("registerCallback"))?;
374
375 register_callback(self.this, None, null_mut());
376
377 if let Some(error) = self.get_error() {
379 return Err(OfficeError::OfficeError(error));
380 }
381
382 self.free_callback();
383
384 Ok(())
385 }
386
387 pub unsafe fn register_callback<F>(&self, callback: F) -> Result<(), OfficeError>
388 where
389 F: FnMut(c_int, *const c_char) + 'static,
390 {
391 unsafe extern "C" fn callback_shim(ty: c_int, payload: *const c_char, data: *mut c_void) {
393 let callback: *mut Box<dyn FnMut(c_int, *const c_char)> = data.cast();
395
396 _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
398 (**callback)(ty, payload);
400 }));
401 }
402
403 let callback_ptr: *mut Box<dyn FnMut(c_int, *const c_char)> =
405 Box::into_raw(Box::new(Box::new(callback)));
406
407 let register_callback = (*self.class)
408 .registerCallback
409 .ok_or(OfficeError::MissingFunction("registerCallback"))?;
410
411 register_callback(self.this, Some(callback_shim), callback_ptr.cast());
412
413 if let Some(error) = self.get_error() {
415 return Err(OfficeError::OfficeError(error));
416 }
417
418 self.free_callback();
420
421 *self.callback_data.lock() = callback_ptr;
423
424 Ok(())
425 }
426
427 unsafe fn free_callback(&self) {
430 let callback = &mut *self.callback_data.lock();
431
432 if callback.is_null() {
434 return;
435 }
436
437 let mut callback_ptr: CallbackData = null_mut();
438
439 std::mem::swap(callback, &mut callback_ptr);
441
442 _ = Box::from_raw(callback_ptr);
444 }
445
446 pub unsafe fn get_error(&self) -> Option<String> {
448 let get_error = (*self.class).getError.expect("missing getError function");
449 let raw_error = get_error(self.this);
450
451 if *raw_error == 0 {
453 return None;
454 }
455
456 let value = CStr::from_ptr(raw_error).to_string_lossy().into_owned();
458
459 self.free_error(raw_error);
461
462 Some(value)
463 }
464
465 unsafe fn free_error(&self, error: *mut c_char) {
470 if let Some(free_error) = (*self.class).freeError {
472 free_error(error);
473 }
474 }
475
476 pub unsafe fn destroy(&self) {
479 let destroy = (*self.class).destroy.expect("missing destroy function");
480 destroy(self.this);
481
482 self.free_callback();
484 }
485}
486
487impl Drop for OfficeRaw {
488 fn drop(&mut self) {
489 unsafe { self.destroy() }
490
491 GLOBAL_OFFICE_LOCK.store(false, Ordering::SeqCst)
493 }
494}
495
496pub struct DocumentRaw {
497 this: *mut LibreOfficeKitDocument,
499}
500
501impl DocumentRaw {
502 pub unsafe fn save_as(
504 &mut self,
505 url: &DocUrl,
506 format: *const c_char,
507 filter: *const c_char,
508 ) -> Result<i32, OfficeError> {
509 let class = (*self.this).pClass;
510 let save_as = (*class)
511 .saveAs
512 .ok_or(OfficeError::MissingFunction("saveAs"))?;
513
514 Ok(save_as(self.this, url.as_ptr(), format, filter))
515 }
516
517 pub unsafe fn get_document_type(&mut self) -> Result<i32, OfficeError> {
519 let class = (*self.this).pClass;
520 let get_document_type = (*class)
521 .getDocumentType
522 .ok_or(OfficeError::MissingFunction("getDocumentType"))?;
523
524 Ok(get_document_type(self.this))
525 }
526
527 pub unsafe fn destroy(&mut self) {
528 let class = (*self.this).pClass;
529 let destroy = (*class).destroy.expect("missing destroy function");
530 destroy(self.this);
531 }
532}
533
534impl Drop for DocumentRaw {
535 fn drop(&mut self) {
536 unsafe { self.destroy() }
537 }
538}