diff options
author | Bowon Ryu <bowon.ryu@samsung.com> | 2024-04-17 18:18:43 +0900 |
---|---|---|
committer | Bowon Ryu <bowon.ryu@samsung.com> | 2024-06-05 17:27:26 +0900 |
commit | 5d222bd5fb39c6afabba14be32486df95e90e3f5 (patch) | |
tree | 484b29950097e995ec44e068fe657b5e02150c9d /dali | |
parent | 3b1f15283e990422c0dc762469d03a1132c10be4 (diff) | |
download | dali-adaptor-5d222bd5fb39c6afabba14be32486df95e90e3f5.tar.gz dali-adaptor-5d222bd5fb39c6afabba14be32486df95e90e3f5.tar.bz2 dali-adaptor-5d222bd5fb39c6afabba14be32486df95e90e3f5.zip |
Implement multi-type copy support for the clipboard
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>
Diffstat (limited to 'dali')
4 files changed, 308 insertions, 71 deletions
diff --git a/dali/internal/clipboard/common/clipboard-impl.h b/dali/internal/clipboard/common/clipboard-impl.h index cf508ea36..a7a2eea1a 100644 --- a/dali/internal/clipboard/common/clipboard-impl.h +++ b/dali/internal/clipboard/common/clipboard-impl.h @@ -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&); diff --git a/dali/internal/clipboard/generic/clipboard-impl-generic.cpp b/dali/internal/clipboard/generic/clipboard-impl-generic.cpp index ce788bd87..b5732405c 100644 --- a/dali/internal/clipboard/generic/clipboard-impl-generic.cpp +++ b/dali/internal/clipboard/generic/clipboard-impl-generic.cpp @@ -135,6 +135,11 @@ bool Clipboard::OnReceiveData() return false; } +bool Clipboard::OnMultiSelectionTimeout() +{ + return false; +} + } // namespace Adaptor } // namespace Internal diff --git a/dali/internal/clipboard/tizen-wayland/clipboard-impl-ecore-wl.cpp b/dali/internal/clipboard/tizen-wayland/clipboard-impl-ecore-wl.cpp index 0dc294958..19b1163da 100644 --- a/dali/internal/clipboard/tizen-wayland/clipboard-impl-ecore-wl.cpp +++ b/dali/internal/clipboard/tizen-wayland/clipboard-impl-ecore-wl.cpp @@ -19,11 +19,13 @@ #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 diff --git a/dali/internal/clipboard/ubuntu-x11/clipboard-impl-x.cpp b/dali/internal/clipboard/ubuntu-x11/clipboard-impl-x.cpp index 9c15021c3..4780e04a1 100644 --- a/dali/internal/clipboard/ubuntu-x11/clipboard-impl-x.cpp +++ b/dali/internal/clipboard/ubuntu-x11/clipboard-impl-x.cpp @@ -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 |