// -*- 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 . * * Authored by: Jason Smith * Marco Trevisan * Andrea Azzarone */ #include "EdgeBarrierController.h" #include "EdgeBarrierControllerPrivate.h" #include "Decaymulator.h" #include #include "unity-shared/UnitySettings.h" #include "unity-shared/UScreen.h" #include "unity-shared/InputMonitor.h" #include "UnityCore/GLibSource.h" namespace unity { namespace ui { namespace { int const Y_BREAK_BUFFER = 20; int const X_BREAK_BUFFER = 20; } EdgeBarrierController::Impl::Impl(EdgeBarrierController *parent) : edge_overcome_pressure_(0) , parent_(parent) { UScreen *uscreen = UScreen::GetDefault(); auto monitors = uscreen->GetMonitors(); ResizeBarrierList(monitors); uscreen->changed.connect(sigc::track_obj([this] (int primary, std::vector const& layout) { ResetBarriers(); }, *this)); parent_->force_disable.changed.connect(sigc::track_obj([this] (bool) { ResetBarriers(); }, *this)); Settings::Instance().launcher_position.changed.connect(sigc::track_obj([this] (LauncherPosition) { ResetBarriers(); }, *this)); parent_->sticky_edges.SetGetterFunction([this] { return parent_->options() ? parent_->options()->edge_resist() : false; }); parent_->sticky_edges.SetSetterFunction([this] (bool const& new_val) { if (parent_->options() && new_val != parent_->options()->edge_resist()) { parent_->options()->edge_resist = new_val; return true; } return false; }); parent_->options.changed.connect([this](launcher::Options::Ptr options) { options->option_changed.connect(sigc::track_obj([this] { ResetBarriers(); }, *this)); ResetBarriers(); }); } EdgeBarrierController::Impl::~Impl() { nux::GetGraphicsDisplay()->RemoveEventFilter(this); } void EdgeBarrierController::Impl::ResetBarriers() { auto const& monitors = UScreen::GetDefault()->GetMonitors(); ResizeBarrierList(monitors); SetupBarriers(monitors); } void EdgeBarrierController::Impl::AddSubscriber(EdgeBarrierSubscriber* subscriber, unsigned int monitor, std::vector& subscribers) { if (monitor >= subscribers.size()) subscribers.resize(monitor + 1); subscribers[monitor] = subscriber; ResetBarriers(); } void EdgeBarrierController::Impl::RemoveSubscriber(EdgeBarrierSubscriber* subscriber, unsigned int monitor, std::vector& subscribers) { if (monitor >= subscribers.size() || subscribers[monitor] != subscriber) return; subscribers[monitor] = nullptr; ResetBarriers(); } void EdgeBarrierController::Impl::ResizeBarrierList(std::vector const& layout) { if (parent_->force_disable) { vertical_barriers_.clear(); horizontal_barriers_.clear(); return; } auto num_monitors = layout.size(); if (vertical_barriers_.size() > num_monitors) vertical_barriers_.resize(num_monitors); if (horizontal_barriers_.size() > num_monitors) horizontal_barriers_.resize(num_monitors); while (vertical_barriers_.size() < num_monitors) { auto barrier = std::make_shared(); barrier->orientation = VERTICAL; barrier->barrier_event.connect(sigc::mem_fun(this, &EdgeBarrierController::Impl::OnPointerBarrierEvent)); vertical_barriers_.push_back(barrier); } while (horizontal_barriers_.size() < num_monitors) { auto barrier = std::make_shared(); barrier->orientation = HORIZONTAL; barrier->barrier_event.connect(sigc::mem_fun(this, &EdgeBarrierController::Impl::OnPointerBarrierEvent)); horizontal_barriers_.push_back(barrier); } } void EdgeBarrierController::Impl::SetupBarriers(std::vector const& layout) { if (parent_->force_disable()) return; size_t monitors_size = layout.size(); auto launcher_position = Settings::Instance().launcher_position(); bool edge_resist = parent_->sticky_edges(); bool needs_barrier = edge_resist && monitors_size > 1; bool needs_vertical_barrier = needs_barrier; if (parent_->options()->hide_mode() != launcher::LauncherHideMode::LAUNCHER_HIDE_NEVER) needs_vertical_barrier = true; for (unsigned i = 0; i < layout.size(); ++i) { auto const& vertical_barrier = vertical_barriers_[i]; auto const& horizontal_barrier = horizontal_barriers_[i]; auto const& monitor = layout[i]; vertical_barrier->DestroyBarrier(); horizontal_barrier->DestroyBarrier(); if (needs_barrier) { horizontal_barrier->x1 = monitor.x; horizontal_barrier->x2 = monitor.x + monitor.width; horizontal_barrier->y1 = monitor.y; horizontal_barrier->y2 = monitor.y; horizontal_barrier->index = i; horizontal_barrier->direction = UP; horizontal_barrier->threshold = parent_->options()->edge_stop_velocity(); horizontal_barrier->max_velocity_multiplier = parent_->options()->edge_responsiveness(); horizontal_barrier->ConstructBarrier(); } if (!needs_vertical_barrier) continue; if (launcher_position == LauncherPosition::LEFT) { vertical_barrier->x1 = monitor.x; vertical_barrier->x2 = monitor.x; vertical_barrier->y1 = monitor.y; vertical_barrier->y2 = monitor.y + monitor.height; } else { vertical_barrier->x1 = monitor.x; vertical_barrier->x2 = monitor.x + monitor.width; vertical_barrier->y1 = monitor.y + monitor.height; vertical_barrier->y2 = monitor.y + monitor.height; vertical_barrier->direction = DOWN; } vertical_barrier->index = i; vertical_barrier->threshold = parent_->options()->edge_stop_velocity(); vertical_barrier->max_velocity_multiplier = parent_->options()->edge_responsiveness(); vertical_barrier->ConstructBarrier(); } if (needs_barrier || needs_vertical_barrier) input::Monitor::Get().RegisterClient(input::Events::BARRIER, sigc::mem_fun(this, &Impl::HandleEvent)); else input::Monitor::Get().UnregisterClient(sigc::mem_fun(this, &Impl::HandleEvent)); float decay_responsiveness_mult = ((parent_->options()->edge_responsiveness() - 1) * .3f) + 1; decaymulator_.rate_of_decay = parent_->options()->edge_decay_rate() * decay_responsiveness_mult; float overcome_responsiveness_mult = ((parent_->options()->edge_responsiveness() - 1) * 1.0f) + 1; edge_overcome_pressure_ = parent_->options()->edge_overcome_pressure() * overcome_responsiveness_mult; } void EdgeBarrierController::Impl::HandleEvent(XEvent const& xevent) { if (xevent.xcookie.evtype != XI_BarrierHit) return; auto* barrier_event = reinterpret_cast(xevent.xcookie.data); PointerBarrierWrapper::Ptr const& wrapper = FindBarrierEventOwner(barrier_event); if (wrapper) wrapper->HandleBarrierEvent(barrier_event); } PointerBarrierWrapper::Ptr EdgeBarrierController::Impl::FindBarrierEventOwner(XIBarrierEvent* barrier_event) { for (auto const& barrier : vertical_barriers_) if (barrier->OwnsBarrierEvent(barrier_event->barrier)) return barrier; for (auto const& barrier : horizontal_barriers_) if (barrier->OwnsBarrierEvent(barrier_event->barrier)) return barrier; return nullptr; } void EdgeBarrierController::Impl::BarrierReset() { decaymulator_.value = 0; } void EdgeBarrierController::Impl::BarrierPush(PointerBarrierWrapper::Ptr const& owner, BarrierEvent::Ptr const& event) { if ((owner->orientation == VERTICAL and EventIsInsideYBreakZone(event)) or (owner->orientation == HORIZONTAL and EventIsInsideXBreakZone(event))) { decaymulator_.value = decaymulator_.value + event->velocity; } else { BarrierReset(); } if (decaymulator_.value > edge_overcome_pressure_) { BarrierRelease(owner, event->event_id); } } bool EdgeBarrierController::Impl::EventIsInsideYBreakZone(BarrierEvent::Ptr const& event) { static int y_break_zone = event->y; if (decaymulator_.value <= 0) y_break_zone = event->y; if (event->y <= y_break_zone + Y_BREAK_BUFFER && event->y >= y_break_zone - Y_BREAK_BUFFER) { return true; } return false; } bool EdgeBarrierController::Impl::EventIsInsideXBreakZone(BarrierEvent::Ptr const& event) { static int x_break_zone = event->y; if (decaymulator_.value <= 0) x_break_zone = event->x; if (event->x <= x_break_zone + X_BREAK_BUFFER && event->x >= x_break_zone - X_BREAK_BUFFER) { return true; } return false; } void EdgeBarrierController::Impl::OnPointerBarrierEvent(PointerBarrierWrapper::Ptr const& owner, BarrierEvent::Ptr const& event) { if (owner->released) { BarrierRelease(owner, event->event_id); return; } unsigned int monitor = owner->index; auto orientation = owner->orientation(); auto result = EdgeBarrierSubscriber::Result::NEEDS_RELEASE; auto subscribers = orientation == VERTICAL ? vertical_subscribers_ : horizontal_subscribers_ ; if (monitor < subscribers.size()) { auto subscriber = subscribers[monitor]; if (subscriber) result = subscriber->HandleBarrierEvent(owner, event); } switch (result) { case EdgeBarrierSubscriber::Result::HANDLED: BarrierReset(); break; case EdgeBarrierSubscriber::Result::ALREADY_HANDLED: BarrierPush(owner, event); break; case EdgeBarrierSubscriber::Result::IGNORED: if (parent_->sticky_edges()) { BarrierPush(owner, event); } else { owner->release_once = true; BarrierRelease(owner, event->event_id); } break; case EdgeBarrierSubscriber::Result::NEEDS_RELEASE: BarrierRelease(owner, event->event_id); break; } } void EdgeBarrierController::Impl::BarrierRelease(PointerBarrierWrapper::Ptr const& owner, int event) { owner->ReleaseBarrier(event); owner->released = true; BarrierReset(); if (!owner->release_once() || (owner->release_once() && (!release_timeout_ || !release_timeout_->IsRunning()))) { unsigned duration = parent_->options()->edge_passed_disabled_ms; std::weak_ptr owner_weak(owner); release_timeout_.reset(new glib::Timeout(duration, [owner_weak] { if (PointerBarrierWrapper::Ptr const& owner = owner_weak.lock()) { owner->released = false; owner->release_once = false; } return false; })); } } EdgeBarrierController::EdgeBarrierController() : force_disable(false) , pimpl(new Impl(this)) {} EdgeBarrierController::~EdgeBarrierController() {} void EdgeBarrierController::AddVerticalSubscriber(EdgeBarrierSubscriber* subscriber, unsigned int monitor) { pimpl->AddSubscriber(subscriber, monitor, pimpl->vertical_subscribers_); } void EdgeBarrierController::RemoveVerticalSubscriber(EdgeBarrierSubscriber* subscriber, unsigned int monitor) { pimpl->RemoveSubscriber(subscriber, monitor, pimpl->vertical_subscribers_); } void EdgeBarrierController::AddHorizontalSubscriber(EdgeBarrierSubscriber* subscriber, unsigned int monitor) { pimpl->AddSubscriber(subscriber, monitor, pimpl->horizontal_subscribers_); } void EdgeBarrierController::RemoveHorizontalSubscriber(EdgeBarrierSubscriber* subscriber, unsigned int monitor) { pimpl->RemoveSubscriber(subscriber, monitor, pimpl->horizontal_subscribers_); } EdgeBarrierSubscriber* EdgeBarrierController::GetVerticalSubscriber(unsigned int monitor) { if (monitor >= pimpl->vertical_subscribers_.size()) return nullptr; return pimpl->vertical_subscribers_[monitor]; } EdgeBarrierSubscriber* EdgeBarrierController::GetHorizontalSubscriber(unsigned int monitor) { if (monitor >= pimpl->horizontal_subscribers_.size()) return nullptr; return pimpl->horizontal_subscribers_[monitor]; } } }