#include #include #include #include #include #include #include #include struct selection { const char * name; uint32_t len; char * data; xcb_atom_t atom; }; int get_selection_index(const char *); xcb_window_t find_owner(int); void update_selection(int); xcb_atom_t get_atom(xcb_connection_t *, const char *); int clip_getattr(const char *, struct stat *, struct fuse_file_info *); int clip_readdir(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags); int clip_open(const char *, struct fuse_file_info *); int clip_read(const char *, char *, size_t, off_t, struct fuse_file_info *); const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; const uint32_t values[] = {1, XCB_EVENT_MASK_PROPERTY_CHANGE}; const struct fuse_operations clip_ops = { .getattr = clip_getattr, .readdir = clip_readdir, .open = clip_open, .read = clip_read, }; static xcb_connection_t * c; static xcb_screen_t * s; static xcb_window_t w; static struct selection x[] = { {.name = "primary", .len = 0, .data = NULL}, {.name = "secondary", .len = 0, .data = NULL}, {.name = "clipboard", .len = 0, .data = NULL}, }; int main(int argc, char ** argv) { c = xcb_connect(NULL, NULL); s = xcb_setup_roots_iterator(xcb_get_setup(c)).data; w = xcb_generate_id(c); xcb_create_window( c, XCB_COPY_FROM_PARENT, w, s->root, 0, 0, 1, 1, 0, XCB_COPY_FROM_PARENT, s->root_visual, mask, values); xcb_flush(c); x[0].atom = get_atom(c, "PRIMARY"); x[1].atom = get_atom(c, "SECONDARY"); x[2].atom = get_atom(c, "CLIPBOARD"); fuse_main(argc, argv, &clip_ops, NULL); xcb_disconnect(c); } int get_selection_index(const char * path) { for (int i = 0; i < 3; ++i) if (0 == strcmp(path + 1, x[i].name)) return i; return -ENOENT; } xcb_window_t find_owner(const int i) { xcb_get_selection_owner_cookie_t cookie = xcb_get_selection_owner(c, x[i].atom); xcb_get_selection_owner_reply_t * reply = xcb_get_selection_owner_reply(c, cookie, NULL); xcb_window_t owner = XCB_WINDOW_NONE; if (reply) owner = reply->owner; free(reply); return owner; } void update_selection(const int i) { xcb_atom_t TARGET_PROPERTY = get_atom(c, "_TARGET_SELECTION"); xcb_atom_t UTF8_STRING = get_atom(c, "UTF8_STRING"); // TODO: Look into ways to support various TARGETS. if (XCB_WINDOW_NONE == find_owner(i)) return; xcb_convert_selection(c, w, x[i].atom, UTF8_STRING, TARGET_PROPERTY, XCB_CURRENT_TIME); xcb_flush(c); xcb_generic_event_t * e; int done = 0; // TODO: Look into continuous notifications. while (!done) { e = xcb_wait_for_event(c); if (NULL == e) return; if (XCB_SELECTION_NOTIFY != XCB_EVENT_RESPONSE_TYPE(e)) { free(e); continue; } xcb_selection_notify_event_t * n = (xcb_selection_notify_event_t *) e; if (x[i].atom == n->selection && XCB_NONE != n->property) { xcb_get_property_cookie_t cookie = xcb_get_property( c, 1, w, TARGET_PROPERTY, XCB_ATOM_ANY, 0, UINT32_MAX); xcb_get_property_reply_t * reply = xcb_get_property_reply(c, cookie, NULL); if (reply) { const int len = xcb_get_property_value_length(reply); x[i].data = realloc(x[i].data, len); if (NULL == x[i].data) x[i].len = 0; x[i].len = len; memcpy(x[i].data, xcb_get_property_value(reply), len); done = 1; free(reply); } } free(e); } } xcb_atom_t get_atom(xcb_connection_t * c, const char * name) { xcb_intern_atom_cookie_t cookie = xcb_intern_atom(c, 0, strlen(name), name); xcb_intern_atom_reply_t * reply = xcb_intern_atom_reply(c, cookie, NULL); xcb_atom_t atom = reply->atom; free(reply); return atom; } int clip_getattr(const char * path, struct stat * st, struct fuse_file_info * fi) { (void) fi; memset(st, 0, sizeof(struct stat)); if (0 == strcmp(path, "/")) { st->st_mode = S_IFDIR | 0755; st->st_nlink = 2; } else { const int i = get_selection_index(path); if (-ENOENT == i) return -ENOENT; update_selection(i); // TODO: The content is updated way too often. st->st_mode = S_IFREG | 0444; st->st_nlink = 1; st->st_size = x[i].len; } return 0; } int clip_readdir( const char * path, void * buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info * fi, enum fuse_readdir_flags flags) { (void) offset; (void) fi; (void) flags; if (0 != strcmp(path, "/")) return -ENOENT; filler(buf, ".", NULL, 0, 0); filler(buf, "..", NULL, 0, 0); for (int i = 0; i < 3; ++i) filler(buf, x[i].name, NULL, 0, 0); return 0; } int clip_open(const char * path, struct fuse_file_info * fi) { if (-ENOENT == get_selection_index(path)) return -ENOENT; if (O_RDONLY != (fi->flags & O_ACCMODE)) return -EACCES; return 0; // TODO: Maybe queue selection content retrieval here? } int clip_read(const char * path, char * buf, size_t size, off_t offset, struct fuse_file_info * fi) { (void) fi; const int i = get_selection_index(path); if (-ENOENT == i) return -ENOENT; update_selection(i); // TODO: The content is updated way too often. if (offset < x[i].len) { if (offset + size > x[i].len) size = x[i].len - offset; memcpy(buf, x[i].data + offset, size); } else size = 0; return size; }