diff options
author | lambroslambrou@google.com <lambroslambrou@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-27 00:24:49 +0000 |
---|---|---|
committer | lambroslambrou@google.com <lambroslambrou@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-27 00:24:49 +0000 |
commit | 070ffb78150f235db8596104958722c19c6d1313 (patch) | |
tree | 30d4f6e72d37e78021e33da8c9e35eed251bd75a /remoting | |
parent | 4b0bcefe9ddfd0b205d64fc5bd6c5a31f8a05d36 (diff) | |
download | chromium_src-070ffb78150f235db8596104958722c19c6d1313.zip chromium_src-070ffb78150f235db8596104958722c19c6d1313.tar.gz chromium_src-070ffb78150f235db8596104958722c19c6d1313.tar.bz2 |
Implement clipboard for Chromoting Linux hosts.
BUG=132454
Review URL: https://chromiumcodereview.appspot.com/10909133
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@158944 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r-- | remoting/host/chromoting_host_context.cc | 12 | ||||
-rw-r--r-- | remoting/host/clipboard_linux.cc | 92 | ||||
-rw-r--r-- | remoting/host/event_executor_linux.cc | 29 | ||||
-rw-r--r-- | remoting/host/linux/x_server_clipboard.cc | 359 | ||||
-rw-r--r-- | remoting/host/linux/x_server_clipboard.h | 106 | ||||
-rw-r--r-- | remoting/remoting.gyp | 2 |
6 files changed, 586 insertions, 14 deletions
diff --git a/remoting/host/chromoting_host_context.cc b/remoting/host/chromoting_host_context.cc index 37ba9f3..87eeb1b 100644 --- a/remoting/host/chromoting_host_context.cc +++ b/remoting/host/chromoting_host_context.cc @@ -40,14 +40,12 @@ void ChromotingHostContext::ReleaseTaskRunners() { bool ChromotingHostContext::Start() { // Start all the threads. + base::Thread::Options io_thread_options(MessageLoop::TYPE_IO, 0); bool started = capture_thread_.Start() && encode_thread_.Start() && - audio_thread_.StartWithOptions(base::Thread::Options( - MessageLoop::TYPE_IO, 0)) && - network_thread_.StartWithOptions(base::Thread::Options( - MessageLoop::TYPE_IO, 0)) && - desktop_thread_.Start() && - file_thread_.StartWithOptions( - base::Thread::Options(MessageLoop::TYPE_IO, 0)); + audio_thread_.StartWithOptions(io_thread_options) && + network_thread_.StartWithOptions(io_thread_options) && + desktop_thread_.StartWithOptions(io_thread_options) && + file_thread_.StartWithOptions(io_thread_options); if (!started) return false; diff --git a/remoting/host/clipboard_linux.cc b/remoting/host/clipboard_linux.cc index 6a17291..1f74689 100644 --- a/remoting/host/clipboard_linux.cc +++ b/remoting/host/clipboard_linux.cc @@ -4,37 +4,119 @@ #include "remoting/host/clipboard.h" +#include <X11/Xlib.h> + +#include "base/bind.h" #include "base/logging.h" +#include "base/message_loop.h" +#include "remoting/host/linux/x_server_clipboard.h" +#include "remoting/proto/event.pb.h" +#include "remoting/protocol/clipboard_stub.h" namespace remoting { -class ClipboardLinux : public Clipboard { +// This code is expected to be called on the desktop thread only. +class ClipboardLinux : public Clipboard, + public MessageLoopForIO::Watcher { public: ClipboardLinux(); + virtual ~ClipboardLinux(); - // Must be called on the UI thread. + // Clipboard interface. virtual void Start( scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE; virtual void InjectClipboardEvent( const protocol::ClipboardEvent& event) OVERRIDE; virtual void Stop() OVERRIDE; + // MessageLoopForIO::Watcher interface. + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + private: + void OnClipboardChanged(const std::string& mime_type, + const std::string& data); + void PumpXEvents(); + + scoped_ptr<protocol::ClipboardStub> client_clipboard_; + + XServerClipboard x_server_clipboard_; + Display* display_; + + MessageLoopForIO::FileDescriptorWatcher x_connection_watcher_; + DISALLOW_COPY_AND_ASSIGN(ClipboardLinux); }; +ClipboardLinux::ClipboardLinux() + : display_(NULL) { +} + +ClipboardLinux::~ClipboardLinux() { + Stop(); +} + void ClipboardLinux::Start( scoped_ptr<protocol::ClipboardStub> client_clipboard) { - NOTIMPLEMENTED(); + // TODO(lambroslambrou): Share the X connection with EventExecutor. + display_ = XOpenDisplay(NULL); + if (!display_) { + LOG(ERROR) << "Couldn't open X display"; + return; + } + client_clipboard_.swap(client_clipboard); + + x_server_clipboard_.Init(display_, + base::Bind(&ClipboardLinux::OnClipboardChanged, + base::Unretained(this))); + + MessageLoopForIO::current()->WatchFileDescriptor( + ConnectionNumber(display_), true, MessageLoopForIO::WATCH_READ, + &x_connection_watcher_, this); + PumpXEvents(); } void ClipboardLinux::InjectClipboardEvent( const protocol::ClipboardEvent& event) { - NOTIMPLEMENTED(); + x_server_clipboard_.SetClipboard(event.mime_type(), event.data()); } void ClipboardLinux::Stop() { - NOTIMPLEMENTED(); + client_clipboard_.reset(); + x_connection_watcher_.StopWatchingFileDescriptor(); + + if (display_) { + XCloseDisplay(display_); + display_ = NULL; + } +} + +void ClipboardLinux::OnFileCanReadWithoutBlocking(int fd) { + PumpXEvents(); +} + +void ClipboardLinux::OnFileCanWriteWithoutBlocking(int fd) { +} + +void ClipboardLinux::OnClipboardChanged(const std::string& mime_type, + const std::string& data) { + protocol::ClipboardEvent event; + event.set_mime_type(mime_type); + event.set_data(data); + + if (client_clipboard_.get()) { + client_clipboard_->InjectClipboardEvent(event); + } +} + +void ClipboardLinux::PumpXEvents() { + DCHECK(display_); + + while (XPending(display_)) { + XEvent event; + XNextEvent(display_, &event); + x_server_clipboard_.ProcessXEvent(&event); + } } scoped_ptr<Clipboard> Clipboard::Create() { diff --git a/remoting/host/event_executor_linux.cc b/remoting/host/event_executor_linux.cc index 3ac3392..449a524 100644 --- a/remoting/host/event_executor_linux.cc +++ b/remoting/host/event_executor_linux.cc @@ -16,6 +16,7 @@ #include "base/location.h" #include "base/logging.h" #include "base/single_thread_task_runner.h" +#include "remoting/host/clipboard.h" #include "remoting/proto/internal.pb.h" #include "third_party/skia/include/core/SkPoint.h" @@ -59,6 +60,8 @@ class EventExecutorLinux : public EventExecutor { // Left, Right, Middle, VScroll Up/Down, HScroll Left/Right. static const int kNumPointerButtons = 7; + void InitClipboard(); + // |mode| is one of the AutoRepeatModeOn, AutoRepeatModeOff, // AutoRepeatModeDefault constants defined by the XChangeKeyboardControl() // API. @@ -83,6 +86,9 @@ class EventExecutorLinux : public EventExecutor { int test_error_base_; int pointer_button_map_[kNumPointerButtons]; + + scoped_ptr<Clipboard> clipboard_; + DISALLOW_COPY_AND_ASSIGN(EventExecutorLinux); }; @@ -92,6 +98,11 @@ EventExecutorLinux::EventExecutorLinux( latest_mouse_position_(SkIPoint::Make(-1, -1)), display_(XOpenDisplay(NULL)), root_window_(BadValue) { + if (!task_runner_->BelongsToCurrentThread()) { + task_runner_->PostTask( + FROM_HERE, + base::Bind(&EventExecutorLinux::InitClipboard, base::Unretained(this))); + } } EventExecutorLinux::~EventExecutorLinux() { @@ -120,7 +131,15 @@ bool EventExecutorLinux::Init() { } void EventExecutorLinux::InjectClipboardEvent(const ClipboardEvent& event) { - // TODO(simonmorris): Implement clipboard injection. + if (!task_runner_->BelongsToCurrentThread()) { + task_runner_->PostTask( + FROM_HERE, + base::Bind(&EventExecutorLinux::InjectClipboardEvent, + base::Unretained(this), event)); + return; + } + + clipboard_->InjectClipboardEvent(event); } void EventExecutorLinux::InjectKeyEvent(const KeyEvent& event) { @@ -170,6 +189,11 @@ void EventExecutorLinux::InjectKeyEvent(const KeyEvent& event) { XFlush(display_); } +void EventExecutorLinux::InitClipboard() { + DCHECK(task_runner_->BelongsToCurrentThread()); + clipboard_ = Clipboard::Create(); +} + void EventExecutorLinux::SetAutoRepeatForKey(int keycode, int mode) { XKeyboardControl control; control.key = keycode; @@ -365,8 +389,9 @@ void EventExecutorLinux::Start( base::Passed(&client_clipboard))); return; } + InitMouseButtonMap(); - return; + clipboard_->Start(client_clipboard.Pass()); } void EventExecutorLinux::StopAndDelete() { diff --git a/remoting/host/linux/x_server_clipboard.cc b/remoting/host/linux/x_server_clipboard.cc new file mode 100644 index 0000000..86bea19 --- /dev/null +++ b/remoting/host/linux/x_server_clipboard.cc @@ -0,0 +1,359 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/host/linux/x_server_clipboard.h" + +#include <X11/extensions/Xfixes.h> + +#include "base/callback.h" +#include "base/logging.h" +#include "remoting/base/constants.h" + +namespace remoting { + +XServerClipboard::XServerClipboard() + : display_(NULL), + clipboard_window_(BadValue), + xfixes_event_base_(-1), + xfixes_error_base_(-1), + clipboard_atom_(None), + large_selection_atom_(None), + selection_string_atom_(None), + targets_atom_(None), + timestamp_atom_(None), + utf8_string_atom_(None), + large_selection_property_(None) { +} + +XServerClipboard::~XServerClipboard() { +} + +void XServerClipboard::Init(Display* display, + const ClipboardChangedCallback& callback) { + display_ = display; + callback_ = callback; + + // If any of these X API calls fail, an X Error will be raised, crashing the + // process. This is unlikely to occur in practice, and even if it does, it + // would mean the X server is in a bad state, so it's not worth trying to + // trap such errors here. + + // TODO(lambroslambrou): Consider using ScopedXErrorHandler here, or consider + // placing responsibility for handling X Errors outside this class, since + // X Error handlers are global to all X connections. + if (!XFixesQueryExtension(display_, &xfixes_event_base_, + &xfixes_error_base_)) { + LOG(INFO) << "X server does not support XFixes."; + return; + } + + clipboard_window_ = XCreateSimpleWindow(display_, + DefaultRootWindow(display_), + 0, 0, 1, 1, // x, y, width, height + 0, 0, 0); + + XFixesSelectSelectionInput(display_, clipboard_window_, XA_PRIMARY, + XFixesSetSelectionOwnerNotifyMask); + XFixesSelectSelectionInput(display_, clipboard_window_, clipboard_atom_, + XFixesSetSelectionOwnerNotifyMask); + + // TODO(lambroslambrou): Use ui::X11AtomCache for this, either by adding a + // dependency on ui/ or by moving X11AtomCache to base/. + const int kNumAtoms = 6; + const char* names[kNumAtoms] = { + "CLIPBOARD", + "INCR", + "SELECTION_STRING", + "TARGETS", + "TIMESTAMP", + "UTF8_STRING" }; + Atom atoms[kNumAtoms]; + if (XInternAtoms(display_, const_cast<char**>(names), kNumAtoms, False, + atoms)) { + clipboard_atom_ = atoms[0]; + large_selection_atom_ = atoms[1]; + selection_string_atom_ = atoms[2]; + targets_atom_ = atoms[3]; + timestamp_atom_ = atoms[4]; + utf8_string_atom_ = atoms[5]; + } else { + LOG(ERROR) << "XInternAtoms failed"; + } +} + +void XServerClipboard::SetClipboard(const std::string& mime_type, + const std::string& data) { + DCHECK(display_); + + if (clipboard_window_ == BadValue) { + return; + } + + // Currently only UTF-8 is supported. + if (mime_type != kMimeTypeTextUtf8) { + return; + } + + data_ = data; + + AssertSelectionOwnership(XA_PRIMARY); + AssertSelectionOwnership(clipboard_atom_); +} + +void XServerClipboard::ProcessXEvent(XEvent* event) { + if (clipboard_window_ == BadValue || + event->xany.window != clipboard_window_) { + return; + } + + switch (event->type) { + case PropertyNotify: + OnPropertyNotify(event); + break; + case SelectionNotify: + OnSelectionNotify(event); + break; + case SelectionRequest: + OnSelectionRequest(event); + break; + default: + break; + } + + if (event->type == xfixes_event_base_ + XFixesSetSelectionOwnerNotify) { + XFixesSelectionNotifyEvent* notify_event = + reinterpret_cast<XFixesSelectionNotifyEvent*>(event); + OnSetSelectionOwnerNotify(notify_event->selection, + notify_event->selection_timestamp); + } +} + +void XServerClipboard::OnSetSelectionOwnerNotify(Atom selection, + Time timestamp) { + // Protect against receiving new XFixes selection notifications whilst we're + // in the middle of waiting for information from the current selection owner. + // A reasonable timeout allows for misbehaving apps that don't respond + // quickly to our requests. + if (!get_selections_time_.is_null() && + (base::TimeTicks::Now() - get_selections_time_) < + base::TimeDelta::FromSeconds(5)) { + // TODO(lambroslambrou): Instead of ignoring this notification, cancel any + // pending request operations and ignore the resulting events, before + // dispatching new requests here. + return; + } + + if (selection != clipboard_atom_ && selection != XA_PRIMARY) { + // Only process PRIMARY and CLIPBOARD selections. + return; + } + + // If we own the selection, don't request details for it. + if (IsSelectionOwner(selection)) { + return; + } + + get_selections_time_ = base::TimeTicks::Now(); + + // Before getting the value of the chosen selection, request the list of + // target formats it supports. + RequestSelectionTargets(selection); +} + +void XServerClipboard::OnPropertyNotify(XEvent* event) { + if (large_selection_property_ != None && + event->xproperty.atom == large_selection_property_ && + event->xproperty.state == PropertyNewValue) { + Atom type; + int format; + unsigned long item_count, after; + unsigned char *data; + XGetWindowProperty(display_, clipboard_window_, large_selection_property_, + 0, ~0L, True, AnyPropertyType, &type, &format, + &item_count, &after, &data); + if (type != None) { + // TODO(lambroslambrou): Properly support large transfers - + // http://crbug.com/151447. + XFree(data); + + // If the property is zero-length then the large transfer is complete. + if (item_count == 0) { + large_selection_property_ = None; + } + } + } +} + +void XServerClipboard::OnSelectionNotify(XEvent* event) { + if (event->xselection.property != None) { + Atom type; + int format; + unsigned long item_count, after; + unsigned char *data; + XGetWindowProperty(display_, clipboard_window_, + event->xselection.property, 0, ~0L, True, + AnyPropertyType, &type, &format, + &item_count, &after, &data); + if (type == large_selection_atom_) { + // Large selection - just read and ignore these for now. + large_selection_property_ = event->xselection.property; + } else { + // Standard selection - call the selection notifier. + large_selection_property_ = None; + if (type != None) { + HandleSelectionNotify(&event->xselection, type, format, item_count, + data); + XFree(data); + return; + } + } + } + HandleSelectionNotify(&event->xselection, 0, 0, 0, 0); +} + +void XServerClipboard::OnSelectionRequest(XEvent* event) { + XSelectionEvent selection_event; + selection_event.type = SelectionNotify; + selection_event.display = event->xselectionrequest.display; + selection_event.requestor = event->xselectionrequest.requestor; + selection_event.selection = event->xselectionrequest.selection; + selection_event.time = event->xselectionrequest.time; + selection_event.target = event->xselectionrequest.target; + if (event->xselectionrequest.property == None) { + event->xselectionrequest.property = event->xselectionrequest.target; + } + if (!IsSelectionOwner(selection_event.selection)) { + selection_event.property = None; + } else { + selection_event.property = event->xselectionrequest.property; + if (selection_event.target == targets_atom_) { + // Respond advertising XA_STRING, UTF8_STRING and TIMESTAMP data for the + // selection. + Atom targets[3]; + targets[0] = timestamp_atom_; + targets[1] = utf8_string_atom_; + targets[2] = XA_STRING; + XChangeProperty(display_, selection_event.requestor, + selection_event.property, XA_ATOM, 32, PropModeReplace, + reinterpret_cast<unsigned char*>(targets), 3); + } else if (selection_event.target == timestamp_atom_) { + // Respond with the timestamp of our selection; we always return + // CurrentTime since our selections are set by remote clients, so there + // is no associated local X event. + + // TODO(lambroslambrou): Should use a proper timestamp here instead of + // CurrentTime. ICCCM recommends doing a zero-length property append, + // and getting a timestamp from the subsequent PropertyNotify event. + Time time = CurrentTime; + XChangeProperty(display_, selection_event.requestor, + selection_event.property, XA_INTEGER, 32, + PropModeReplace, reinterpret_cast<unsigned char*>(&time), + 1); + } else if (selection_event.target == utf8_string_atom_ || + selection_event.target == XA_STRING) { + if (!data_.empty()) { + // Return the actual string data; we always return UTF8, regardless of + // the configured locale. + XChangeProperty(display_, selection_event.requestor, + selection_event.property, selection_event.target, 8, + PropModeReplace, + reinterpret_cast<unsigned char*>( + const_cast<char*>(data_.data())), + data_.size()); + } + } + } + XSendEvent(display_, selection_event.requestor, False, 0, + reinterpret_cast<XEvent*>(&selection_event)); +} + +void XServerClipboard::HandleSelectionNotify(XSelectionEvent* event, + Atom type, + int format, + int item_count, + void* data) { + bool finished = false; + + if (event->target == targets_atom_) { + finished = HandleSelectionTargetsEvent(event, format, item_count, data); + } else if (event->target == utf8_string_atom_ || + event->target == XA_STRING) { + finished = HandleSelectionStringEvent(event, format, item_count, data); + } + + if (finished) { + get_selections_time_ = base::TimeTicks(); + } +} + +bool XServerClipboard::HandleSelectionTargetsEvent(XSelectionEvent* event, + int format, + int item_count, + void* data) { + if (event->property == targets_atom_) { + if (data && format == 32) { + // The XGetWindowProperty man-page specifies that the returned + // property data will be an array of |long|s in the case where + // |format| == 32. Although the items are 32-bit values (as stored and + // sent over the X protocol), Xlib presents the data to the client as an + // array of |long|s, with zero-padding on a 64-bit system where |long| + // is bigger than 32 bits. + const long* targets = static_cast<const long*>(data); + for (int i = 0; i < item_count; i++) { + if (targets[i] == static_cast<long>(utf8_string_atom_)) { + RequestSelectionString(event->selection, utf8_string_atom_); + return false; + } + } + } + } + RequestSelectionString(event->selection, XA_STRING); + return false; +} + +bool XServerClipboard::HandleSelectionStringEvent(XSelectionEvent* event, + int format, + int item_count, + void* data) { + if (event->property != selection_string_atom_ || !data || format != 8) { + return true; + } + + std::string text(static_cast<char*>(data), item_count); + + if (event->target == XA_STRING || event->target == utf8_string_atom_) { + NotifyClipboardText(text); + } + return true; +} + +void XServerClipboard::NotifyClipboardText(const std::string& text) { + data_ = text; + callback_.Run(kMimeTypeTextUtf8, data_); +} + +void XServerClipboard::RequestSelectionTargets(Atom selection) { + XConvertSelection(display_, selection, targets_atom_, targets_atom_, + clipboard_window_, CurrentTime); +} + +void XServerClipboard::RequestSelectionString(Atom selection, Atom target) { + XConvertSelection(display_, selection, target, selection_string_atom_, + clipboard_window_, CurrentTime); +} + +void XServerClipboard::AssertSelectionOwnership(Atom selection) { + XSetSelectionOwner(display_, selection, clipboard_window_, CurrentTime); + if (XGetSelectionOwner(display_, selection) == clipboard_window_) { + selections_owned_.insert(selection); + } else { + LOG(ERROR) << "XSetSelectionOwner failed for selection " << selection; + } +} + +bool XServerClipboard::IsSelectionOwner(Atom selection) { + return selections_owned_.find(selection) != selections_owned_.end(); +} + +} // namespace remoting diff --git a/remoting/host/linux/x_server_clipboard.h b/remoting/host/linux/x_server_clipboard.h new file mode 100644 index 0000000..f173351 --- /dev/null +++ b/remoting/host/linux/x_server_clipboard.h @@ -0,0 +1,106 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Don't include this file from any .h files because it pulls in some X headers. + +#ifndef REMOTING_HOST_LINUX_X_SERVER_CLIPBOARD_H_ +#define REMOTING_HOST_LINUX_X_SERVER_CLIPBOARD_H_ + +#include <X11/Xatom.h> +#include <X11/Xlib.h> + +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/timer.h" + +namespace remoting { + +// A class to allow manipulation of the X clipboard, using only X API calls. +// This class is not thread-safe, so all its methods must be called on the +// application's main event-processing thread. +class XServerClipboard { + public: + typedef base::Callback<void(const std::string& mime_type, + const std::string& data)> + ClipboardChangedCallback; + + XServerClipboard(); + ~XServerClipboard(); + + // Start monitoring |display|'s selections, and invoke |callback| whenever + // their content changes. The caller must ensure |display| is still valid + // whenever any other methods are called on this object. + void Init(Display* display, const ClipboardChangedCallback& callback); + + // Copy data to the X Clipboard. This acquires ownership of the + // PRIMARY and CLIPBOARD selections. + void SetClipboard(const std::string& mime_type, const std::string& data); + + // Process |event| if it is an X selection notification. The caller should + // invoke this for every event it receives from |display|. + void ProcessXEvent(XEvent* event); + + private: + // Handlers for X selection events. + void OnSetSelectionOwnerNotify(Atom selection, Time timestamp); + void OnPropertyNotify(XEvent* event); + void OnSelectionNotify(XEvent* event); + void OnSelectionRequest(XEvent* event); + + // Called when the selection owner has replied to a request for information + // about a selection. + void HandleSelectionNotify(XSelectionEvent* event, + Atom type, + int format, + int item_count, + void* data); + + // These methods return true if selection processing is complete, false + // otherwise. + bool HandleSelectionTargetsEvent(XSelectionEvent* event, + int format, + int item_count, + void* data); + bool HandleSelectionStringEvent(XSelectionEvent* event, + int format, + int item_count, + void* data); + + // Notify the registered callback of new clipboard text. + void NotifyClipboardText(const std::string& text); + + // These methods trigger the X server or selection owner to send back an + // event containing the requested information. + void RequestSelectionTargets(Atom selection); + void RequestSelectionString(Atom selection, Atom target); + + // Assert ownership of the specified |selection|. + void AssertSelectionOwnership(Atom selection); + bool IsSelectionOwner(Atom selection); + + Display* display_; + Window clipboard_window_; + int xfixes_event_base_; + int xfixes_error_base_; + Atom clipboard_atom_; + Atom large_selection_atom_; + Atom selection_string_atom_; + Atom targets_atom_; + Atom timestamp_atom_; + Atom utf8_string_atom_; + std::set<Atom> selections_owned_; + std::string data_; + Atom large_selection_property_; + base::TimeTicks get_selections_time_; + ClipboardChangedCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(XServerClipboard); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_LINUX_X_SERVER_CLIPBOARD_H_ diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index a448657..5ff1b86 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -1349,6 +1349,8 @@ 'host/it2me_host_user_interface.h', 'host/json_host_config.cc', 'host/json_host_config.h', + 'host/linux/x_server_clipboard.cc', + 'host/linux/x_server_clipboard.h', 'host/local_input_monitor.h', 'host/local_input_monitor_linux.cc', 'host/local_input_monitor_mac.mm', |