[Tizen] Implement multi-type copy support for the clipboard 05/312505/1 accepted/tizen/unified/20240612.100003 accepted/tizen/unified/x/20240612.110436
authorBowon Ryu <bowon.ryu@samsung.com>
Wed, 17 Apr 2024 09:18:43 +0000 (18:18 +0900)
committerBowon Ryu <bowon.ryu@samsung.com>
Tue, 11 Jun 2024 01:24:57 +0000 (10:24 +0900)
For SetData, identical serials are considered the same source.
ecore_wl2_dnd_selection_set is invoked using the types requested by the user.

For GetData, only requests ecore_wl2_offer_receive once for the same offer and type.
And clipboard do not request multiple offer receives simultaneously but rather sequentially.

Change-Id: I6cd91026b22986ab51c21e94c073691065149a9e
Signed-off-by: Bowon Ryu <bowon.ryu@samsung.com>
dali/internal/clipboard/common/clipboard-impl.h
dali/internal/clipboard/generic/clipboard-impl-generic.cpp
dali/internal/clipboard/tizen-wayland/clipboard-impl-ecore-wl.cpp
dali/internal/clipboard/ubuntu-x11/clipboard-impl-x.cpp

index cf508ea..a7a2eea 100644 (file)
@@ -119,6 +119,12 @@ public:
    */
   bool OnReceiveData();
 
+  /**
+   * This is called after a timeout when no new data set.
+   * @return will return false; one-shot timer.
+   */
+  bool OnMultiSelectionTimeout();
+
 private:
   // Undefined
   Clipboard(const Clipboard&);
index ce788bd..b573240 100644 (file)
@@ -135,6 +135,11 @@ bool Clipboard::OnReceiveData()
   return false;
 }
 
+bool Clipboard::OnMultiSelectionTimeout()
+{
+  return false;
+}
+
 } // namespace Adaptor
 
 } // namespace Internal
index 0dc2949..19b1163 100644 (file)
 #include <dali/internal/clipboard/common/clipboard-impl.h>
 
 // EXTERNAL INCLUDES
+#include <dali/devel-api/adaptor-framework/environment-variable.h>
 #include <dali/devel-api/common/singleton-service.h>
 #include <dali/internal/adaptor/tizen-wayland/dali-ecore-wl2.h>
 #include <dali/integration-api/debug.h>
+#include <dali/public-api/adaptor-framework/timer.h>
+#include <map>
 #include <unistd.h>
-#include <unordered_map>
 
 namespace Dali
 {
@@ -31,6 +33,12 @@ namespace Internal
 {
 namespace Adaptor
 {
+namespace
+{
+const char*    DALI_CLIPBOARD_MULTI_SELECTION_TIMEOUT("DALI_CLIPBOARD_MULTI_SELECTION_TIMEOUT");
+const uint32_t DEFAULT_MULTI_SELECTION_TIMEOUT = 500u;
+}
+
 static Eina_Bool EcoreEventDataSend(void* data, int type, void* event);
 static Eina_Bool EcoreEventOfferDataReady(void* data, int type, void* event);
 static Eina_Bool EcoreEventSelectionOffer(void* data, int type, void* event);
@@ -78,23 +86,78 @@ struct Clipboard::Impl
 
   bool SetData(const Dali::Clipboard::ClipData& clipData)
   {
-    mMimeType = clipData.GetMimeType();
-    mData     = clipData.GetData();
+    std::string mimeType = clipData.GetMimeType();
+    std::string data     = clipData.GetData();
 
-    if(mData.empty())
+    if(data.empty())
     {
       DALI_LOG_ERROR("ClipData is empty, return false.\n");
       return false;
     }
 
     const char* mimeTypes[2];
-    mimeTypes[0] = mMimeType.c_str();
+    mimeTypes[0] = mimeType.c_str();
     mimeTypes[1] = nullptr;
 
+    mSetDatas[mimeType] = data;
+
     Ecore_Wl2_Display* display = ecore_wl2_connected_display_get(NULL);
     Ecore_Wl2_Input* input     = ecore_wl2_input_default_input_get(display);
-    mSerial                    = ecore_wl2_dnd_selection_set(input, mimeTypes);
-    DALI_LOG_RELEASE_INFO("selection_set success, serial:%u, type:%s, data:%s\n", mSerial, mMimeType.c_str(), mData.c_str());
+
+    uint32_t serial = ecore_wl2_dnd_selection_set(input, mimeTypes);
+    DALI_LOG_RELEASE_INFO("selection_set success, serial:%u, type:%s, data:%s\n", serial, mimeType.c_str(), data.c_str());
+
+    // If the serial is the same, it is the same source.
+    // If the type is the same, it is a separate copy.
+    if(mSerial == serial && mLastType != mimeType && !mMultiSelectionTimeout)
+    {
+      // Checks whether there is an identical type requested from one source.
+      bool typeExists = false;
+      for (const auto& type : mSetTypes)
+      {
+        if (type == mimeType)
+        {
+          typeExists = true;
+          break;
+        }
+      }
+
+      if(!typeExists) // Same copy.
+      {
+        // It requests all types of copies requested from one source at once.
+        // EcoreEventDataSend callback is called as many as the number of requested types.
+        mSetTypes.push_back(mimeType);
+
+        size_t typeCount = mSetTypes.size();
+        const char* types[typeCount + 1];
+        for(size_t i = 0; i < typeCount; i++)
+        {
+          types[i] = mSetTypes[i].c_str();
+          DALI_LOG_RELEASE_INFO("selection_set multi types, serial:%u, type:%s\n", serial, types[i]);
+        }
+        types[typeCount] = nullptr;
+
+        // TODO : At this point, it is impossible to avoid duplicate calls,
+        // because we cannot know how many more times the copy will be called for the same source.
+        serial = ecore_wl2_dnd_selection_set(input, types);
+      }
+      else // Separate copy.
+      {
+        mSetTypes.clear();
+        mSetTypes.push_back(mimeType);
+      }
+    }
+    else
+    {
+      mSetTypes.clear();
+      mSetTypes.push_back(mimeType);
+    }
+
+    // Store the last serial and type.
+    mSerial   = serial;
+    mLastType = mimeType;
+
+    SetMultiSelectionTimeout();
 
     return true;
   }
@@ -115,6 +178,7 @@ struct Clipboard::Impl
     if(!offer)
     {
       DALI_LOG_ERROR("selection_get fail, request type:%s\n", mimeType.c_str());
+      mLastOffer = nullptr;
       return 0u;
     }
 
@@ -142,13 +206,33 @@ struct Clipboard::Impl
       return 0u;
     }
 
+    uint32_t lastDataId = mDataId;
     mDataId++;
     mDataRequestIds.push_back(mDataId);
     mDataRequestItems[mDataId] = std::make_pair(mimeType, offer);
 
-    DALI_LOG_RELEASE_INFO("offer_receive, id:%u, request type:%s\n", mDataId, mimeType.c_str());
-    ecore_wl2_offer_receive(offer, const_cast<char*>(type));
-    ecore_wl2_display_flush(ecore_wl2_input_display_get(input));
+    // Not yet received a callback for the recent offer receive.
+    if(mLastOffer == offer && mDataRequestItems.count(lastDataId))
+    {
+      // A receive request for the same offer and type is made only once.
+      if(std::find(mGetTypes.begin(), mGetTypes.end(), mimeType) == mGetTypes.end())
+      {
+        mGetTypes.push_back(mimeType);
+        mReservedOfferReceives[lastDataId] = mDataId;
+      } // else do nothing.
+    }
+    else
+    {
+      mGetTypes.clear();
+      mGetTypes.push_back(mimeType);
+
+      DALI_LOG_RELEASE_INFO("offer_receive, id:%u, request type:%s\n", mDataId, mimeType.c_str());
+      ecore_wl2_offer_receive(offer, const_cast<char*>(type));
+      ecore_wl2_display_flush(ecore_wl2_input_display_get(input));
+    }
+
+    mLastOffer = offer;
+
     return mDataId;
   }
 
@@ -158,37 +242,20 @@ struct Clipboard::Impl
 
     if(ev->serial != mSerial)
     {
+      DALI_LOG_ERROR("ev->serial:%u, mSerial:%u, type:%s\n", ev->serial, mSerial, ev->type);
       return;
     }
 
-    // no matching mime type.
-    if(mMimeType.compare(ev->type))
+    // Check whether the hash has data of the requested type.
+    // If there is no data of the requested type, something has already gone wrong.
+    std::string type = ev->type;
+    std::string data = "";
+    if(mSetDatas.count(type))
     {
-      auto it = mDataRequestIds.begin();
-      while(it != mDataRequestIds.end())
-      {
-        uint32_t dataRequestId = *it;
-        auto     item          = mDataRequestItems.find(dataRequestId);
-        if(item != mDataRequestItems.end())
-        {
-          std::string mimeType = static_cast<std::string>(item->second.first);
-          if(!mimeType.compare(ev->type))
-          {
-            mDataRequestItems.erase(dataRequestId);
-            it = mDataRequestIds.erase(it);
-            DALI_LOG_ERROR("no matching type, empty signal emit, request type:%s, available type:%s\n", ev->type, mMimeType.c_str());
-            mDataReceivedSignal.Emit(dataRequestId, "", "");
-          }
-          else
-          {
-            ++it;
-          }
-        }
-      }
-      return;
+      data = mSetDatas[type];
     }
 
-    size_t dataLength = strlen(mData.c_str());
+    size_t dataLength = strlen(data.c_str());
     size_t bufferSize = dataLength + 1u;
 
     char* buffer = new char[bufferSize];
@@ -197,7 +264,7 @@ struct Clipboard::Impl
       return;
     }
 
-    memcpy(buffer, mData.c_str(), dataLength);
+    memcpy(buffer, data.c_str(), dataLength);
     buffer[dataLength] = '\0';
 
     auto ret = write(ev->fd, buffer, bufferSize);
@@ -209,8 +276,8 @@ struct Clipboard::Impl
     close(ev->fd);
     delete[] buffer;
 
-    DALI_LOG_RELEASE_INFO("send data, type:%s, data:%s \n", mMimeType.c_str(), mData.c_str());
-    mDataSentSignal.Emit(ev->type, mData.c_str());
+    DALI_LOG_RELEASE_INFO("send data, type:%s, data:%s \n", ev->type, data.c_str());
+    mDataSentSignal.Emit(ev->type, data.c_str());
   }
 
   void ReceiveData(void* event)
@@ -244,27 +311,72 @@ struct Clipboard::Impl
 
     DALI_LOG_RELEASE_INFO("receive data, type:%s, data:%s\n", ev->mimetype, content.c_str());
 
-    auto it = mDataRequestIds.begin();
-    while(it != mDataRequestIds.end())
+    // Retrieve request id list.
+    for(auto it = mDataRequestIds.begin(); it != mDataRequestIds.end();)
     {
       uint32_t dataRequestId = *it;
-      auto     item          = mDataRequestItems.find(dataRequestId);
-      if(item != mDataRequestItems.end())
+      if(mDataRequestItems.count(dataRequestId))
       {
-        Ecore_Wl2_Offer* offer = static_cast<Ecore_Wl2_Offer*>(item->second.second);
-        if(offer == ev->offer)
+        auto             item     = mDataRequestItems[dataRequestId];
+        std::string      mimeType = static_cast<std::string>(item.first);
+        Ecore_Wl2_Offer* offer    = static_cast<Ecore_Wl2_Offer*>(item.second);
+
+        // Processes all the same types stored in the request list.
+        if(!mimeType.compare(ev->mimetype))
         {
-          std::string mimeType = static_cast<std::string>(item->second.first);
           mDataRequestItems.erase(dataRequestId);
           it = mDataRequestIds.erase(it);
-          DALI_LOG_RELEASE_INFO("receive data, success signal emit, id:%u, type:%s\n", dataRequestId, mimeType.c_str());
-          mDataReceivedSignal.Emit(dataRequestId, mimeType.c_str(), content.c_str());
+
+          // A change in an offer means a change in the clipboard's data.
+          // Old offers are not always invalid, but at least in Dali it is unknown whether they are valid or not.
+          // For safe processing, old offers are considered invalid offers.
+          if(offer && offer == ev->offer && mLastOffer == offer)
+          {
+            DALI_LOG_RELEASE_INFO("receive data, success signal emit, id:%u, type:%s\n", dataRequestId, mimeType.c_str());
+            mDataReceivedSignal.Emit(dataRequestId, mimeType.c_str(), content.c_str());
+
+            if(mReservedOfferReceives.count(dataRequestId))
+            {
+              uint32_t reservedId = mReservedOfferReceives[dataRequestId];
+              if(mDataRequestItems.count(reservedId))
+              {
+                auto             reservedItem  = mDataRequestItems[reservedId];
+                std::string      reservedType  = static_cast<std::string>(reservedItem.first);
+                Ecore_Wl2_Offer* reservedOffer = static_cast<Ecore_Wl2_Offer*>(reservedItem.second);
+
+                if(reservedOffer)
+                {
+                  Ecore_Wl2_Display* display = ecore_wl2_connected_display_get(NULL);
+                  Ecore_Wl2_Input*   input   = ecore_wl2_input_default_input_get(display);
+
+                  DALI_LOG_RELEASE_INFO("offer_receive, id:%u, request type:%s\n", reservedId, reservedType.c_str());
+                  ecore_wl2_offer_receive(reservedOffer, const_cast<char*>(reservedType.c_str()));
+                  ecore_wl2_display_flush(ecore_wl2_input_display_get(input));
+                }
+              }
+              mReservedOfferReceives.erase(dataRequestId);
+            }
+          }
+          else // null or invalid offer.
+          {
+            DALI_LOG_RELEASE_INFO("invalid offer, id:%u, request type:%s\n", dataRequestId, mimeType.c_str());
+            mDataReceivedSignal.Emit(dataRequestId, "", "");
+
+            if(mReservedOfferReceives.count(dataRequestId))
+            {
+              mReservedOfferReceives.erase(dataRequestId);
+            }
+          }
         }
-        else
+        else // item's type and event data's type are different.
         {
           ++it;
         }
       }
+      else // There is no id in request items.
+      {
+        it = mDataRequestIds.erase(it);
+      }
     }
   }
 
@@ -295,16 +407,15 @@ struct Clipboard::Impl
 
     for(int i = 0; i < ev->num_types; i++)
     {
-      DALI_LOG_RELEASE_INFO("mime type(%s)", ev->types[i]);
       if(!formatMarkup.compare(ev->types[i]))
       {
+        // Ignore elementary markup from efl.
         continue;
       }
 
-      if(!selectedType)
-      {
-        selectedType = ev->types[i];
-      }
+      selectedType = ev->types[i];
+      DALI_LOG_RELEASE_INFO("data selected signal emit, type:%s\n", selectedType);
+      mDataSelectedSignal.Emit(selectedType);
     }
 
     if(!selectedType)
@@ -312,14 +423,29 @@ struct Clipboard::Impl
       DALI_LOG_ERROR("mime type is invalid.\n");
       return;
     }
+  }
+
+  void SetMultiSelectionTimeout()
+  {
+    mMultiSelectionTimeout = false;
+    if(mMultiSelectionTimeoutTimer.IsRunning())
+    {
+      mMultiSelectionTimeoutTimer.Stop();
+    }
+    mMultiSelectionTimeoutTimer.Start();
+  }
 
-    DALI_LOG_RELEASE_INFO("data selected signal emit, type:%s\n", selectedType);
-    mDataSelectedSignal.Emit(selectedType);
+  bool OnMultiSelectionTimeout()
+  {
+    DALI_LOG_RELEASE_INFO("multi-selection end\n");
+    mMultiSelectionTimeout = true;
+    return false;
   }
 
-  uint32_t             mSerial{std::numeric_limits<uint32_t>::max()};
-  std::string          mMimeType;
-  std::string          mData;
+  uint32_t         mSerial{std::numeric_limits<uint32_t>::max()};
+  std::string      mLastType;  // mime type used in last copy.
+  Ecore_Wl2_Offer* mLastOffer; // offer used in last paste.
+
   Ecore_Event_Handler* mSendHandler{nullptr};
   Ecore_Event_Handler* mReceiveHandler{nullptr};
   Ecore_Event_Handler* mSelectionHanlder{nullptr};
@@ -330,7 +456,15 @@ struct Clipboard::Impl
 
   uint32_t mDataId{0};
   std::vector<uint32_t> mDataRequestIds;
-  std::unordered_map<uint32_t, std::pair<std::string, Ecore_Wl2_Offer*>> mDataRequestItems;
+  std::map<uint32_t, std::pair<std::string, Ecore_Wl2_Offer*>> mDataRequestItems;
+
+  std::vector<std::string>           mSetTypes;              // types for the same source (one user copy).
+  std::map<std::string, std::string> mSetDatas;              // datas for the same source (one user copy), key is mime type, value is data.
+  std::vector<std::string>           mGetTypes;              // types requested to receive for the same offer.
+  std::map<uint32_t, uint32_t>       mReservedOfferReceives; // in order to process offer receive sequentially, key is current id, value is reserved id.
+
+  Dali::Timer mMultiSelectionTimeoutTimer;
+  bool        mMultiSelectionTimeout{false};
 };
 
 static Eina_Bool EcoreEventDataSend(void* data, int type, void* event)
@@ -360,6 +494,13 @@ static Eina_Bool EcoreEventSelectionOffer(void* data, int type, void* event)
 Clipboard::Clipboard(Impl* impl)
 : mImpl(impl)
 {
+  // Check environment variable for DALI_CLIPBOARD_MULTI_SELECTION_TIMEOUT
+  auto timeoutString = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_CLIPBOARD_MULTI_SELECTION_TIMEOUT);
+  uint32_t multiSelectionTimeout = timeoutString ? static_cast<uint32_t>(std::atoi(timeoutString)) : DEFAULT_MULTI_SELECTION_TIMEOUT;
+
+  DALI_LOG_RELEASE_INFO("multi-selection timeout set:%u\n", multiSelectionTimeout);
+  mImpl->mMultiSelectionTimeoutTimer = Dali::Timer::New(multiSelectionTimeout);
+  mImpl->mMultiSelectionTimeoutTimer.TickSignal().Connect(this, &Clipboard::OnMultiSelectionTimeout);
 }
 
 Clipboard::~Clipboard()
@@ -460,6 +601,11 @@ bool Clipboard::OnReceiveData()
   return false;
 }
 
+bool Clipboard::OnMultiSelectionTimeout()
+{
+  return mImpl->OnMultiSelectionTimeout();
+}
+
 } // namespace Adaptor
 
 } // namespace Internal
index 9c15021..4780e04 100644 (file)
@@ -29,6 +29,8 @@
 #include <dali/devel-api/common/singleton-service.h>
 #include <dali/internal/adaptor/common/adaptor-impl.h>
 #include <dali/internal/window-system/ubuntu-x11/window-interface-ecore-x.h>
+#include <map>
+#include <queue>
 
 namespace Dali
 {
@@ -45,30 +47,64 @@ struct Clipboard::Impl
 
   bool HasType(const std::string& mimeType)
   {
-    return mMimeType == mimeType ? true : false;
+    for(const auto& type : mMimeTypes)
+    {
+      if (type == mimeType)
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  void UpdateData(std::string& mimeType, std::string& data, bool clearBuffer)
+  {
+    if(clearBuffer)
+    {
+      mMimeTypes.clear();
+      mDatas.clear();
+    }
+    mMimeTypes.push_back(mimeType);
+    mDatas[mimeType] = data;
   }
 
   bool SetData(const Dali::Clipboard::ClipData& clipData)
   {
-    mMimeType = clipData.GetMimeType();
-    mData     = clipData.GetData();
+    std::string mimeType = clipData.GetMimeType();
+    std::string data     = clipData.GetData();
 
-    if(mData.empty())
+    if(mimeType.empty() || data.empty())
     {
       return false;
     }
 
-    mDataSentSignal.Emit(mMimeType.c_str(), mData.c_str());
-    mDataSelectedSignal.Emit(mMimeType.c_str());
+    if(mLastType != mimeType && !mMultiSelectionTimeout)
+    {
+      bool clearBuffer = HasType(mimeType);
+      UpdateData(mimeType, data, clearBuffer);
+    }
+    else
+    {
+      UpdateData(mimeType, data, true);
+    }
+
+    mLastType = mimeType;
+
+    mDataSentSignal.Emit(mimeType.c_str(), data.c_str());
+    mDataSelectedSignal.Emit(mimeType.c_str());
+
+    SetMultiSelectionTimeout();
 
     return true;
   }
 
   uint32_t GetData(const std::string &mimeType)
   {
-    if(!mMimeType.compare(mimeType.c_str()))
+    if(mDatas.count(mimeType))
     {
       mDataId++;
+      mDataReceiveQueue.push(std::make_pair(mDataId, mimeType));
+
       // For consistency of operation with tizen Wl2, a fake callback is occurs using a timer.
       if(mDataReceiveTimer.IsRunning())
       {
@@ -83,28 +119,67 @@ struct Clipboard::Impl
 
   bool OnReceiveData()
   {
-    DALI_LOG_RELEASE_INFO("receive data, success signal emit, id:%u, type:%s, data:%s\n", mDataId, mMimeType.c_str(), mData.c_str());
-    mDataReceivedSignal.Emit(mDataId, mMimeType.c_str(), mData.c_str());
+    while(!mDataReceiveQueue.empty())
+    {
+      auto item = mDataReceiveQueue.front();
+      mDataReceiveQueue.pop();
+
+      uint32_t    requestId   = item.first;
+      std::string requestType = item.second;
+      std::string data        = "";
+
+      if(mDatas.count(requestType))
+      {
+        data = mDatas[requestType];
+      }
+
+      DALI_LOG_RELEASE_INFO("receive data, success signal emit, id:%u, type:%s, data:%s\n", requestId, requestType.c_str(), data.c_str());
+      mDataReceivedSignal.Emit(requestId, requestType.c_str(), data.c_str());
+    }
+    return false;
+  }
+
+  void SetMultiSelectionTimeout()
+  {
+    mMultiSelectionTimeout = false;
+    if(mMultiSelectionTimeoutTimer.IsRunning())
+    {
+      mMultiSelectionTimeoutTimer.Stop();
+    }
+    mMultiSelectionTimeoutTimer.Start();
+  }
+
+  bool OnMultiSelectionTimeout()
+  {
+    mMultiSelectionTimeout = true;
     return false;
   }
 
   Ecore_X_Window mApplicationWindow;
-  std::string    mMimeType;
-  std::string    mData;
   uint32_t       mDataId{0};
+  std::string    mLastType;
+
+  std::vector<std::string>                     mMimeTypes;
+  std::map<std::string, std::string>           mDatas;            // type, data
+  std::queue<std::pair<uint32_t, std::string>> mDataReceiveQueue; // id, type
 
   Dali::Clipboard::DataSentSignalType     mDataSentSignal;
   Dali::Clipboard::DataReceivedSignalType mDataReceivedSignal;
   Dali::Clipboard::DataSelectedSignalType mDataSelectedSignal;
 
   Dali::Timer mDataReceiveTimer;
+  Dali::Timer mMultiSelectionTimeoutTimer;
+  bool        mMultiSelectionTimeout{false};
 };
 
 Clipboard::Clipboard(Impl* impl)
 : mImpl(impl)
 {
-  mImpl->mDataReceiveTimer = Dali::Timer::New(10);
+  mImpl->mDataReceiveTimer = Dali::Timer::New(10u);
   mImpl->mDataReceiveTimer.TickSignal().Connect(this, &Clipboard::OnReceiveData);
+
+  mImpl->mMultiSelectionTimeoutTimer = Dali::Timer::New(500u);
+  mImpl->mMultiSelectionTimeoutTimer.TickSignal().Connect(this, &Clipboard::OnMultiSelectionTimeout);
 }
 
 Clipboard::~Clipboard()
@@ -217,6 +292,11 @@ bool Clipboard::OnReceiveData()
   return mImpl->OnReceiveData();
 }
 
+bool Clipboard::OnMultiSelectionTimeout()
+{
+  return mImpl->OnMultiSelectionTimeout();
+}
+
 } // namespace Adaptor
 
 } // namespace Internal