summaryrefslogtreecommitdiff
path: root/chromium/ui/base/x/x11_drag_drop_client.cc
blob: c96e99425d5c3c21341d932044ec1ee4b42cbc30 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
// Copyright 2019 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 "ui/base/x/x11_drag_drop_client.h"

#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "ui/base/clipboard/clipboard_constants.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/x/x11_os_exchange_data_provider.h"
#include "ui/base/x/x11_util.h"
#include "ui/gfx/x/connection.h"
#include "ui/gfx/x/x11_atom_cache.h"
#include "ui/gfx/x/xproto.h"
#include "ui/gfx/x/xproto_util.h"

// Reading recommended for understanding the implementation in this file:
//
// * The X Window System Concepts section in The X New Developer’s Guide
// * The X Selection Mechanism paper by Keith Packard
// * The Peer-to-Peer Communication by Means of Selections section in the
//   ICCCM (X Consortium's Inter-Client Communication Conventions Manual)
// * The XDND specification, Drag-and-Drop Protocol for the X Window System
// * The XDS specification, The Direct Save Protocol for the X Window System
//
// All the readings are freely available online.

namespace ui {
namespace {

using mojom::DragOperation;

constexpr int kWillAcceptDrop = 1;
constexpr int kWantFurtherPosEvents = 2;

// The lowest XDND protocol version that we understand.
//
// The XDND protocol specification says that we must support all versions
// between 3 and the version we advertise in the XDndAware property.
constexpr int kMinXdndVersion = 3;

// The value used in the XdndAware property.
//
// The XDND protocol version used between two windows will be the minimum
// between the two versions advertised in the XDndAware property.
constexpr int kMaxXdndVersion = 5;

// Window property that tells other applications the window understands XDND.
const char kXdndAware[] = "XdndAware";

// Window property that holds the supported drag and drop data types.
// This property is set on the XDND source window when the drag and drop data
// can be converted to more than 3 types.
const char kXdndTypeList[] = "XdndTypeList";

// These actions have the same meaning as in the W3C Drag and Drop spec.
const char kXdndActionCopy[] = "XdndActionCopy";
const char kXdndActionMove[] = "XdndActionMove";
const char kXdndActionLink[] = "XdndActionLink";

// Triggers the XDS protocol.
const char kXdndActionDirectSave[] = "XdndActionDirectSave";

// Window property that contains the possible actions that will be presented to
// the user when the drag and drop action is kXdndActionAsk.
const char kXdndActionList[] = "XdndActionList";

// Window property on the source window and message used by the XDS protocol.
// This atom name intentionally includes the XDS protocol version (0).
// After the source sends the XdndDrop message, this property stores the
// (path-less) name of the file to be saved, and has the type text/plain, with
// an optional charset attribute.
// When receiving an XdndDrop event, the target needs to check for the
// XdndDirectSave property on the source window. The target then modifies the
// XdndDirectSave on the source window, and sends an XdndDirectSave message to
// the source.
// After the target sends the XdndDirectSave message, this property stores an
// URL indicating the location where the source should save the file.
const char kXdndDirectSave0[] = "XdndDirectSave0";

// Window property pointing to a proxy window to receive XDND target messages.
// The XDND source must check the proxy window must for the XdndAware property,
// and must send all XDND messages to the proxy instead of the target. However,
// the target field in the messages must still represent the original target
// window (the window pointed to by the cursor).
const char kXdndProxy[] = "XdndProxy";

// Message sent from an XDND source to the target when the user confirms the
// drag and drop operation.
const char kXdndDrop[] = "XdndDrop";

// Message sent from an XDND source to the target to start the XDND protocol.
// The target must wait for an XDndPosition event before querying the data.
const char kXdndEnter[] = "XdndEnter";

// Message sent from an XDND target to the source in response to an XdndDrop.
// The message must be sent whether the target acceepts the drop or not.
const char kXdndFinished[] = "XdndFinished";

// Message sent from an XDND source to the target when the user cancels the drag
// and drop operation.
const char kXdndLeave[] = "XdndLeave";

// Message sent by the XDND source when the cursor position changes.
// The source will also send an XdndPosition event right after the XdndEnter
// event, to tell the target about the initial cursor position and the desired
// drop action.
// The time stamp in the XdndPosition must be used when requesting selection
// information.
// After the target optionally acquires selection information, it must tell the
// source if it can accept the drop via an XdndStatus message.
const char kXdndPosition[] = "XdndPosition";

// Message sent by the XDND target in response to an XdndPosition message.
// The message informs the source if the target will accept the drop, and what
// action will be taken if the drop is accepted.
const char kXdndStatus[] = "XdndStatus";

static base::LazyInstance<std::map<x11::Window, XDragDropClient*>>::Leaky
    g_live_client_map = LAZY_INSTANCE_INITIALIZER;

x11::Atom DragOperationToAtom(DragOperation operation) {
  switch (operation) {
    case DragOperation::kNone:
      return x11::Atom::None;
    case DragOperation::kCopy:
      return x11::GetAtom(kXdndActionCopy);
    case DragOperation::kMove:
      return x11::GetAtom(kXdndActionMove);
    case DragOperation::kLink:
      return x11::GetAtom(kXdndActionLink);
  }
  NOTREACHED();
  return x11::Atom::None;
}

DragOperation AtomToDragOperation(x11::Atom atom) {
  if (atom == x11::GetAtom(kXdndActionCopy))
    return DragOperation::kCopy;
  if (atom == x11::GetAtom(kXdndActionMove))
    return DragOperation::kMove;
  if (atom == x11::GetAtom(kXdndActionLink))
    return DragOperation::kLink;
  return DragOperation::kNone;
}

}  // namespace

int XGetMaskAsEventFlags() {
  x11::KeyButMask mask{};
  auto* connection = x11::Connection::Get();
  if (auto reply =
          connection->QueryPointer({connection->default_root()}).Sync()) {
    mask = reply->mask;
  }

  int modifiers = ui::EF_NONE;
  if (static_cast<bool>(mask & x11::KeyButMask::Shift))
    modifiers |= ui::EF_SHIFT_DOWN;
  if (static_cast<bool>(mask & x11::KeyButMask::Control))
    modifiers |= ui::EF_CONTROL_DOWN;
  if (static_cast<bool>(mask & x11::KeyButMask::Mod1))
    modifiers |= ui::EF_ALT_DOWN;
  if (static_cast<bool>(mask & x11::KeyButMask::Mod4))
    modifiers |= ui::EF_COMMAND_DOWN;
  if (static_cast<bool>(mask & x11::KeyButMask::Button1))
    modifiers |= ui::EF_LEFT_MOUSE_BUTTON;
  if (static_cast<bool>(mask & x11::KeyButMask::Button2))
    modifiers |= ui::EF_MIDDLE_MOUSE_BUTTON;
  if (static_cast<bool>(mask & x11::KeyButMask::Button3))
    modifiers |= ui::EF_RIGHT_MOUSE_BUTTON;
  return modifiers;
}

// static
XDragDropClient* XDragDropClient::GetForWindow(x11::Window window) {
  std::map<x11::Window, XDragDropClient*>::const_iterator it =
      g_live_client_map.Get().find(window);
  if (it == g_live_client_map.Get().end())
    return nullptr;
  return it->second;
}

XDragDropClient::XDragDropClient(XDragDropClient::Delegate* delegate,
                                 x11::Window xwindow)
    : delegate_(delegate), xwindow_(xwindow) {
  DCHECK(delegate_);

  // Mark that we are aware of drag and drop concepts.
  uint32_t xdnd_version = kMaxXdndVersion;
  SetProperty(xwindow_, x11::GetAtom(kXdndAware), x11::Atom::ATOM,
              xdnd_version);

  // Some tests change the DesktopDragDropClientAuraX11 associated with an
  // |xwindow|.
  g_live_client_map.Get()[xwindow] = this;
}

XDragDropClient::~XDragDropClient() {
  g_live_client_map.Get().erase(xwindow());
}

std::vector<x11::Atom> XDragDropClient::GetOfferedDragOperations() const {
  std::vector<x11::Atom> operations;
  if (allowed_operations_ & DragDropTypes::DRAG_COPY)
    operations.push_back(x11::GetAtom(kXdndActionCopy));
  if (allowed_operations_ & DragDropTypes::DRAG_MOVE)
    operations.push_back(x11::GetAtom(kXdndActionMove));
  if (allowed_operations_ & DragDropTypes::DRAG_LINK)
    operations.push_back(x11::GetAtom(kXdndActionLink));
  return operations;
}

void XDragDropClient::CompleteXdndPosition(x11::Window source_window,
                                           const gfx::Point& screen_point) {
  DragOperation drag_operation =
      PreferredDragOperation(delegate_->UpdateDrag(screen_point));

  // Sends an XdndStatus message back to the source_window. l[2,3]
  // theoretically represent an area in the window where the current action is
  // the same as what we're returning, but I can't find any implementation that
  // actually making use of this. A client can return (0, 0) and/or set the
  // first bit of l[1] to disable the feature, and it appears that gtk neither
  // sets this nor respects it if set.
  auto xev = PrepareXdndClientMessage(kXdndStatus, source_window);
  xev.data.data32[1] = (drag_operation != DragOperation::kNone)
                           ? (kWantFurtherPosEvents | kWillAcceptDrop)
                           : 0;
  xev.data.data32[4] =
      static_cast<uint32_t>(DragOperationToAtom(drag_operation));
  SendXClientEvent(source_window, xev);
}

void XDragDropClient::ProcessMouseMove(const gfx::Point& screen_point,
                                       unsigned long event_time) {
  if (source_state_ != SourceState::kOther)
    return;

  // Find the current window the cursor is over.
  x11::Window dest_window = FindWindowFor(screen_point);

  if (target_current_window_ != dest_window) {
    if (target_current_window_ != x11::Window::None)
      SendXdndLeave(target_current_window_);

    target_current_window_ = dest_window;
    waiting_on_status_ = false;
    next_position_message_.reset();
    status_received_since_enter_ = false;
    negotiated_operation_ = DragOperation::kNone;

    if (target_current_window_ != x11::Window::None) {
      std::vector<x11::Atom> targets;
      source_provider_->RetrieveTargets(&targets);
      SendXdndEnter(target_current_window_, targets);
    }
  }

  if (target_current_window_ != x11::Window::None) {
    if (waiting_on_status_) {
      next_position_message_ =
          std::make_unique<std::pair<gfx::Point, unsigned long>>(screen_point,
                                                                 event_time);
    } else {
      SendXdndPosition(dest_window, screen_point, event_time);
    }
  }
}

bool XDragDropClient::HandleXdndEvent(const x11::ClientMessageEvent& event) {
  x11::Atom message_type = event.type;
  if (message_type == x11::GetAtom("XdndEnter"))
    OnXdndEnter(event);
  else if (message_type == x11::GetAtom("XdndLeave"))
    OnXdndLeave(event);
  else if (message_type == x11::GetAtom("XdndPosition"))
    OnXdndPosition(event);
  else if (message_type == x11::GetAtom("XdndStatus"))
    OnXdndStatus(event);
  else if (message_type == x11::GetAtom("XdndFinished"))
    OnXdndFinished(event);
  else if (message_type == x11::GetAtom("XdndDrop"))
    OnXdndDrop(event);
  else
    return false;
  return true;
}

void XDragDropClient::OnXdndEnter(const x11::ClientMessageEvent& event) {
  DVLOG(1) << "OnXdndEnter, version "
           << ((event.data.data32[1] & 0xff000000) >> 24);

  int version = (event.data.data32[1] & 0xff000000) >> 24;
  if (version < kMinXdndVersion) {
    // This protocol version is not documented in the XDND standard (last
    // revised in 1999), so we don't support it. Since don't understand the
    // protocol spoken by the source, we can't tell it that we can't talk to it.
    LOG(ERROR) << "XdndEnter message discarded because its version is too old.";
    return;
  }
  if (version > kMaxXdndVersion) {
    // The XDND version used should be the minimum between the versions
    // advertised by the source and the target. We advertise kMaxXdndVersion, so
    // this should never happen when talking to an XDND-compliant application.
    LOG(ERROR) << "XdndEnter message discarded because its version is too new.";
    return;
  }

  // Make sure that we've run ~X11DragContext() before creating another one.
  ResetDragContext();
  auto* source_client =
      GetForWindow(static_cast<x11::Window>(event.data.data32[0]));
  DCHECK(!source_client || source_client->source_provider_);
  target_current_context_ = std::make_unique<XDragContext>(
      xwindow_, event,
      (source_client ? source_client->source_provider_->GetFormatMap()
                     : SelectionFormatMap()));

  if (!source_client) {
    // The window doesn't have a DesktopDragDropClientAuraX11, which means it's
    // created by some other process.  Listen for messages on it.
    delegate_->OnBeginForeignDrag(
        static_cast<x11::Window>(event.data.data32[0]));
  }

  // In the Windows implementation, we immediately call DesktopDropTargetWin::
  // Translate().  The XDND specification demands that we wait until we receive
  // an XdndPosition message before we use XConvertSelection or send an
  // XdndStatus message.
}

void XDragDropClient::OnXdndPosition(const x11::ClientMessageEvent& event) {
  DVLOG(1) << "OnXdndPosition";

  auto source_window = static_cast<x11::Window>(event.data.data32[0]);
  int x_root_window = event.data.data32[2] >> 16;
  int y_root_window = event.data.data32[2] & 0xffff;
  x11::Time time_stamp = static_cast<x11::Time>(event.data.data32[3]);
  x11::Atom suggested_action = static_cast<x11::Atom>(event.data.data32[4]);

  if (!target_current_context()) {
    NOTREACHED();
    return;
  }

  target_current_context()->OnXdndPositionMessage(
      this, suggested_action, source_window, time_stamp,
      gfx::Point(x_root_window, y_root_window));
}

void XDragDropClient::OnXdndStatus(const x11::ClientMessageEvent& event) {
  DVLOG(1) << "OnXdndStatus";

  auto source_window = static_cast<x11::Window>(event.data.data32[0]);

  if (source_window != target_current_window_)
    return;

  if (source_state_ != SourceState::kPendingDrop &&
      source_state_ != SourceState::kOther) {
    return;
  }

  waiting_on_status_ = false;
  status_received_since_enter_ = true;

  if (event.data.data32[1] & 1) {
    x11::Atom atom_operation = static_cast<x11::Atom>(event.data.data32[4]);
    negotiated_operation_ = AtomToDragOperation(atom_operation);
  } else {
    negotiated_operation_ = DragOperation::kNone;
  }

  if (source_state_ == SourceState::kPendingDrop) {
    // We were waiting on the status message so we could send the XdndDrop.
    if (negotiated_operation_ == DragOperation::kNone) {
      EndMoveLoop();
      return;
    }
    source_state_ = SourceState::kDropped;
    SendXdndDrop(source_window);
    return;
  }

  delegate_->UpdateCursor(negotiated_operation_);

  // Note: event.data.[2,3] specify a rectangle. It is a request by the other
  // window to not send further XdndPosition messages while the cursor is
  // within it. However, it is considered advisory and (at least according to
  // the spec) the other side must handle further position messages within
  // it. GTK+ doesn't bother with this, so neither should we.

  if (next_position_message_.get()) {
    // We were waiting on the status message so we could send off the next
    // position message we queued up.
    gfx::Point p = next_position_message_->first;
    unsigned long event_time = next_position_message_->second;
    next_position_message_.reset();

    SendXdndPosition(source_window, p, event_time);
  }
}

void XDragDropClient::OnXdndLeave(const x11::ClientMessageEvent& event) {
  DVLOG(1) << "OnXdndLeave";
  delegate_->OnBeforeDragLeave();
  ResetDragContext();
}

void XDragDropClient::OnXdndDrop(const x11::ClientMessageEvent& event) {
  DVLOG(1) << "OnXdndDrop";

  auto source_window = static_cast<x11::Window>(event.data.data32[0]);

  DragOperation drag_operation = delegate_->PerformDrop();

  auto xev = PrepareXdndClientMessage(kXdndFinished, source_window);
  xev.data.data32[1] = (drag_operation != DragOperation::kNone) ? 1 : 0;
  xev.data.data32[2] =
      static_cast<uint32_t>(DragOperationToAtom(drag_operation));
  SendXClientEvent(source_window, xev);
}

void XDragDropClient::OnXdndFinished(const x11::ClientMessageEvent& event) {
  DVLOG(1) << "OnXdndFinished";
  auto source_window = static_cast<x11::Window>(event.data.data32[0]);
  if (target_current_window_ != source_window)
    return;

  // Clear |negotiated_operation_| if the drag was rejected.
  if ((event.data.data32[1] & 1) == 0)
    negotiated_operation_ = DragOperation::kNone;

  // Clear |target_current_window_| to avoid sending XdndLeave upon ending the
  // move loop.
  target_current_window_ = x11::Window::None;
  EndMoveLoop();
}

void XDragDropClient::OnSelectionNotify(
    const x11::SelectionNotifyEvent& xselection) {
  DVLOG(1) << "OnSelectionNotify";
  if (target_current_context_)
    target_current_context_->OnSelectionNotify(xselection);

  // ICCCM requires us to delete the property passed into SelectionNotify.
  if (xselection.property != x11::Atom::None)
    x11::DeleteProperty(xwindow_, xselection.property);
}

void XDragDropClient::InitDrag(int allowed_operations,
                               const OSExchangeData* data) {
  target_current_window_ = x11::Window::None;
  source_state_ = SourceState::kOther;
  waiting_on_status_ = false;
  next_position_message_.reset();
  status_received_since_enter_ = false;
  allowed_operations_ = allowed_operations;
  negotiated_operation_ = DragOperation::kNone;

  source_provider_ =
      static_cast<const XOSExchangeDataProvider*>(&data->provider());
  source_provider_->TakeOwnershipOfSelection();

  std::vector<x11::Atom> actions = GetOfferedDragOperations();
  if (!source_provider_->file_contents_name().empty()) {
    actions.push_back(x11::GetAtom(kXdndActionDirectSave));
    SetStringProperty(xwindow_, x11::GetAtom(kXdndDirectSave0),
                      x11::GetAtom(kMimeTypeText),
                      source_provider_->file_contents_name().AsUTF8Unsafe());
  }
  SetArrayProperty(xwindow_, x11::GetAtom(kXdndActionList), x11::Atom::ATOM,
                   actions);
}

void XDragDropClient::CleanupDrag() {
  source_provider_ = nullptr;
  x11::DeleteProperty(xwindow_, x11::GetAtom(kXdndActionList));
  x11::DeleteProperty(xwindow_, x11::GetAtom(kXdndDirectSave0));
}

void XDragDropClient::UpdateModifierState(int flags) {
  const int kModifiers = EF_SHIFT_DOWN | EF_CONTROL_DOWN | EF_ALT_DOWN |
                         EF_COMMAND_DOWN | EF_LEFT_MOUSE_BUTTON |
                         EF_MIDDLE_MOUSE_BUTTON | EF_RIGHT_MOUSE_BUTTON;
  current_modifier_state_ = flags & kModifiers;
}

void XDragDropClient::ResetDragContext() {
  if (!target_current_context())
    return;
  XDragDropClient* source_client =
      GetForWindow(target_current_context()->source_window());
  if (!source_client)
    delegate_->OnEndForeignDrag();

  target_current_context_.reset();
}

void XDragDropClient::StopRepeatMouseMoveTimer() {
  repeat_mouse_move_timer_.Stop();
}

void XDragDropClient::StartEndMoveLoopTimer() {
  end_move_loop_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(1000),
                             this, &XDragDropClient::EndMoveLoop);
}

void XDragDropClient::StopEndMoveLoopTimer() {
  end_move_loop_timer_.Stop();
}

void XDragDropClient::HandleMouseMovement(const gfx::Point& screen_point,
                                          int flags,
                                          base::TimeTicks event_time) {
  UpdateModifierState(flags);
  StopRepeatMouseMoveTimer();
  ProcessMouseMove(screen_point,
                   (event_time - base::TimeTicks()).InMilliseconds());
}

void XDragDropClient::HandleMouseReleased() {
  StopRepeatMouseMoveTimer();

  if (source_state_ != SourceState::kOther) {
    // The user has previously released the mouse and is clicking in
    // frustration.
    EndMoveLoop();
    return;
  }

  if (target_current_window_ != x11::Window::None) {
    if (waiting_on_status_) {
      if (status_received_since_enter_) {
        // If we are waiting for an XdndStatus message, we need to wait for it
        // to complete.
        source_state_ = SourceState::kPendingDrop;

        // Start timer to end the move loop if the target takes too long to send
        // the XdndStatus and XdndFinished messages.
        StartEndMoveLoopTimer();
        return;
      }

      EndMoveLoop();
      return;
    }

    if (negotiated_operation() != DragOperation::kNone) {
      // Start timer to end the move loop if the target takes too long to send
      // an XdndFinished message. It is important that StartEndMoveLoopTimer()
      // is called before SendXdndDrop() because SendXdndDrop()
      // sends XdndFinished synchronously if the drop target is a Chrome
      // window.
      StartEndMoveLoopTimer();

      // We have negotiated an action with the other end.
      source_state_ = SourceState::kDropped;
      SendXdndDrop(target_current_window_);
      return;
    } else {
      // No transfer is negotiated.  We need to tell the target window that we
      // are leaving.
      SendXdndLeave(target_current_window_);
    }
  }

  EndMoveLoop();
}

void XDragDropClient::HandleMoveLoopEnded() {
  if (target_current_window_ != x11::Window::None) {
    SendXdndLeave(target_current_window_);
    target_current_window_ = x11::Window::None;
  }
  ResetDragContext();
  StopRepeatMouseMoveTimer();
  StopEndMoveLoopTimer();
}

x11::ClientMessageEvent XDragDropClient::PrepareXdndClientMessage(
    const char* message,
    x11::Window recipient) const {
  x11::ClientMessageEvent xev;
  xev.type = x11::GetAtom(message);
  xev.window = recipient;
  xev.format = 32;
  xev.data.data32.fill(0);
  xev.data.data32[0] = static_cast<uint32_t>(xwindow_);
  return xev;
}

x11::Window XDragDropClient::FindWindowFor(const gfx::Point& screen_point) {
  auto finder = delegate_->CreateWindowFinder();
  x11::Window target = finder->FindWindowAt(screen_point);

  if (target == x11::Window::None)
    return x11::Window::None;

  // TODO(crbug/651775): The proxy window should be reported separately from the
  //     target window. XDND messages should be sent to the proxy, and their
  //     window field should point to the target.

  // Figure out which window we should test as XdndAware. If |target| has
  // XdndProxy, it will set that proxy on target, and if not, |target|'s
  // original value will remain.
  GetProperty(target, x11::GetAtom(kXdndProxy), &target);

  uint32_t version;
  if (GetProperty(target, x11::GetAtom(kXdndAware), &version) &&
      version >= kMaxXdndVersion) {
    return target;
  }
  return x11::Window::None;
}

void XDragDropClient::SendXClientEvent(x11::Window window,
                                       const x11::ClientMessageEvent& xev) {
  // Don't send messages to the X11 message queue if we can help it.
  XDragDropClient* short_circuit = GetForWindow(window);
  if (short_circuit && short_circuit->HandleXdndEvent(xev))
    return;

  // I don't understand why the GTK+ code is doing what it's doing here. It
  // goes out of its way to send the XEvent so that it receives a callback on
  // success or failure, and when it fails, it then sends an internal
  // GdkEvent about the failed drag. (And sending this message doesn't appear
  // to go through normal xlib machinery, but instead passes through the low
  // level xProto (the x11 wire format) that I don't understand.
  //
  // I'm unsure if I have to jump through those hoops, or if XSendEvent is
  // sufficient.
  x11::SendEvent(xev, window, x11::EventMask::NoEvent);
}

void XDragDropClient::SendXdndEnter(x11::Window dest_window,
                                    const std::vector<x11::Atom>& targets) {
  auto xev = PrepareXdndClientMessage(kXdndEnter, dest_window);
  xev.data.data32[1] = (kMaxXdndVersion << 24);  // The version number.

  if (targets.size() > 3) {
    xev.data.data32[1] |= 1;
    SetArrayProperty(xwindow(), x11::GetAtom(kXdndTypeList), x11::Atom::ATOM,
                     targets);
  } else {
    // Pack the targets into the enter message.
    for (size_t i = 0; i < targets.size(); ++i)
      xev.data.data32[2 + i] = static_cast<uint32_t>(targets[i]);
  }

  SendXClientEvent(dest_window, xev);
}

void XDragDropClient::SendXdndPosition(x11::Window dest_window,
                                       const gfx::Point& screen_point,
                                       unsigned long event_time) {
  waiting_on_status_ = true;

  auto xev = PrepareXdndClientMessage(kXdndPosition, dest_window);
  xev.data.data32[2] = (screen_point.x() << 16) | screen_point.y();
  xev.data.data32[3] = event_time;
  xev.data.data32[4] = static_cast<uint32_t>(
      DragOperationToAtom(PreferredDragOperation(allowed_operations_)));
  SendXClientEvent(dest_window, xev);

  // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html and
  // the Xdnd protocol both recommend that drag events should be sent
  // periodically.
  repeat_mouse_move_timer_.Start(
      FROM_HERE, base::TimeDelta::FromMilliseconds(350),
      base::BindOnce(&XDragDropClient::ProcessMouseMove, base::Unretained(this),
                     screen_point, event_time));
}

void XDragDropClient::SendXdndLeave(x11::Window dest_window) {
  auto xev = PrepareXdndClientMessage(kXdndLeave, dest_window);
  SendXClientEvent(dest_window, xev);
}

void XDragDropClient::SendXdndDrop(x11::Window dest_window) {
  auto xev = PrepareXdndClientMessage(kXdndDrop, dest_window);
  xev.data.data32[2] = static_cast<uint32_t>(x11::Time::CurrentTime);
  SendXClientEvent(dest_window, xev);
}

void XDragDropClient::EndMoveLoop() {
  StopEndMoveLoopTimer();
  delegate_->EndDragLoop();
}

}  // namespace ui