sapp_linux/
clipboard.rs

1//! Clipboard implementation for X11
2//! Clipboard API on X11 is pretty weird https://www.uninformativ.de/blog/postings/2017-04-02/0/POSTING-en.html
3//! so use this with caution.
4
5use crate::x::*;
6
7use crate::{_sapp_x11_display, _sapp_x11_window};
8
9const CurrentTime: libc::c_long = 0 as libc::c_long;
10pub(crate) const SelectionRequest: libc::c_int = 30 as libc::c_int;
11pub(crate) const SelectionNotify: libc::c_int = 31 as libc::c_int;
12const AnyPropertyType: libc::c_long = 0 as libc::c_long;
13
14type Time = libc::c_ulong;
15
16pub unsafe fn get_clipboard(
17    mut bufname: *const libc::c_char,
18    mut fmtname: *const libc::c_char,
19) -> Option<String> {
20    assert!(_sapp_x11_display as usize != 0 && _sapp_x11_window != 0);
21
22    let mut result = 0 as *mut libc::c_char;
23    let mut ressize: libc::c_ulong = 0;
24    let mut restail: libc::c_ulong = 0;
25    let mut resbits: libc::c_int = 0;
26    let mut bufid = XInternAtom(_sapp_x11_display, bufname, false as _);
27    let mut fmtid = XInternAtom(_sapp_x11_display, fmtname, false as _);
28    let mut propid = XInternAtom(
29        _sapp_x11_display,
30        b"XSEL_DATA\x00" as *const u8 as *const libc::c_char,
31        false as _,
32    );
33    let mut incrid = XInternAtom(
34        _sapp_x11_display,
35        b"INCR\x00" as *const u8 as *const libc::c_char,
36        false as _,
37    );
38    let mut event = _XEvent { type_0: 0 };
39
40    // Here we ask X server to ask current clipboard owner to convert selection to UTF8 for us
41    // Selection owner is other X11 application and if it will not satisfy our request for any reason -
42    // we will wait for appropriate event forever
43    // but OK lets believe that all other linux apps will gracefully send us nice utf-8 strings
44    XConvertSelection(
45        _sapp_x11_display,
46        bufid,
47        fmtid,
48        propid,
49        _sapp_x11_window,
50        CurrentTime as Time,
51    );
52
53    // And now X server will respond with clipboard data
54    // But we want "get_clipboard" to be blocking function and get result asap
55    // So we just start to wait for the event right here
56    // In case that our app already is clipboard owner - we need to handle SelectionNotify for data response
57    // and SelectionRequest - to handle this request we just did couple of lines above
58    loop {
59        XNextEvent(_sapp_x11_display, &mut event);
60        if !(event.type_0 != SelectionNotify || event.xselection.selection != bufid) {
61            break;
62        }
63        if event.type_0 == SelectionRequest {
64            respond_to_clipboard_request(&mut event as *mut _);
65        }
66    }
67    if event.xselection.property != 0 {
68        let read_size = (100 as u32 * std::mem::size_of::<Atom>() as u32) as libc::c_long;
69        let mut bytes: Vec<u8> = vec![];
70        let mut offset: libc::c_long = 0 as libc::c_long;
71        loop {
72            XGetWindowProperty(
73                _sapp_x11_display,
74                _sapp_x11_window,
75                propid,
76                offset,
77                read_size,
78                false as _,
79                AnyPropertyType as Atom,
80                &mut fmtid,
81                &mut resbits,
82                &mut ressize,
83                &mut restail,
84                &mut result as *mut *mut libc::c_char as *mut *mut libc::c_uchar,
85            );
86            if fmtid == incrid {
87                XFree(result as *mut libc::c_void);
88                panic!("Buffer is too large and INCR reading is not implemented yet.");
89            } else {
90                let slice = std::slice::from_raw_parts(result as *const _, ressize as _);
91                let str_result = bytes.extend(slice);
92
93                XFree(result as *mut libc::c_void);
94
95                if restail == 0 {
96                    return std::str::from_utf8(&bytes[..]).map(|s| s.to_owned()).ok();
97                } else {
98                    offset += read_size;
99                }
100            }
101        }
102    }
103
104    return None;
105}
106
107// Next message for clipboard request
108static mut MESSAGE: Option<String> = None;
109
110/// Claim that our app is X11 clipboard owner
111/// Now when some other linux app will ask X11 for clipboard content - it will be redirected to our app
112pub unsafe fn claim_clipboard_ownership(mut bufname: *const libc::c_char, message: String) {
113    assert!(_sapp_x11_display as usize != 0 && _sapp_x11_window != 0);
114
115    let mut selection = XInternAtom(
116        _sapp_x11_display,
117        bufname as *const u8 as *const libc::c_char,
118        0 as libc::c_int,
119    );
120
121    XSetSelectionOwner(
122        _sapp_x11_display,
123        selection,
124        _sapp_x11_window,
125        0 as libc::c_int as Time,
126    );
127
128    MESSAGE = Some(message);
129}
130
131/// this function is supposed to be called from sapp's event loop
132/// when XSelectionEvent received.
133/// It will parse event and call XSendEvent with event response
134pub(crate) unsafe fn respond_to_clipboard_request(event: *const XEvent) {
135    assert!((*event).type_0 == 30); // is it really SelectionRequest
136
137    let empty_message = String::new();
138    let message = MESSAGE.as_ref().unwrap_or(&empty_message);
139
140    let UTF8 = XInternAtom(
141        _sapp_x11_display,
142        b"UTF8_STRING\x00" as *const u8 as *const libc::c_char,
143        1 as libc::c_int,
144    );
145    let xselectionrequest = (*event).xselectionrequest;
146    let mut ev = XSelectionEvent {
147        type_0: crate::clipboard::SelectionNotify,
148        serial: 0,
149        send_event: 0,
150        display: xselectionrequest.display,
151        requestor: xselectionrequest.requestor,
152        selection: xselectionrequest.selection,
153        target: xselectionrequest.target,
154        property: xselectionrequest.property,
155        time: xselectionrequest.time,
156    };
157
158    // only UTF8 requests are supported
159    if xselectionrequest.target == UTF8 {
160        XChangeProperty(
161            xselectionrequest.display,
162            xselectionrequest.requestor,
163            xselectionrequest.property,
164            UTF8,
165            8 as libc::c_int,
166            PropModeReplace,
167            message.as_bytes().as_ptr() as *const u8 as *const _,
168            message.as_bytes().len() as _,
169        );
170
171        XSendEvent(
172            _sapp_x11_display,
173            ev.requestor,
174            0 as libc::c_int,
175            0 as libc::c_int as libc::c_long,
176            &mut ev as *mut XSelectionEvent as *mut XEvent,
177        );
178    } else {
179        // signal X that request is denied
180        ev.property = 0 as Atom;
181
182        XSendEvent(
183            _sapp_x11_display,
184            ev.requestor,
185            0 as libc::c_int,
186            0 as libc::c_int as libc::c_long,
187            &mut ev as *mut XSelectionEvent as *mut XEvent,
188        );
189    }
190}