diff options
31 files changed, 1075 insertions, 316 deletions
diff --git a/launcher/AbstractLauncherIcon.h b/launcher/AbstractLauncherIcon.h index bb58b7191..b0cb5f11b 100644 --- a/launcher/AbstractLauncherIcon.h +++ b/launcher/AbstractLauncherIcon.h @@ -103,6 +103,7 @@ public: RUNNING, URGENT, PRESENTED, + UNFOLDED, STARTING, SHIMMER, CENTER_SAVED, diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 54c3c8961..923aa8208 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -33,7 +33,6 @@ set (LAUNCHER_SOURCES ApplicationLauncherIcon.cpp BFBLauncherIcon.cpp CairoBaseWindow.cpp - DNDCollectionWindow.cpp Decaymulator.cpp DesktopLauncherIcon.cpp DeviceLauncherSection.cpp @@ -73,6 +72,10 @@ set (LAUNCHER_SOURCES VolumeImp.cpp VolumeLauncherIcon.cpp VolumeMonitorWrapper.cpp + XdndCollectionWindowImp.cpp + XdndManagerImp.cpp + XdndStartStopNotifier.cpp + XdndStartStopNotifierImp.cpp ) if (ENABLE_X_SUPPORT) diff --git a/launcher/DNDCollectionWindow.cpp b/launcher/DNDCollectionWindow.cpp deleted file mode 100644 index 6b0dac695..000000000 --- a/launcher/DNDCollectionWindow.cpp +++ /dev/null @@ -1,99 +0,0 @@ -// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- -/* -* Copyright (C) 2011 Canonical Ltd -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License version 3 as -* published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see <http://www.gnu.org/licenses/>. -* -* Authored by: Andrea Azzarone <azzaronea@gmail.com> -*/ - -#include "DNDCollectionWindow.h" - -#include "unity-shared/WindowManager.h" - -namespace unity { - -NUX_IMPLEMENT_OBJECT_TYPE(DNDCollectionWindow); - -DNDCollectionWindow::DNDCollectionWindow() - : nux::BaseWindow("") - , display(NULL) -{ - // Make it invisible... - SetBackgroundColor(nux::Color(0x00000000)); - SetOpacity(0.0f); - // ... and as big as the whole screen. - WindowManager& wm = WindowManager::Default(); - SetGeometry(wm.GetScreenGeometry()); - - ShowWindow(true); - PushToBack(); - // Hack to create the X Window as soon as possible. - EnableInputWindow(true, "DNDCollectionWindow"); - EnableInputWindow(false, "DNDCollectionWindow"); - SetDndEnabled(false, true); - - wm.window_moved.connect(sigc::mem_fun(this, &DNDCollectionWindow::OnWindowMoved)); -} - -DNDCollectionWindow::~DNDCollectionWindow() -{ - for (auto it : mimes_) - g_free(it); -} - -/** - * EnableInputWindow doesn't show the window immediately. - * Since nux::EnableInputWindow uses XMoveResizeWindow the best way to know if - * the X Window is really on/off screen is receiving WindowManager::window_moved - * signal. Please don't hate me! - **/ -void DNDCollectionWindow::OnWindowMoved(Window window_id) -{ - if (window_id == GetInputWindowId() && display() != NULL) - { - // Create a fake mouse move because sometimes an extra one is required. - XWarpPointer(display(), None, None, 0, 0, 0, 0, 0, 0); - XFlush(display()); - } -} - -void DNDCollectionWindow::Collect() -{ - // Using PushToFront we're sure that the window is shown over the panel window, - // the launcher window and the dash window. Don't forget to call PushToBack as - // soon as possible. - PushToFront(); - EnableInputWindow(true, "DndCollectionWindow"); -} - -void DNDCollectionWindow::ProcessDndMove(int x, int y, std::list<char*> mimes) -{ - // Hide the window as soon as possible. - PushToBack(); - EnableInputWindow(false, "DNDCollectionWindow"); - - // Free mimes_ before fill it again. - for (auto it : mimes_) - g_free(it); - mimes_.clear(); - - // Duplicate the list. - for (auto it : mimes) - mimes_.push_back(g_strdup(it)); - - // Emit the collected signal. - collected.emit(mimes_); -} - -} // namespace unity diff --git a/launcher/DNDCollectionWindow.h b/launcher/DNDCollectionWindow.h deleted file mode 100644 index a899d3cde..000000000 --- a/launcher/DNDCollectionWindow.h +++ /dev/null @@ -1,63 +0,0 @@ -// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- -/* -* Copyright (C) 2011 Canonical Ltd -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License version 3 as -* published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see <http://www.gnu.org/licenses/>. -* -* Authored by: Andrea Azzarone <azzaronea@gmail.com> -*/ - -#ifndef DNDCOLLECTIONWINDOW_H -#define DNDCOLLECTIONWINDOW_H - -#include <list> - -#include <Nux/Nux.h> -#include <Nux/BaseWindow.h> -#include <sigc++/sigc++.h> - -namespace unity { - -/** - * DNDCollectionWindow makes it possible to collect drag and drop (dnd) data as - * soon as dnd starts and not when the mouse pointer enter the x window. - **/ - -class DNDCollectionWindow : public nux::BaseWindow -{ -NUX_DECLARE_OBJECT_TYPE(DNDCollectionWindow, nux::BaseWindow); - -// Methods -public: - DNDCollectionWindow(); - ~DNDCollectionWindow(); - - void Collect(); - -private: - void ProcessDndMove(int x, int y, std::list<char*> mimes); - void OnWindowMoved(Window window_id); - -// Members -public: - nux::Property<Display*> display; - - sigc::signal<void, const std::list<char*>&> collected; - -private: - std::list<char*> mimes_; -}; - -} // namespace unity - -#endif // DNDCOLLECTIONWINDOW_H diff --git a/launcher/Launcher.cpp b/launcher/Launcher.cpp index 25d4588c4..95072c72c 100644 --- a/launcher/Launcher.cpp +++ b/launcher/Launcher.cpp @@ -91,7 +91,6 @@ const int START_DRAGICON_DURATION = 250; const int MOUSE_DEADZONE = 15; const float DRAG_OUT_PIXELS = 300.0f; -const std::string DND_CHECK_TIMEOUT = "dnd-check-timeout"; const std::string START_DRAGICON_TIMEOUT = "start-dragicon-timeout"; const std::string SCROLL_TIMEOUT = "scroll-timeout"; const std::string ANIMATION_IDLE = "animation-idle"; @@ -103,7 +102,6 @@ NUX_IMPLEMENT_OBJECT_TYPE(Launcher); const int Launcher::Launcher::ANIM_DURATION_SHORT = 125; Launcher::Launcher(nux::BaseWindow* parent, - nux::ObjectPtr<DNDCollectionWindow> const& collection_window, NUX_FILE_LINE_DECL) : View(NUX_FILE_LINE_PARAM) #ifdef USE_X11 @@ -145,14 +143,11 @@ Launcher::Launcher(nux::BaseWindow* parent, , _drag_out_delta_x(0.0f) , _drag_gesture_ongoing(false) , _last_reveal_progress(0.0f) - , _collection_window(collection_window) , _selection_atom(0) , _background_color(nux::color::DimGray) { m_Layout = new nux::HLayout(NUX_TRACKER_LOCATION); - _collection_window->collected.connect(sigc::mem_fun(this, &Launcher::OnDNDDataCollected)); - bg_effect_helper_.owner = this; bg_effect_helper_.enabled = false; @@ -180,18 +175,12 @@ Launcher::Launcher(nux::BaseWindow* parent, ql_manager.quicklist_closed.connect(sigc::mem_fun(this, &Launcher::RecvQuicklistClosed)); WindowManager& wm = WindowManager::Default(); - wm.window_mapped.connect(sigc::hide(sigc::mem_fun(this, &Launcher::DndTimeoutSetup))); - wm.window_unmapped.connect(sigc::hide(sigc::mem_fun(this, &Launcher::DndTimeoutSetup))); wm.initiate_spread.connect(sigc::mem_fun(this, &Launcher::OnPluginStateChanged)); wm.initiate_expo.connect(sigc::mem_fun(this, &Launcher::OnPluginStateChanged)); wm.terminate_spread.connect(sigc::mem_fun(this, &Launcher::OnPluginStateChanged)); wm.terminate_expo.connect(sigc::mem_fun(this, &Launcher::OnPluginStateChanged)); wm.screen_viewport_switch_ended.connect(sigc::mem_fun(this, &Launcher::EnsureAnimation)); -#ifdef USE_X11 - display.changed.connect(sigc::mem_fun(this, &Launcher::OnDisplayChanged)); -#endif - // 0 out timers to avoid wonky startups for (int i = 0; i < TIME_LAST; ++i) { @@ -227,11 +216,6 @@ std::string Launcher::GetName() const return "Launcher"; } -void Launcher::OnDisplayChanged(Display* display) -{ - _collection_window->display = display; -} - #ifdef NUX_GESTURES_SUPPORT void Launcher::OnDragStart(const nux::GestureEvent &event) { @@ -399,6 +383,10 @@ bool Launcher::IconNeedsAnimation(AbstractLauncherIcon::Ptr const& icon, struct if (unity::TimeUtil::TimeDelta(¤t, &time) < ANIM_DURATION) return true; + time = icon->GetQuirkTime(AbstractLauncherIcon::Quirk::UNFOLDED); + if (unity::TimeUtil::TimeDelta(¤t, &time) < ANIM_DURATION) + return true; + time = icon->GetQuirkTime(AbstractLauncherIcon::Quirk::SHIMMER); if (unity::TimeUtil::TimeDelta(¤t, &time) < ANIM_DURATION_LONG) return true; @@ -552,6 +540,18 @@ float Launcher::IconPresentProgress(AbstractLauncherIcon::Ptr const& icon, struc return 1.0f - result; } +float Launcher::IconUnfoldProgress(AbstractLauncherIcon::Ptr const& icon, struct timespec const& current) const +{ + struct timespec icon_unfold_time = icon->GetQuirkTime(AbstractLauncherIcon::Quirk::UNFOLDED); + int ms = unity::TimeUtil::TimeDelta(¤t, &icon_unfold_time); + float result = CLAMP((float) ms / (float) ANIM_DURATION, 0.0f, 1.0f); + + if (icon->GetQuirk(AbstractLauncherIcon::Quirk::UNFOLDED)) + return result; + else + return 1.0f - result; +} + float Launcher::IconUrgentProgress(AbstractLauncherIcon::Ptr const& icon, struct timespec const& current) const { struct timespec urgent_time = icon->GetQuirkTime(AbstractLauncherIcon::Quirk::URGENT); @@ -919,13 +919,14 @@ void Launcher::FillRenderArg(AbstractLauncherIcon::Ptr const& icon, // goes for 0.0f when fully unfolded, to 1.0f folded float folding_progress = CLAMP((center.y + _icon_size - folding_threshold) / (float) _icon_size, 0.0f, 1.0f); - float present_progress = IconPresentProgress(icon, current); + float unfold_progress = IconUnfoldProgress(icon, current); - folding_progress *= 1.0f - present_progress; + folding_progress *= 1.0f - unfold_progress; float half_size = (folded_size / 2.0f) + (_icon_size / 2.0f - folded_size / 2.0f) * (1.0f - folding_progress); float icon_hide_offset = autohide_offset; + float present_progress = IconPresentProgress(icon, current); icon_hide_offset *= 1.0f - (present_progress * icon->PresentUrgency()); // icon is crossing threshold, start folding @@ -1017,8 +1018,8 @@ void Launcher::RenderArgs(std::list<RenderArg> &launcher_args, // magic constant must some day be explained, for now suffice to say this constant prevents the bottom from "marching"; float magic_constant = 1.3f; - float present_progress = IconPresentProgress(*it, current); - folding_threshold -= CLAMP(sum - launcher_height, 0.0f, height * magic_constant) * (folding_constant + (1.0f - folding_constant) * present_progress); + float unfold_progress = IconUnfoldProgress(*it, current); + folding_threshold -= CLAMP(sum - launcher_height, 0.0f, height * magic_constant) * (folding_constant + (1.0f - folding_constant) * unfold_progress); } if (sum - _space_between_icons <= launcher_height) @@ -1366,59 +1367,6 @@ int Launcher::GetMouseY() const return _mouse_position.y; } -bool Launcher::OnUpdateDragManagerTimeout() -{ -#ifdef USE_X11 - if (!display()) - return false; - - if (!_selection_atom) - _selection_atom = XInternAtom(display(), "XdndSelection", false); - - Window drag_owner = XGetSelectionOwner(display(), _selection_atom); - - // evil hack because Qt does not release the seelction owner on drag finished - Window root_r, child_r; - int root_x_r, root_y_r, win_x_r, win_y_r; - unsigned int mask; - XQueryPointer(display(), DefaultRootWindow(display()), &root_r, &child_r, &root_x_r, &root_y_r, &win_x_r, &win_y_r, &mask); - - if (drag_owner && (mask & (Button1Mask | Button2Mask | Button3Mask))) - { - if (_data_checked == false) - { - _data_checked = true; - _collection_window->Collect(); - } - - return true; - } - - _data_checked = false; - _collection_window->PushToBack(); - _collection_window->EnableInputWindow(false, "DNDCollectionWindow"); - - if (IsOverlayOpen() && !_hovered) - DesaturateIcons(); - - DndReset(); - _hide_machine.SetQuirk(LauncherHideMachine::EXTERNAL_DND_ACTIVE, false); - _hide_machine.SetQuirk(LauncherHideMachine::DND_PUSHED_OFF, false); -#endif - return false; -} - -void Launcher::DndTimeoutSetup() -{ -#ifdef USE_X11 - if (sources_.GetSource(DND_CHECK_TIMEOUT)) - return; - - auto cb_func = sigc::mem_fun(this, &Launcher::OnUpdateDragManagerTimeout); - sources_.AddTimeout(200, cb_func, DND_CHECK_TIMEOUT); -#endif -} - void Launcher::OnPluginStateChanged() { WindowManager& wm = WindowManager::Default(); @@ -2566,53 +2514,6 @@ bool Launcher::DndIsSpecialRequest(std::string const& uri) const return (boost::algorithm::ends_with(uri, ".desktop") || uri.find("device://") == 0); } -void Launcher::OnDNDDataCollected(const std::list<char*>& mimes) -{ -#ifdef USE_X11 - _dnd_data.Reset(); - - const std::string uri_list = "text/uri-list"; - auto& display = nux::GetWindowThread()->GetGraphicsDisplay(); - - for (auto const& mime : mimes) - { - if (mime != uri_list) - continue; - - _dnd_data.Fill(display.GetDndData(const_cast<char*>(uri_list.c_str()))); - break; - } - - _hide_machine.SetQuirk(LauncherHideMachine::EXTERNAL_DND_ACTIVE, true); - - auto const& uris = _dnd_data.Uris(); - if (std::find_if(uris.begin(), uris.end(), [this] (std::string const& uri) - {return DndIsSpecialRequest(uri);}) != uris.end()) - { - _steal_drag = true; - - if (IsOverlayOpen()) - SaturateIcons(); - } - else - { - for (auto const& it : *_model) - { - if (it->ShouldHighlightOnDrag(_dnd_data)) - { - it->SetQuirk(AbstractLauncherIcon::Quirk::DESAT, false); - it->SetQuirk(AbstractLauncherIcon::Quirk::PRESENTED, true); - } - else - { - it->SetQuirk(AbstractLauncherIcon::Quirk::DESAT, true); - it->SetQuirk(AbstractLauncherIcon::Quirk::PRESENTED, false); - } - } - } -#endif -} - void Launcher::ProcessDndEnter() { #ifdef USE_X11 @@ -2648,7 +2549,7 @@ void Launcher::DndReset() it->SetQuirk(AbstractLauncherIcon::Quirk::DESAT, is_overlay_open && !_hovered); } - it->SetQuirk(AbstractLauncherIcon::Quirk::PRESENTED, false); + it->SetQuirk(AbstractLauncherIcon::Quirk::UNFOLDED, false); } DndHoveredIconReset(); @@ -2873,5 +2774,69 @@ int Launcher::GetDragDelta() const return _launcher_drag_delta; } +void Launcher::DndStarted(std::string const& data) +{ +#ifdef USE_X11 + SetDndQuirk(); + + _dnd_data.Fill(data.c_str()); + + auto const& uris = _dnd_data.Uris(); + if (std::find_if(uris.begin(), uris.end(), [this] (std::string const& uri) + {return DndIsSpecialRequest(uri);}) != uris.end()) + { + _steal_drag = true; + + if (IsOverlayOpen()) + SaturateIcons(); + } + else + { + for (auto const& it : *_model) + { + if (it->ShouldHighlightOnDrag(_dnd_data)) + { + it->SetQuirk(AbstractLauncherIcon::Quirk::DESAT, false); + it->SetQuirk(AbstractLauncherIcon::Quirk::UNFOLDED, true); + } + else + { + it->SetQuirk(AbstractLauncherIcon::Quirk::DESAT, true); + it->SetQuirk(AbstractLauncherIcon::Quirk::UNFOLDED, false); + } + } + } +#endif +} + +void Launcher::DndFinished() +{ +#ifdef USE_X11 + UnsetDndQuirk(); + + _data_checked = false; + + if (IsOverlayOpen() && !_hovered) + DesaturateIcons(); + + DndReset(); +#endif +} + +void Launcher::SetDndQuirk() +{ +#ifdef USE_X11 + _hide_machine.SetQuirk(LauncherHideMachine::EXTERNAL_DND_ACTIVE, true); +#endif +} + +void Launcher::UnsetDndQuirk() +{ +#ifdef USE_X11 + _hide_machine.SetQuirk(LauncherHideMachine::EXTERNAL_DND_ACTIVE, false); + _hide_machine.SetQuirk(LauncherHideMachine::EXTERNAL_DND_ACTIVE, false); +#endif +} + } // namespace launcher } // namespace unity diff --git a/launcher/Launcher.h b/launcher/Launcher.h index 99e8ca9d2..aa871ffc8 100644 --- a/launcher/Launcher.h +++ b/launcher/Launcher.h @@ -32,7 +32,6 @@ #include "unity-shared/AbstractIconRenderer.h" #include "unity-shared/BackgroundEffectHelper.h" #include "DevicesSettings.h" -#include "DNDCollectionWindow.h" #include "DndData.h" #include "unity-shared/Introspectable.h" #include "LauncherModel.h" @@ -66,7 +65,7 @@ class Launcher : public unity::debug::Introspectable, NUX_DECLARE_OBJECT_TYPE(Launcher, nux::View); public: - Launcher(nux::BaseWindow* parent, nux::ObjectPtr<DNDCollectionWindow> const& collection_window, NUX_FILE_LINE_PROTO); + Launcher(nux::BaseWindow* parent, NUX_FILE_LINE_PROTO); nux::Property<Display*> display; nux::Property<int> monitor; @@ -127,6 +126,11 @@ public: int GetDragDelta() const; void SetHover(bool hovered); + void DndStarted(std::string const& mimes); + void DndFinished(); + void SetDndQuirk(); + void UnsetDndQuirk(); + sigc::signal<void, std::string const&, AbstractLauncherIcon::Ptr const&> add_request; sigc::signal<void, AbstractLauncherIcon::Ptr const&> remove_request; sigc::signal<void> selection_change; @@ -213,7 +217,6 @@ private: bool StrutHack(); bool StartIconDragTimeout(int x, int y); bool OnScrollTimeout(); - bool OnUpdateDragManagerTimeout(); void SetMousePosition(int x, int y); @@ -248,6 +251,7 @@ private: float DragOutProgress(struct timespec const& current) const; float IconDesatValue(AbstractLauncherIcon::Ptr const& icon, struct timespec const& current) const; float IconPresentProgress(AbstractLauncherIcon::Ptr const& icon, struct timespec const& current) const; + float IconUnfoldProgress(AbstractLauncherIcon::Ptr const& icon, struct timespec const& current) const; float IconUrgentProgress(AbstractLauncherIcon::Ptr const& icon, struct timespec const& current) const; float IconShimmerProgress(AbstractLauncherIcon::Ptr const& icon, struct timespec const& current) const; float IconUrgentPulseValue(AbstractLauncherIcon::Ptr const& icon, struct timespec const& current) const; @@ -322,12 +326,8 @@ private: virtual long PostLayoutManagement(long LayoutResult); - void OnDisplayChanged(Display* display); - void OnDNDDataCollected(const std::list<char*>& mimes); - void DndReset(); void DndHoveredIconReset(); - void DndTimeoutSetup(); bool DndIsSpecialRequest(std::string const& uri) const; LauncherModel::Ptr _model; @@ -383,7 +383,6 @@ private: nux::Point2 _mouse_position; nux::ObjectPtr<nux::IOpenGLBaseTexture> _offscreen_drag_texture; nux::ObjectPtr<LauncherDragWindow> _drag_window; - nux::ObjectPtr<unity::DNDCollectionWindow> _collection_window; LauncherHideMachine _hide_machine; LauncherHoverMachine _hover_machine; diff --git a/launcher/LauncherController.cpp b/launcher/LauncherController.cpp index a23498101..a257b2dff 100644 --- a/launcher/LauncherController.cpp +++ b/launcher/LauncherController.cpp @@ -95,10 +95,11 @@ GDBusInterfaceVTable Controller::Impl::interface_vtable = { Controller::Impl::OnDBusMethodCall, NULL, NULL}; -Controller::Impl::Impl(Controller* parent) +Controller::Impl::Impl(Controller* parent, XdndManager::Ptr const& xdnd_manager) : parent_(parent) , model_(std::make_shared<LauncherModel>()) , matcher_(bamf_matcher_get_default()) + , xdnd_manager_(xdnd_manager) , device_section_(std::make_shared<VolumeMonitorWrapper>(), std::make_shared<DevicesSettingsImp>()) , expo_icon_(new ExpoLauncherIcon()) , desktop_icon_(new DesktopLauncherIcon()) @@ -161,6 +162,10 @@ Controller::Impl::Impl(Controller* parent) }); parent_->AddChild(model_.get()); + + xdnd_manager_->dnd_started.connect(sigc::mem_fun(this, &Impl::OnDndStarted)); + xdnd_manager_->dnd_finished.connect(sigc::mem_fun(this, &Impl::OnDndFinished)); + xdnd_manager_->monitor_changed.connect(sigc::mem_fun(this, &Impl::OnDndMonitorChanged)); } Controller::Impl::~Impl() @@ -255,11 +260,47 @@ void Controller::Impl::OnWindowFocusChanged(guint32 xid) } } +void Controller::Impl::OnDndStarted(std::string const& data, int monitor) +{ + if (parent_->multiple_launchers) + { + last_dnd_monitor_ = monitor; + launchers[last_dnd_monitor_]->DndStarted(data); + } + else + { + launcher_->DndStarted(data); + } +} + +void Controller::Impl::OnDndFinished() +{ + if (parent_->multiple_launchers) + { + launchers[last_dnd_monitor_]->DndFinished(); + last_dnd_monitor_ = -1; + } + else + { + launcher_->DndFinished(); + } +} + +void Controller::Impl::OnDndMonitorChanged(int monitor) +{ + if (parent_->multiple_launchers) + { + launchers[last_dnd_monitor_]->UnsetDndQuirk(); + last_dnd_monitor_ = monitor; + launchers[last_dnd_monitor_]->SetDndQuirk(); + } +} + Launcher* Controller::Impl::CreateLauncher(int monitor) { nux::BaseWindow* launcher_window = new nux::BaseWindow(TEXT("LauncherWindow")); - Launcher* launcher = new Launcher(launcher_window, nux::ObjectPtr<DNDCollectionWindow>(new DNDCollectionWindow)); + Launcher* launcher = new Launcher(launcher_window); launcher->monitor = monitor; launcher->options = parent_->options(); launcher->SetModel(model_); @@ -974,10 +1015,10 @@ void Controller::Impl::SendHomeActivationRequest() g_variant_new("(sus)", "home.lens", dash::NOT_HANDLED, "")); } -Controller::Controller() +Controller::Controller(XdndManager::Ptr const& xdnd_manager) : options(Options::Ptr(new Options())) , multiple_launchers(true) - , pimpl(new Impl(this)) + , pimpl(new Impl(this, xdnd_manager)) { multiple_launchers.changed.connect([&](bool value) -> void { UScreen* uscreen = UScreen::GetDefault(); diff --git a/launcher/LauncherController.h b/launcher/LauncherController.h index ec203ec8f..72995e5ce 100644 --- a/launcher/LauncherController.h +++ b/launcher/LauncherController.h @@ -27,6 +27,7 @@ #include "LauncherOptions.h" #include "SoftwareCenterLauncherIcon.h" +#include "XdndManager.h" namespace unity { @@ -47,7 +48,7 @@ public: nux::Property<Options::Ptr> options; nux::Property<bool> multiple_launchers; - Controller(); + Controller(XdndManager::Ptr const& xdnd_manager); ~Controller(); Launcher& launcher() const; diff --git a/launcher/LauncherControllerPrivate.h b/launcher/LauncherControllerPrivate.h index 210cedeb0..6e35f182e 100644 --- a/launcher/LauncherControllerPrivate.h +++ b/launcher/LauncherControllerPrivate.h @@ -39,6 +39,7 @@ #include "SoftwareCenterLauncherIcon.h" #include "unity-shared/UBusWrapper.h" #include "VolumeMonitorWrapper.h" +#include "XdndManager.h" namespace unity { @@ -48,7 +49,7 @@ namespace launcher class Controller::Impl { public: - Impl(Controller* parent); + Impl(Controller* parent, XdndManager::Ptr const& xdnd_manager); ~Impl(); void UpdateNumWorkspaces(int workspaces); @@ -111,6 +112,10 @@ public: void OpenQuicklist(); + void OnDndStarted(std::string const& data, int monitor); + void OnDndFinished(); + void OnDndMonitorChanged(int monitor); + static void OnBusAcquired(GDBusConnection* connection, const gchar* name, gpointer user_data); static void OnDBusMethodCall(GDBusConnection* connection, const gchar* sender, const gchar* object_path, const gchar* interface_name, const gchar* method_name, @@ -124,6 +129,7 @@ public: glib::Object<BamfMatcher> matcher_; nux::ObjectPtr<Launcher> launcher_; nux::ObjectPtr<Launcher> keyboard_launcher_; + XdndManager::Ptr xdnd_manager_; DeviceLauncherSection device_section_; LauncherEntryRemoteModel remote_model_; AbstractLauncherIcon::Ptr expo_icon_; @@ -143,6 +149,7 @@ public: int reactivate_index; bool keynav_restore_window_; int launcher_key_press_time_; + int last_dnd_monitor_; unsigned dbus_owner_; GDBusConnection* gdbus_connection_; diff --git a/launcher/LauncherIcon.cpp b/launcher/LauncherIcon.cpp index 9f6bb8b55..d893a7458 100644 --- a/launcher/LauncherIcon.cpp +++ b/launcher/LauncherIcon.cpp @@ -801,6 +801,7 @@ LauncherIcon::Present(float present_urgency, int length) _present_urgency = CLAMP(present_urgency, 0.0f, 1.0f); SetQuirk(Quirk::PRESENTED, true); + SetQuirk(Quirk::UNFOLDED, true); } void @@ -811,6 +812,7 @@ LauncherIcon::Unpresent() _source_manager.Remove(PRESENT_TIMEOUT); SetQuirk(Quirk::PRESENTED, false); + SetQuirk(Quirk::UNFOLDED, false); } void diff --git a/launcher/StandaloneLauncher.cpp b/launcher/StandaloneLauncher.cpp index 04869b605..2172d2695 100644 --- a/launcher/StandaloneLauncher.cpp +++ b/launcher/StandaloneLauncher.cpp @@ -35,8 +35,8 @@ static launcher::Controller::Ptr controller; void ThreadWidgetInit(nux::NThread* thread, void* InitData) { -// launcherWindow->SetGeometry (nux::Geometry(0, 0, 300, 800)); - controller.reset(new launcher::Controller()); + auto xdnd_manager = std::make_shared<XdndManager>(); + controller = std::make_shared<launcher::Controller>(xdnd_manager); } int main(int argc, char** argv) diff --git a/launcher/XdndCollectionWindow.h b/launcher/XdndCollectionWindow.h new file mode 100644 index 000000000..ce5a5681b --- /dev/null +++ b/launcher/XdndCollectionWindow.h @@ -0,0 +1,51 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* +* Copyright (C) 2012 Canonical Ltd +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 3 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* Authored by: Andrea Azzarone <andrea.azzarone@canonical.com> +*/ + +#ifndef UNITYSHELL_XDND_COLLECTION_WINDOW_H +#define UNITYSHELL_XDND_COLLECTION_WINDOW_H + +#include <boost/noncopyable.hpp> +#include <memory> +#include <sigc++/signal.h> +#include <string> +#include <vector> + +namespace unity { + +/** + * XdndCollectionWindow makes it possible to collect drag and drop data as + * soon as dnd starts and not when the mouse pointer enter the x window. + **/ + +class XdndCollectionWindow : boost::noncopyable +{ +public: + typedef std::shared_ptr<XdndCollectionWindow> Ptr; + + virtual ~XdndCollectionWindow() {} + + virtual void Collect() = 0; + virtual void Deactivate() = 0; + + sigc::signal<void, std::vector<std::string>> collected; +}; + +} + +#endif diff --git a/launcher/XdndCollectionWindowImp.cpp b/launcher/XdndCollectionWindowImp.cpp new file mode 100644 index 000000000..e39f373a7 --- /dev/null +++ b/launcher/XdndCollectionWindowImp.cpp @@ -0,0 +1,112 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* +* Copyright (C) 2011 Canonical Ltd +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 3 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* Authored by: Andrea Azzarone <azzaronea@gmail.com> +*/ + +#include "XdndCollectionWindowImp.h" +#include "unity-shared/UScreen.h" +#include "unity-shared/WindowManager.h" + +namespace unity { +namespace { + +class PrivateWindow : public nux::BaseWindow +{ +public: + PrivateWindow(XdndCollectionWindowImp* parent) + : nux::BaseWindow("") + , parent_(parent) + { + // Make it invisible... + SetBackgroundColor(nux::color::Transparent); + SetOpacity(0.0f); + // ... and as big as the whole screen. + auto uscreen = UScreen::GetDefault(); + SetGeometry(uscreen->GetScreenGeometry()); + + ShowWindow(true); + PushToBack(); + // Hack to create the X Window as soon as possible. + EnableInputWindow(true, "XdndCollectionWindowImp"); + EnableInputWindow(false, "XdndCollectionWindowImp"); + SetDndEnabled(false, true); + + uscreen->changed.connect(sigc::mem_fun(this, &PrivateWindow::OnScreenChanged)); + WindowManager::Default().window_moved.connect(sigc::mem_fun(this, &PrivateWindow::OnWindowMoved)); + } + + void OnScreenChanged(int /*primary*/, std::vector<nux::Geometry>& /*monitors*/) + { + auto uscreen = UScreen::GetDefault(); + SetGeometry(uscreen->GetScreenGeometry()); + } + + /** + * EnableInputWindow doesn't show the window immediately. + * Since nux::EnableInputWindow uses XMoveResizeWindow the best way to know if + * the X Window is really on/off screen is receiving WindowManager::window_moved + * signal. Please don't hate me! + **/ + void OnWindowMoved(Window window_id) + { + if (G_LIKELY(window_id != GetInputWindowId())) + return; + + // Create a fake mouse move because sometimes an extra one is required. + auto display = nux::GetGraphicsDisplay()->GetX11Display(); + XWarpPointer(display, None, None, 0, 0, 0, 0, 0, 0); + XFlush(display); + } + + void ProcessDndMove(int x, int y, std::list<char*> mimes) + { + // Hide the window as soon as possible. + PushToBack(); + EnableInputWindow(false, "XdndCollectionWindowImp"); + + std::vector<std::string> data; + for (auto mime : mimes) + if (mime) data.push_back(mime); + + parent_->collected.emit(data); + } + + XdndCollectionWindowImp* parent_; +}; + +} + +XdndCollectionWindowImp::XdndCollectionWindowImp() + : window_(new PrivateWindow(this)) +{} + +void XdndCollectionWindowImp::Collect() +{ + // Using PushToFront we're sure that the window is shown over the panel window, + // the launcher window and the dash window. Don't forget to call PushToBack as + // soon as possible. + window_->PushToFront(); + window_->EnableInputWindow(true, "XdndCollectionWindowImp"); +} + +void XdndCollectionWindowImp::Deactivate() +{ + window_->PushToBack(); + window_->EnableInputWindow(false, "XdndCollectionWindowImp"); +} + +} diff --git a/launcher/XdndCollectionWindowImp.h b/launcher/XdndCollectionWindowImp.h new file mode 100644 index 000000000..bf0468471 --- /dev/null +++ b/launcher/XdndCollectionWindowImp.h @@ -0,0 +1,44 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* +* Copyright (C) 2011-2012 Canonical Ltd +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 3 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* Authored by: Andrea Azzarone <andrea.azzarone@canonical.com> +*/ + +#ifndef UNITYSHELL_XDND_COLLECTION_WINDOW_IMP_H +#define UNITYSHELL_XDND_COLLECTION_WINDOW_IMP_H + +#include "XdndCollectionWindow.h" + +#include <Nux/Nux.h> +#include <Nux/BaseWindow.h> + +namespace unity { + +class XdndCollectionWindowImp : public XdndCollectionWindow +{ +public: + XdndCollectionWindowImp(); + + void Collect(); + void Deactivate(); + +private: + nux::ObjectPtr<nux::BaseWindow> window_; +}; + +} + +#endif diff --git a/launcher/XdndManager.h b/launcher/XdndManager.h new file mode 100644 index 000000000..ffd3f1f00 --- /dev/null +++ b/launcher/XdndManager.h @@ -0,0 +1,43 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* +* Copyright (C) 2012 Canonical Ltd +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 3 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* Authored by: Andrea Azzarone <andrea.azzarone@canonical.com> +*/ + +#ifndef UNITYSHELL_XDND_MANAGER_H +#define UNITYSHELL_XDND_MANAGER_H + +#include <boost/noncopyable.hpp> +#include <memory> +#include <sigc++/signal.h> +#include <string> + +namespace unity { + +class XdndManager : boost::noncopyable { +public: + typedef std::shared_ptr<XdndManager> Ptr; + + virtual ~XdndManager() {} + + sigc::signal<void, std::string, int> dnd_started; + sigc::signal<void> dnd_finished; + sigc::signal<void, int> monitor_changed; +}; + +} + +#endif diff --git a/launcher/XdndManagerImp.cpp b/launcher/XdndManagerImp.cpp new file mode 100644 index 000000000..5bbd224e6 --- /dev/null +++ b/launcher/XdndManagerImp.cpp @@ -0,0 +1,98 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* +* Copyright (C) 2012 Canonical Ltd +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 3 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* Authored by: Andrea Azzarone <andrea.azzarone@canonical.com> +*/ + +#include "XdndManagerImp.h" + +#include "unity-shared/UScreen.h" + +namespace unity { + +XdndManagerImp::XdndManagerImp(XdndStartStopNotifier::Ptr const& xdnd_start_stop_notifier, + XdndCollectionWindow::Ptr const& xdnd_collection_window) + : xdnd_start_stop_notifier_(xdnd_start_stop_notifier) + , xdnd_collection_window_(xdnd_collection_window) + , last_monitor_(-1) + , valid_dnd_in_progress_(false) +{ + xdnd_start_stop_notifier_->started.connect(sigc::mem_fun(this, &XdndManagerImp::OnDndStarted)); + xdnd_start_stop_notifier_->finished.connect(sigc::mem_fun(this, &XdndManagerImp::OnDndFinished)); + + xdnd_collection_window_->collected.connect(sigc::mem_fun(this, &XdndManagerImp::OnDndDataCollected)); +} + +void XdndManagerImp::OnDndStarted() +{ + xdnd_collection_window_->Collect(); +} + +void XdndManagerImp::OnDndFinished() +{ + xdnd_collection_window_->Deactivate(); + mouse_poller_timeout_.reset(); + + if (valid_dnd_in_progress_) + { + valid_dnd_in_progress_ = false; + dnd_finished.emit(); + } +} + +void XdndManagerImp::OnDndDataCollected(std::vector<std::string> const& mimes) +{ + if (!IsAValidDnd(mimes)) + return; + + valid_dnd_in_progress_ = true; + + auto& gp_display = nux::GetWindowThread()->GetGraphicsDisplay(); + char target[] = "text/uri-list"; + glib::String data(gp_display.GetDndData(target)); + + auto uscreen = UScreen::GetDefault(); + last_monitor_ = uscreen->GetMonitorWithMouse(); + + mouse_poller_timeout_.reset(new glib::Timeout(20, sigc::mem_fun(this, &XdndManagerImp::CheckMousePosition))); + + dnd_started.emit(data.Str(), last_monitor_); +} + +bool XdndManagerImp::IsAValidDnd(std::vector<std::string> const& mimes) +{ + auto end = std::end(mimes); + auto it = std::find(std::begin(mimes), end, "text/uri-list"); + + return it != end; +} + +bool XdndManagerImp::CheckMousePosition() +{ + auto uscreen = UScreen::GetDefault(); + auto monitor = uscreen->GetMonitorWithMouse(); + + if (valid_dnd_in_progress_ && monitor != last_monitor_) + { + last_monitor_ = monitor; + monitor_changed.emit(last_monitor_); + } + + return true; +} + +} + diff --git a/launcher/XdndManagerImp.h b/launcher/XdndManagerImp.h new file mode 100644 index 000000000..e3e237e72 --- /dev/null +++ b/launcher/XdndManagerImp.h @@ -0,0 +1,54 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* +* Copyright (C) 2012 Canonical Ltd +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 3 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* Authored by: Andrea Azzarone <andrea.azzarone@canonical.com> +*/ + +#ifndef UNITYSHELL_XDND_MANAGER_IMP_H +#define UNITYSHELL_XDND_MANAGER_IMP_H + +#include <sigc++/trackable.h> + +#include "XdndManager.h" + +#include "XdndCollectionWindow.h" +#include "XdndStartStopNotifier.h" +#include "UnityCore/GLibSource.h" + +namespace unity { + +class XdndManagerImp : public XdndManager, public sigc::trackable { +public: + XdndManagerImp(XdndStartStopNotifier::Ptr const&, XdndCollectionWindow::Ptr const&); + +private: + void OnDndStarted(); + void OnDndFinished(); + void OnDndDataCollected(std::vector<std::string> const& mimes); + bool IsAValidDnd(std::vector<std::string> const& mimes); + bool CheckMousePosition(); + + XdndStartStopNotifier::Ptr xdnd_start_stop_notifier_; + XdndCollectionWindow::Ptr xdnd_collection_window_; + int last_monitor_; + bool valid_dnd_in_progress_; + + glib::Source::UniquePtr mouse_poller_timeout_; +}; + +} + +#endif diff --git a/launcher/XdndStartStopNotifier.cpp b/launcher/XdndStartStopNotifier.cpp new file mode 100644 index 000000000..18d7119cc --- /dev/null +++ b/launcher/XdndStartStopNotifier.cpp @@ -0,0 +1,27 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* +* Copyright (C) 2012 Canonical Ltd +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 3 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* Authored by: Andrea Azzarone <andrea.azzarone@canonical.com> +*/ + +#include "XdndStartStopNotifier.h" + +namespace unity { + +XdndStartStopNotifier::~XdndStartStopNotifier() +{} + +} \ No newline at end of file diff --git a/launcher/XdndStartStopNotifier.h b/launcher/XdndStartStopNotifier.h new file mode 100644 index 000000000..ed5d5087d --- /dev/null +++ b/launcher/XdndStartStopNotifier.h @@ -0,0 +1,41 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* +* Copyright (C) 2012 Canonical Ltd +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 3 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* Authored by: Andrea Azzarone <andrea.azzarone@canonical.com> +*/ + +#ifndef UNITYSHELL_XDND_START_STOP_NOTIFIER_H +#define UNITYSHELL_XDND_START_STOP_NOTIFIER_H + +#include <boost/noncopyable.hpp> +#include <memory> +#include <sigc++/signal.h> + +namespace unity { + +class XdndStartStopNotifier : boost::noncopyable { +public: + typedef std::shared_ptr<XdndStartStopNotifier> Ptr; + + virtual ~XdndStartStopNotifier() = 0; + + sigc::signal<void> started; + sigc::signal<void> finished; +}; + +} + +#endif diff --git a/launcher/XdndStartStopNotifierImp.cpp b/launcher/XdndStartStopNotifierImp.cpp new file mode 100644 index 000000000..19201ff55 --- /dev/null +++ b/launcher/XdndStartStopNotifierImp.cpp @@ -0,0 +1,77 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* +* Copyright (C) 2012 Canonical Ltd +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 3 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* Authored by: Andrea Azzarone <andrea.azzarone@canonical.com> +*/ + +#include "XdndStartStopNotifierImp.h" + +#include <Nux/Nux.h> + +#include "unity-shared/WindowManager.h" + +namespace unity { + +XdndStartStopNotifierImp::XdndStartStopNotifierImp() + : display_(nux::GetGraphicsDisplay()->GetX11Display()) + , selection_(XInternAtom(display_, "XdndSelection", false)) + , dnd_in_progress_(false) +{ + WindowManager& wm = WindowManager::Default(); + wm.window_mapped.connect(sigc::hide(sigc::mem_fun(this, &XdndStartStopNotifierImp::DndTimeoutSetup))); + wm.window_unmapped.connect(sigc::hide(sigc::mem_fun(this, &XdndStartStopNotifierImp::DndTimeoutSetup))); + } + +void XdndStartStopNotifierImp::DndTimeoutSetup() +{ + if (timeout_ && timeout_->IsRunning()) + return; + + auto cb_func = sigc::mem_fun(this, &XdndStartStopNotifierImp::OnTimeout); + timeout_.reset(new glib::Timeout(200, cb_func)); +} + +bool XdndStartStopNotifierImp::OnTimeout() +{ + Window drag_owner = XGetSelectionOwner(display_, selection_); + + // evil hack because Qt does not release the selction owner on drag finished + Window root_r, child_r; + int root_x_r, root_y_r, win_x_r, win_y_r; + unsigned int mask; + XQueryPointer(display_, DefaultRootWindow(display_), &root_r, &child_r, &root_x_r, &root_y_r, &win_x_r, &win_y_r, &mask); + + if (drag_owner && (mask & (Button1Mask | Button2Mask | Button3Mask))) + { + if (!dnd_in_progress_) + { + started.emit(); + dnd_in_progress_ = true; + } + + return true; + } + + if (dnd_in_progress_) + { + finished.emit(); + dnd_in_progress_ = false; + } + + return false; +} + +} diff --git a/launcher/XdndStartStopNotifierImp.h b/launcher/XdndStartStopNotifierImp.h new file mode 100644 index 000000000..d51cfd53d --- /dev/null +++ b/launcher/XdndStartStopNotifierImp.h @@ -0,0 +1,47 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* +* Copyright (C) 2012 Canonical Ltd +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 3 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* Authored by: Andrea Azzarone <andrea.azzarone@canonical.com> +*/ + +#ifndef UNITYSHELL_XDND_START_STOP_NOTIFIER_IMP_H +#define UNITYSHELL_XDND_START_STOP_NOTIFIER_IMP_H + +#include "XdndStartStopNotifier.h" + +#include <UnityCore/GLibSource.h> +#include <X11/Xlib.h> + +namespace unity { + +class XdndStartStopNotifierImp : public XdndStartStopNotifier { +public: + XdndStartStopNotifierImp(); + +private: + void DndTimeoutSetup(); + bool OnTimeout(); + + Display* display_; + Atom selection_; + bool dnd_in_progress_; + + glib::Source::UniquePtr timeout_; +}; + +} + +#endif \ No newline at end of file diff --git a/plugins/unityshell/src/unitya11ytests.cpp b/plugins/unityshell/src/unitya11ytests.cpp index 1ec2c1af0..425a32468 100644 --- a/plugins/unityshell/src/unitya11ytests.cpp +++ b/plugins/unityshell/src/unitya11ytests.cpp @@ -210,7 +210,7 @@ a11y_unit_test_launcher_connection(void) AtkObject* launcher_icon_accessible = NULL; window = new nux::BaseWindow(TEXT("")); - launcher = new Launcher(window, nux::ObjectPtr<unity::DNDCollectionWindow>(new unity::DNDCollectionWindow), NULL); + launcher = new Launcher(window, NULL); launcher->SinkReference(); launcher_accessible = unity_a11y_get_accessible(launcher); diff --git a/plugins/unityshell/src/unityshell.cpp b/plugins/unityshell/src/unityshell.cpp index fdfa714bf..e3181452f 100644 --- a/plugins/unityshell/src/unityshell.cpp +++ b/plugins/unityshell/src/unityshell.cpp @@ -41,6 +41,9 @@ #include "unityshell.h" #include "BackgroundEffectHelper.h" #include "UnityGestureBroker.h" +#include "launcher/XdndCollectionWindowImp.h" +#include "launcher/XdndManagerImp.h" +#include "launcher/XdndStartStopNotifierImp.h" #include <glib/gi18n-lib.h> #include <gtk/gtk.h> @@ -3096,7 +3099,12 @@ void UnityScreen::OnDashRealized () void UnityScreen::initLauncher() { Timer timer; - launcher_controller_ = std::make_shared<launcher::Controller>(); + + auto xdnd_collection_window = std::make_shared<XdndCollectionWindowImp>(); + auto xdnd_start_stop_notifier = std::make_shared<XdndStartStopNotifierImp>(); + auto xdnd_manager = std::make_shared<XdndManagerImp>(xdnd_start_stop_notifier, xdnd_collection_window); + + launcher_controller_ = std::make_shared<launcher::Controller>(xdnd_manager); AddChild(launcher_controller_.get()); switcher_controller_ = std::make_shared<switcher::Controller>(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5b36e4a3d..7f35cdebb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -251,6 +251,8 @@ if (ENABLE_X_SUPPORT) test_unity_settings.cpp test_volume_imp.cpp test_volume_launcher_icon.cpp + test_xdnd_manager_imp.cpp + test_xdnd_start_stop_notifier_imp.cpp bamf-mock-application.c gmockmount.c gmockvolume.c diff --git a/tests/test_launcher.cpp b/tests/test_launcher.cpp index d41a1ece3..9e76866a2 100644 --- a/tests/test_launcher.cpp +++ b/tests/test_launcher.cpp @@ -26,7 +26,6 @@ using namespace testing; #include <Nux/Nux.h> #include <Nux/BaseWindow.h> -#include "launcher/DNDCollectionWindow.h" #include "launcher/MockLauncherIcon.h" #include "launcher/Launcher.h" #include "unity-shared/PanelStyle.h" @@ -63,8 +62,8 @@ public: class MockLauncher : public Launcher { public: - MockLauncher(nux::BaseWindow* parent, nux::ObjectPtr<DNDCollectionWindow> const& collection_window) - : Launcher(parent, collection_window) + MockLauncher(nux::BaseWindow* parent) + : Launcher(parent) {} AbstractLauncherIcon::Ptr MouseIconIntersection(int x, int y) const @@ -119,10 +118,9 @@ public: TestLauncher() : parent_window_(new nux::BaseWindow("TestLauncherWindow")) - , dnd_collection_window_(new DNDCollectionWindow) , model_(new LauncherModel) , options_(new Options) - , launcher_(new MockLauncher(parent_window_, dnd_collection_window_)) + , launcher_(new MockLauncher(parent_window_)) { launcher_->options = options_; launcher_->SetModel(model_); @@ -153,7 +151,6 @@ public: MockUScreen uscreen; nux::BaseWindow* parent_window_; - nux::ObjectPtr<DNDCollectionWindow> dnd_collection_window_; Settings settings; panel::Style panel_style; LauncherModel::Ptr model_; @@ -189,9 +186,7 @@ TEST_F(TestLauncher, TestQuirksDuringDnd) EXPECT_CALL(*third, ShouldHighlightOnDrag(_)) .WillRepeatedly(Return(false)); - std::list<char*> uris; - dnd_collection_window_->collected.emit(uris); - + launcher_->DndStarted(""); Utils::WaitForTimeout(1); EXPECT_FALSE(first->GetQuirk(launcher::AbstractLauncherIcon::Quirk::DESAT)); diff --git a/tests/test_launcher_controller.cpp b/tests/test_launcher_controller.cpp index 8f033b9ba..516125484 100644 --- a/tests/test_launcher_controller.cpp +++ b/tests/test_launcher_controller.cpp @@ -200,6 +200,11 @@ namespace launcher { struct TestLauncherController : public testing::Test { + TestLauncherController() + : xdnd_manager_(std::make_shared<XdndManager>()) + , lc(xdnd_manager_) + {} + virtual void SetUp() { lc.multiple_launchers = true; @@ -216,6 +221,10 @@ struct TestLauncherController : public testing::Test protected: struct MockLauncherController : Controller { + MockLauncherController(XdndManager::Ptr const& xdnd_manager) + : Controller(xdnd_manager) + {} + Controller::Impl* Impl() const { return pimpl.get(); } AbstractLauncherIcon::Ptr GetIconByDesktop(std::string const& path) const @@ -258,6 +267,7 @@ protected: Settings settings; panel::Style panel_style; MockFavoriteStore favorite_store; + XdndManager::Ptr xdnd_manager_; MockLauncherController lc; }; } @@ -1544,4 +1554,50 @@ TEST_F(TestLauncherController, UpdateSelectionChanged) ASSERT_EQ(lc.Impl()->model_->Selection()->tooltip_text(), last_selection_change); } +TEST_F(TestLauncherController, DragAndDrop_MultipleLaunchers) +{ + lc.multiple_launchers = true; + uscreen.SetupFakeMultiMonitor(); + lc.options()->hide_mode = LAUNCHER_HIDE_AUTOHIDE; + + auto check_fn = [this](int index) { + return lc.launchers()[index]->Hidden(); + }; + + xdnd_manager_->dnd_started.emit("my_awesome_file", 0); + + for (int i = 0; i < max_num_monitors; ++i) + Utils::WaitUntil(std::bind(check_fn, i), i != 0); + + xdnd_manager_->monitor_changed.emit(3); + + for (int i = 0; i < max_num_monitors; ++i) + Utils::WaitUntil(std::bind(check_fn, i), i != 3); + + xdnd_manager_->dnd_finished.emit(); + + for (int i = 0; i < max_num_monitors; ++i) + Utils::WaitUntil(std::bind(check_fn, i), true); +} + +TEST_F(TestLauncherController, DragAndDrop_SingleLauncher) +{ + lc.multiple_launchers = false; + uscreen.SetupFakeMultiMonitor(2); + lc.options()->hide_mode = LAUNCHER_HIDE_AUTOHIDE; + + auto check_fn = [this]() { + return lc.launcher().Hidden(); + }; + + xdnd_manager_->dnd_started.emit("my_awesome_file", 0); + Utils::WaitUntil(check_fn, false); + + xdnd_manager_->monitor_changed.emit(2); + Utils::WaitUntil(check_fn, false); + + xdnd_manager_->dnd_finished.emit(); + Utils::WaitUntil(check_fn, true); +} + } diff --git a/tests/test_xdnd_manager_imp.cpp b/tests/test_xdnd_manager_imp.cpp new file mode 100644 index 000000000..1e4f3a8ab --- /dev/null +++ b/tests/test_xdnd_manager_imp.cpp @@ -0,0 +1,130 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* +* Copyright (C) 2012 Canonical Ltd +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 3 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* Authored by: Andrea Azzarone <andrea.azzarone@canonical.com> +*/ + +#include <gmock/gmock.h> +using namespace testing; + +#include "launcher/XdndManagerImp.h" +#include "launcher/XdndCollectionWindow.h" +#include "launcher/XdndStartStopNotifier.h" + +#include "test_utils.h" + +#include <Nux/Nux.h> +#include <X11/Xlib.h> + +namespace { + +class MockXdndStartStopNotifier : public unity::XdndStartStopNotifier { +public: + typedef std::shared_ptr<MockXdndStartStopNotifier> Ptr; +}; + +class MockXdndCollectionWindow : public unity::XdndCollectionWindow { +public: + typedef std::shared_ptr<MockXdndCollectionWindow> Ptr; + + MOCK_METHOD0(Collect, void(void)); + MOCK_METHOD0(Deactivate, void(void)); +}; + +class TestXdndManager : public Test { +public: + TestXdndManager() + : xdnd_start_stop_notifier_(new MockXdndStartStopNotifier()) + , xdnd_collection_window_(new MockXdndCollectionWindow()) + , xdnd_manager(xdnd_start_stop_notifier_, xdnd_collection_window_) + {} + + void SetUp() + { + // Evil hack to avoid crashes. + XEvent xevent; + xevent.type = ClientMessage; + xevent.xany.display = nux::GetGraphicsDisplay()->GetX11Display(); + xevent.xclient.message_type = XInternAtom(xevent.xany.display, "XdndEnter", false); + xevent.xclient.data.l[1] = 5 >> 24; + + nux::GetGraphicsDisplay()->ProcessXEvent(xevent, true); + } + + MockXdndStartStopNotifier::Ptr xdnd_start_stop_notifier_; + MockXdndCollectionWindow::Ptr xdnd_collection_window_; + unity::XdndManagerImp xdnd_manager; +}; + +TEST_F(TestXdndManager, SignalDndStartedAndFinished) +{ + std::vector<std::string> mimes; + mimes.push_back("text/uri-list"); + mimes.push_back("hello/world"); + + auto emit_collected_signal = [&] () { + xdnd_collection_window_->collected.emit(mimes); + }; + + EXPECT_CALL(*xdnd_collection_window_, Collect()) + .Times(1) + .WillOnce(Invoke(emit_collected_signal)); + + bool dnd_started_emitted = false; + xdnd_manager.dnd_started.connect([&] (std::string const& /*data*/, int /*monitor*/) { + dnd_started_emitted = true; + }); + + xdnd_start_stop_notifier_->started.emit(); + Utils::WaitUntil(dnd_started_emitted); + + EXPECT_CALL(*xdnd_collection_window_, Deactivate()) + .Times(1); + + bool dnd_finished_emitted = false; + xdnd_manager.dnd_finished.connect([&] () { + dnd_finished_emitted = true; + }); + + xdnd_start_stop_notifier_->finished.emit(); + Utils::WaitUntil(dnd_finished_emitted); +} + +TEST_F(TestXdndManager, SignalDndStarted_InvalidMimes) +{ + std::vector<std::string> mimes; + mimes.push_back("hello/world"); + mimes.push_back("invalid/mimes"); + + auto emit_collected_signal = [&] () { + xdnd_collection_window_->collected.emit(mimes); + }; + + EXPECT_CALL(*xdnd_collection_window_, Collect()) + .Times(1) + .WillOnce(Invoke(emit_collected_signal)); + + bool dnd_started_emitted = false; + xdnd_manager.dnd_started.connect([&] (std::string const& /*data*/, int /*monitor*/) { + dnd_started_emitted = true; + }); + + xdnd_start_stop_notifier_->started.emit(); + + EXPECT_FALSE(dnd_started_emitted); +} + +} diff --git a/tests/test_xdnd_start_stop_notifier_imp.cpp b/tests/test_xdnd_start_stop_notifier_imp.cpp new file mode 100644 index 000000000..621010447 --- /dev/null +++ b/tests/test_xdnd_start_stop_notifier_imp.cpp @@ -0,0 +1,107 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* +* Copyright (C) 2012 Canonical Ltd +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 3 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* Authored by: Andrea Azzarone <andrea.azzarone@canonical.com> +*/ + +#include <gmock/gmock.h> +using namespace testing; + +#include "XdndStartStopNotifierImp.h" + +#include <Nux/Nux.h> +#include <X11/Xlib.h> +//#include <X11/extensions/XTest.h> + +#include "unity-shared/WindowManager.h" +#include "test_utils.h" + +namespace { + +struct TestXdndStartStopNotifierImp : public Test { + TestXdndStartStopNotifierImp() + : display_(nux::GetGraphicsDisplay()->GetX11Display()) + , selection_(XInternAtom(display_, "XdndSelection", false)) + { + Window root = DefaultRootWindow(display_); + owner_= XCreateSimpleWindow(display_, root, -1000, -1000, 10, 10, 0, 0, 0); + } + + Display* display_; + Atom selection_; + Window owner_; + + unity::XdndStartStopNotifierImp xdnd_start_stop_notifier; +}; + +TEST_F(TestXdndStartStopNotifierImp, DISABLED_SignalStarted) +{ + bool signal_received = false; + xdnd_start_stop_notifier.started.connect([&](){ + signal_received = true; + }); + + XSetSelectionOwner(display_, selection_, owner_, CurrentTime); + //XTestFakeButtonEvent(display_, 1, True, CurrentTime); + auto& wm = unity::WindowManager::Default(); + wm.window_mapped.emit(0); + + Utils::WaitUntil(signal_received); + //XTestFakeButtonEvent(display_, 1, False, CurrentTime); +} + +TEST_F(TestXdndStartStopNotifierImp, DISABLED_SignalFinished) +{ + bool signal_received = false; + xdnd_start_stop_notifier.finished.connect([&](){ + signal_received = true; + }); + + XSetSelectionOwner(display_, selection_, owner_, CurrentTime); + //XTestFakeButtonEvent(display_, 1, True, CurrentTime); + auto& wm = unity::WindowManager::Default(); + wm.window_mapped.emit(0); + + Utils::WaitForTimeoutMSec(500); + + XSetSelectionOwner(display_, selection_, None, CurrentTime); + //XTestFakeButtonEvent(display_, 1, False, CurrentTime); + wm.window_unmapped.emit(0); + + Utils::WaitUntil(signal_received); +} + +TEST_F(TestXdndStartStopNotifierImp, DISABLED_SignalFinished_QT) +{ + bool signal_received = false; + xdnd_start_stop_notifier.finished.connect([&](){ + signal_received = true; + }); + + XSetSelectionOwner(display_, selection_, owner_, CurrentTime); + //XTestFakeButtonEvent(display_, 1, True, CurrentTime); + auto& wm = unity::WindowManager::Default(); + wm.window_mapped.emit(0); + + Utils::WaitForTimeoutMSec(500); + + //XTestFakeButtonEvent(display_, 1, False, CurrentTime); + wm.window_unmapped.emit(0); + + Utils::WaitUntil(signal_received); +} + +} diff --git a/unity-shared/UScreen.cpp b/unity-shared/UScreen.cpp index 4faa1bc56..6812a8325 100644 --- a/unity-shared/UScreen.cpp +++ b/unity-shared/UScreen.cpp @@ -89,6 +89,13 @@ std::vector<nux::Geometry>& UScreen::GetMonitors() return monitors_; } +nux::Geometry UScreen::GetScreenGeometry() +{ + int width = gdk_screen_get_width(screen_); + int height = gdk_screen_get_height(screen_); + return nux::Geometry(0, 0, width, height); +} + void UScreen::Changed(GdkScreen* screen) { if (refresh_idle_) diff --git a/unity-shared/UScreen.h b/unity-shared/UScreen.h index f66e07d7a..ff33d8e0a 100644 --- a/unity-shared/UScreen.h +++ b/unity-shared/UScreen.h @@ -46,6 +46,7 @@ public: nux::Geometry& GetMonitorGeometry(int monitor); std::vector<nux::Geometry>& GetMonitors(); + nux::Geometry GetScreenGeometry(); // <void, primary_monitor, monitors> sigc::signal<void, int, std::vector<nux::Geometry>&> changed; diff --git a/unity-standalone/StandaloneUnity.cpp b/unity-standalone/StandaloneUnity.cpp index ebc649a96..593af9f61 100644 --- a/unity-standalone/StandaloneUnity.cpp +++ b/unity-standalone/StandaloneUnity.cpp @@ -86,9 +86,10 @@ UnityStandalone::~UnityStandalone () void UnityStandalone::Init () { - launcher_controller.reset(new launcher::Controller()); - panel_controller.reset(new panel::Controller()); - dash_controller.reset(new dash::Controller()); + auto xdnd_manager = std::make_shared<XdndManager>(); + launcher_controller = std::make_shared<launcher::Controller>(xdnd_manager); + panel_controller = std::make_shared<panel::Controller>(); + dash_controller = std::make_shared<dash::Controller>(); dash_controller->launcher_width = launcher_controller->launcher().GetAbsoluteWidth() - 1; panel_controller->launcher_width = launcher_controller->launcher().GetAbsoluteWidth() - 1; @@ -119,8 +120,9 @@ UnityStandaloneTV::~UnityStandaloneTV() {}; void UnityStandaloneTV::Init() { - launcher_controller.reset(new launcher::Controller()); - dash_controller.reset(new dash::Controller()); + auto xdnd_manager = std::make_shared<XdndManager>(); + launcher_controller = std::make_shared<launcher::Controller>(xdnd_manager); + dash_controller = std::make_shared<dash::Controller>(); dash_controller->launcher_width = launcher_controller->launcher().GetAbsoluteWidth() - 1; UBusManager().SendMessage(UBUS_DASH_EXTERNAL_ACTIVATION, nullptr); |
