mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 19:27:44 +00:00
Services: Move to Userland/Services/
This commit is contained in:
parent
4055b03291
commit
c7ac7e6eaf
170 changed files with 4 additions and 4 deletions
141
Userland/Services/WindowServer/AppletManager.cpp
Normal file
141
Userland/Services/WindowServer/AppletManager.cpp
Normal file
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "AppletManager.h"
|
||||
#include <AK/QuickSort.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <WindowServer/MenuManager.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
static AppletManager* s_the;
|
||||
Vector<String> order_vector;
|
||||
|
||||
AppletManager::AppletManager()
|
||||
{
|
||||
s_the = this;
|
||||
|
||||
auto wm_config = Core::ConfigFile::open("/etc/WindowServer/WindowServer.ini");
|
||||
auto order = wm_config->read_entry("Applet", "Order");
|
||||
order_vector = order.split(',');
|
||||
}
|
||||
|
||||
AppletManager::~AppletManager()
|
||||
{
|
||||
}
|
||||
|
||||
AppletManager& AppletManager::the()
|
||||
{
|
||||
ASSERT(s_the);
|
||||
return *s_the;
|
||||
}
|
||||
|
||||
void AppletManager::event(Core::Event& event)
|
||||
{
|
||||
auto& mouse_event = static_cast<MouseEvent&>(event);
|
||||
|
||||
for (auto& applet : m_applets) {
|
||||
if (!applet)
|
||||
continue;
|
||||
if (!applet->rect_in_menubar().contains(mouse_event.position()))
|
||||
continue;
|
||||
auto local_event = mouse_event.translated(-applet->rect_in_menubar().location());
|
||||
applet->event(local_event);
|
||||
}
|
||||
}
|
||||
|
||||
void AppletManager::add_applet(Window& applet)
|
||||
{
|
||||
m_applets.append(applet);
|
||||
|
||||
// Prune any dead weak pointers from the applet list.
|
||||
m_applets.remove_all_matching([](auto& entry) {
|
||||
return entry.is_null();
|
||||
});
|
||||
|
||||
quick_sort(m_applets, [](auto& a, auto& b) {
|
||||
auto index_a = order_vector.find_first_index(a->title());
|
||||
auto index_b = order_vector.find_first_index(b->title());
|
||||
return index_a.value_or(0) > index_b.value_or(0);
|
||||
});
|
||||
|
||||
calculate_applet_rects(MenuManager::the().window());
|
||||
|
||||
MenuManager::the().refresh();
|
||||
}
|
||||
|
||||
void AppletManager::calculate_applet_rects(Window& window)
|
||||
{
|
||||
auto menubar_rect = window.rect();
|
||||
int right_edge_x = menubar_rect.width() - 4;
|
||||
for (auto& existing_applet : m_applets) {
|
||||
|
||||
Gfx::IntRect new_applet_rect(right_edge_x - existing_applet->size().width(), 0, existing_applet->size().width(), existing_applet->size().height());
|
||||
Gfx::IntRect dummy_menubar_rect(0, 0, 0, 18);
|
||||
new_applet_rect.center_vertically_within(dummy_menubar_rect);
|
||||
|
||||
existing_applet->set_rect_in_menubar(new_applet_rect);
|
||||
right_edge_x = existing_applet->rect_in_menubar().x() - 4;
|
||||
}
|
||||
}
|
||||
|
||||
void AppletManager::remove_applet(Window& applet)
|
||||
{
|
||||
m_applets.remove_first_matching([&](auto& entry) {
|
||||
return &applet == entry.ptr();
|
||||
});
|
||||
|
||||
MenuManager::the().refresh();
|
||||
}
|
||||
|
||||
void AppletManager::draw()
|
||||
{
|
||||
for (auto& applet : m_applets) {
|
||||
if (!applet)
|
||||
continue;
|
||||
draw_applet(*applet);
|
||||
}
|
||||
}
|
||||
|
||||
void AppletManager::draw_applet(const Window& applet)
|
||||
{
|
||||
if (!applet.backing_store())
|
||||
return;
|
||||
|
||||
Gfx::Painter painter(*MenuManager::the().window().backing_store());
|
||||
Gfx::PainterStateSaver saver(painter);
|
||||
painter.add_clip_rect(applet.rect_in_menubar());
|
||||
painter.fill_rect(applet.rect_in_menubar(), WindowManager::the().palette().window());
|
||||
painter.blit(applet.rect_in_menubar().location(), *applet.backing_store(), applet.backing_store()->rect());
|
||||
}
|
||||
|
||||
void AppletManager::invalidate_applet(const Window& applet, const Gfx::IntRect& rect)
|
||||
{
|
||||
draw_applet(applet);
|
||||
MenuManager::the().window().invalidate(rect.translated(applet.rect_in_menubar().location()));
|
||||
}
|
||||
|
||||
}
|
56
Userland/Services/WindowServer/AppletManager.h
Normal file
56
Userland/Services/WindowServer/AppletManager.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <WindowServer/Window.h>
|
||||
#include <WindowServer/WindowManager.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
class AppletManager : public Core::Object {
|
||||
C_OBJECT(AppletManager)
|
||||
public:
|
||||
AppletManager();
|
||||
~AppletManager();
|
||||
|
||||
static AppletManager& the();
|
||||
|
||||
virtual void event(Core::Event&) override;
|
||||
|
||||
void add_applet(Window& applet);
|
||||
void remove_applet(Window& applet);
|
||||
void draw();
|
||||
void invalidate_applet(const Window& applet, const Gfx::IntRect& rect);
|
||||
void calculate_applet_rects(Window& window);
|
||||
|
||||
private:
|
||||
void draw_applet(const Window& applet);
|
||||
|
||||
Vector<WeakPtr<Window>> m_applets;
|
||||
};
|
||||
|
||||
}
|
116
Userland/Services/WindowServer/Button.cpp
Normal file
116
Userland/Services/WindowServer/Button.cpp
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibGfx/CharacterBitmap.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <LibGfx/StylePainter.h>
|
||||
#include <WindowServer/Button.h>
|
||||
#include <WindowServer/Event.h>
|
||||
#include <WindowServer/WindowManager.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
Button::Button(WindowFrame& frame, Function<void(Button&)>&& on_click_handler)
|
||||
: on_click(move(on_click_handler))
|
||||
, m_frame(frame)
|
||||
{
|
||||
}
|
||||
|
||||
Button::~Button()
|
||||
{
|
||||
}
|
||||
|
||||
void Button::paint(Gfx::Painter& painter)
|
||||
{
|
||||
auto palette = WindowManager::the().palette();
|
||||
Gfx::PainterStateSaver saver(painter);
|
||||
painter.translate(relative_rect().location());
|
||||
Gfx::StylePainter::paint_button(painter, rect(), palette, Gfx::ButtonStyle::Normal, m_pressed, m_hovered);
|
||||
|
||||
if (m_icon) {
|
||||
auto icon_location = rect().center().translated(-(m_icon->width() / 2), -(m_icon->height() / 2));
|
||||
if (m_pressed)
|
||||
painter.translate(1, 1);
|
||||
painter.blit(icon_location, *m_icon, m_icon->rect());
|
||||
}
|
||||
}
|
||||
|
||||
void Button::on_mouse_event(const MouseEvent& event)
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
|
||||
if (event.type() == Event::MouseDown && event.button() == MouseButton::Left) {
|
||||
m_pressed = true;
|
||||
wm.set_cursor_tracking_button(this);
|
||||
m_frame.invalidate(m_relative_rect);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type() == Event::MouseUp && event.button() == MouseButton::Left) {
|
||||
if (wm.cursor_tracking_button() != this)
|
||||
return;
|
||||
wm.set_cursor_tracking_button(nullptr);
|
||||
bool old_pressed = m_pressed;
|
||||
m_pressed = false;
|
||||
if (rect().contains(event.position())) {
|
||||
if (on_click)
|
||||
on_click(*this);
|
||||
}
|
||||
if (old_pressed != m_pressed) {
|
||||
// Would like to compute:
|
||||
// m_hovered = rect_after_action().contains(event.position());
|
||||
// However, we don't know that rect yet. We can make an educated
|
||||
// guess which also looks okay even when wrong:
|
||||
m_hovered = false;
|
||||
m_frame.invalidate(m_relative_rect);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type() == Event::MouseMove) {
|
||||
bool old_hovered = m_hovered;
|
||||
m_hovered = rect().contains(event.position());
|
||||
wm.set_hovered_button(m_hovered ? this : nullptr);
|
||||
if (old_hovered != m_hovered)
|
||||
m_frame.invalidate(m_relative_rect);
|
||||
}
|
||||
|
||||
if (event.type() == Event::MouseMove && event.buttons() & (unsigned)MouseButton::Left) {
|
||||
if (wm.cursor_tracking_button() != this)
|
||||
return;
|
||||
bool old_pressed = m_pressed;
|
||||
m_pressed = m_hovered;
|
||||
if (old_pressed != m_pressed)
|
||||
m_frame.invalidate(m_relative_rect);
|
||||
}
|
||||
}
|
||||
|
||||
Gfx::IntRect Button::screen_rect() const
|
||||
{
|
||||
return m_relative_rect.translated(m_frame.rect().location());
|
||||
}
|
||||
|
||||
}
|
69
Userland/Services/WindowServer/Button.h
Normal file
69
Userland/Services/WindowServer/Button.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/Weakable.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
class MouseEvent;
|
||||
class WindowFrame;
|
||||
|
||||
class Button : public Weakable<Button> {
|
||||
public:
|
||||
Button(WindowFrame&, Function<void(Button&)>&& on_click_handler);
|
||||
~Button();
|
||||
|
||||
Gfx::IntRect relative_rect() const { return m_relative_rect; }
|
||||
void set_relative_rect(const Gfx::IntRect& rect) { m_relative_rect = rect; }
|
||||
|
||||
Gfx::IntRect rect() const { return { {}, m_relative_rect.size() }; }
|
||||
Gfx::IntRect screen_rect() const;
|
||||
|
||||
void paint(Gfx::Painter&);
|
||||
|
||||
void on_mouse_event(const MouseEvent&);
|
||||
|
||||
Function<void(Button&)> on_click;
|
||||
|
||||
bool is_visible() const { return m_visible; }
|
||||
|
||||
void set_icon(const Gfx::Bitmap& icon) { m_icon = icon; }
|
||||
|
||||
private:
|
||||
WindowFrame& m_frame;
|
||||
Gfx::IntRect m_relative_rect;
|
||||
RefPtr<Gfx::Bitmap> m_icon;
|
||||
bool m_pressed { false };
|
||||
bool m_visible { true };
|
||||
bool m_hovered { false };
|
||||
};
|
||||
|
||||
}
|
26
Userland/Services/WindowServer/CMakeLists.txt
Normal file
26
Userland/Services/WindowServer/CMakeLists.txt
Normal file
|
@ -0,0 +1,26 @@
|
|||
compile_ipc(WindowServer.ipc WindowServerEndpoint.h)
|
||||
compile_ipc(WindowClient.ipc WindowClientEndpoint.h)
|
||||
|
||||
set(SOURCES
|
||||
AppletManager.cpp
|
||||
Button.cpp
|
||||
ClientConnection.cpp
|
||||
Compositor.cpp
|
||||
Cursor.cpp
|
||||
EventLoop.cpp
|
||||
main.cpp
|
||||
MenuBar.cpp
|
||||
Menu.cpp
|
||||
MenuItem.cpp
|
||||
MenuManager.cpp
|
||||
Screen.cpp
|
||||
Window.cpp
|
||||
WindowFrame.cpp
|
||||
WindowManager.cpp
|
||||
WindowSwitcher.cpp
|
||||
WindowServerEndpoint.h
|
||||
WindowClientEndpoint.h
|
||||
)
|
||||
|
||||
serenity_bin(WindowServer)
|
||||
target_link_libraries(WindowServer LibCore LibGfx LibThread LibPthread LibIPC)
|
941
Userland/Services/WindowServer/ClientConnection.cpp
Normal file
941
Userland/Services/WindowServer/ClientConnection.cpp
Normal file
|
@ -0,0 +1,941 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/Badge.h>
|
||||
#include <AK/SharedBuffer.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/StandardCursor.h>
|
||||
#include <LibGfx/SystemTheme.h>
|
||||
#include <WindowServer/AppletManager.h>
|
||||
#include <WindowServer/ClientConnection.h>
|
||||
#include <WindowServer/Compositor.h>
|
||||
#include <WindowServer/Menu.h>
|
||||
#include <WindowServer/MenuBar.h>
|
||||
#include <WindowServer/MenuItem.h>
|
||||
#include <WindowServer/Screen.h>
|
||||
#include <WindowServer/Window.h>
|
||||
#include <WindowServer/WindowClientEndpoint.h>
|
||||
#include <WindowServer/WindowManager.h>
|
||||
#include <WindowServer/WindowSwitcher.h>
|
||||
#include <errno.h>
|
||||
#include <serenity.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
HashMap<int, NonnullRefPtr<ClientConnection>>* s_connections;
|
||||
|
||||
static Gfx::IntRect normalize_window_rect(Gfx::IntRect rect, WindowType window_type)
|
||||
{
|
||||
auto min_size = 1;
|
||||
if (window_type == WindowType::Normal)
|
||||
min_size = 50;
|
||||
Gfx::IntRect normalized_rect = { rect.x(), rect.y(), max(rect.width(), min_size), max(rect.height(), min_size) };
|
||||
return normalized_rect;
|
||||
}
|
||||
|
||||
void ClientConnection::for_each_client(Function<void(ClientConnection&)> callback)
|
||||
{
|
||||
if (!s_connections)
|
||||
return;
|
||||
for (auto& it : *s_connections) {
|
||||
callback(*it.value);
|
||||
}
|
||||
}
|
||||
|
||||
ClientConnection* ClientConnection::from_client_id(int client_id)
|
||||
{
|
||||
if (!s_connections)
|
||||
return nullptr;
|
||||
auto it = s_connections->find(client_id);
|
||||
if (it == s_connections->end())
|
||||
return nullptr;
|
||||
return (*it).value.ptr();
|
||||
}
|
||||
|
||||
ClientConnection::ClientConnection(NonnullRefPtr<Core::LocalSocket> client_socket, int client_id)
|
||||
: IPC::ClientConnection<WindowClientEndpoint, WindowServerEndpoint>(*this, move(client_socket), client_id)
|
||||
{
|
||||
if (!s_connections)
|
||||
s_connections = new HashMap<int, NonnullRefPtr<ClientConnection>>;
|
||||
s_connections->set(client_id, *this);
|
||||
}
|
||||
|
||||
ClientConnection::~ClientConnection()
|
||||
{
|
||||
if (m_has_display_link)
|
||||
Compositor::the().decrement_display_link_count({});
|
||||
|
||||
MenuManager::the().close_all_menus_from_client({}, *this);
|
||||
auto windows = move(m_windows);
|
||||
for (auto& window : windows) {
|
||||
window.value->detach_client({});
|
||||
if (window.value->type() == WindowType::MenuApplet)
|
||||
AppletManager::the().remove_applet(window.value);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientConnection::die()
|
||||
{
|
||||
deferred_invoke([this](auto&) {
|
||||
s_connections->remove(client_id());
|
||||
});
|
||||
}
|
||||
|
||||
void ClientConnection::notify_about_new_screen_rect(const Gfx::IntRect& rect)
|
||||
{
|
||||
post_message(Messages::WindowClient::ScreenRectChanged(rect));
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::CreateMenubarResponse> ClientConnection::handle(const Messages::WindowServer::CreateMenubar&)
|
||||
{
|
||||
int menubar_id = m_next_menubar_id++;
|
||||
auto menubar = make<MenuBar>(*this, menubar_id);
|
||||
m_menubars.set(menubar_id, move(menubar));
|
||||
return make<Messages::WindowServer::CreateMenubarResponse>(menubar_id);
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::DestroyMenubarResponse> ClientConnection::handle(const Messages::WindowServer::DestroyMenubar& message)
|
||||
{
|
||||
int menubar_id = message.menubar_id();
|
||||
auto it = m_menubars.find(menubar_id);
|
||||
if (it == m_menubars.end()) {
|
||||
did_misbehave("DestroyMenubar: Bad menubar ID");
|
||||
return {};
|
||||
}
|
||||
auto& menubar = *(*it).value;
|
||||
MenuManager::the().close_menubar(menubar);
|
||||
m_menubars.remove(it);
|
||||
return make<Messages::WindowServer::DestroyMenubarResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::CreateMenuResponse> ClientConnection::handle(const Messages::WindowServer::CreateMenu& message)
|
||||
{
|
||||
int menu_id = m_next_menu_id++;
|
||||
auto menu = Menu::construct(this, menu_id, message.menu_title());
|
||||
m_menus.set(menu_id, move(menu));
|
||||
return make<Messages::WindowServer::CreateMenuResponse>(menu_id);
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::DestroyMenuResponse> ClientConnection::handle(const Messages::WindowServer::DestroyMenu& message)
|
||||
{
|
||||
int menu_id = message.menu_id();
|
||||
auto it = m_menus.find(menu_id);
|
||||
if (it == m_menus.end()) {
|
||||
did_misbehave("DestroyMenu: Bad menu ID");
|
||||
return {};
|
||||
}
|
||||
auto& menu = *(*it).value;
|
||||
menu.close();
|
||||
m_menus.remove(it);
|
||||
remove_child(menu);
|
||||
return make<Messages::WindowServer::DestroyMenuResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetApplicationMenubarResponse> ClientConnection::handle(const Messages::WindowServer::SetApplicationMenubar& message)
|
||||
{
|
||||
int menubar_id = message.menubar_id();
|
||||
auto it = m_menubars.find(menubar_id);
|
||||
if (it == m_menubars.end()) {
|
||||
did_misbehave("SetApplicationMenubar: Bad menubar ID");
|
||||
return {};
|
||||
}
|
||||
auto& menubar = *(*it).value;
|
||||
m_app_menubar = menubar.make_weak_ptr();
|
||||
WindowManager::the().notify_client_changed_app_menubar(*this);
|
||||
return make<Messages::WindowServer::SetApplicationMenubarResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::AddMenuToMenubarResponse> ClientConnection::handle(const Messages::WindowServer::AddMenuToMenubar& message)
|
||||
{
|
||||
int menubar_id = message.menubar_id();
|
||||
int menu_id = message.menu_id();
|
||||
auto it = m_menubars.find(menubar_id);
|
||||
auto jt = m_menus.find(menu_id);
|
||||
if (it == m_menubars.end()) {
|
||||
did_misbehave("AddMenuToMenubar: Bad menubar ID");
|
||||
return {};
|
||||
}
|
||||
if (jt == m_menus.end()) {
|
||||
did_misbehave("AddMenuToMenubar: Bad menu ID");
|
||||
return {};
|
||||
}
|
||||
auto& menubar = *(*it).value;
|
||||
auto& menu = *(*jt).value;
|
||||
menubar.add_menu(menu);
|
||||
return make<Messages::WindowServer::AddMenuToMenubarResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::AddMenuItemResponse> ClientConnection::handle(const Messages::WindowServer::AddMenuItem& message)
|
||||
{
|
||||
int menu_id = message.menu_id();
|
||||
unsigned identifier = message.identifier();
|
||||
auto it = m_menus.find(menu_id);
|
||||
if (it == m_menus.end()) {
|
||||
dbg() << "AddMenuItem: Bad menu ID: " << menu_id;
|
||||
return {};
|
||||
}
|
||||
auto& menu = *(*it).value;
|
||||
auto menu_item = make<MenuItem>(menu, identifier, message.text(), message.shortcut(), message.enabled(), message.checkable(), message.checked());
|
||||
if (message.is_default())
|
||||
menu_item->set_default(true);
|
||||
if (message.icon_buffer_id() != -1) {
|
||||
auto icon_buffer = SharedBuffer::create_from_shbuf_id(message.icon_buffer_id());
|
||||
if (!icon_buffer)
|
||||
return {};
|
||||
// FIXME: Verify that the icon buffer can accommodate a 16x16 bitmap view.
|
||||
auto shared_icon = Gfx::Bitmap::create_with_shared_buffer(Gfx::BitmapFormat::RGBA32, icon_buffer.release_nonnull(), { 16, 16 });
|
||||
menu_item->set_icon(shared_icon);
|
||||
}
|
||||
menu_item->set_submenu_id(message.submenu_id());
|
||||
menu_item->set_exclusive(message.exclusive());
|
||||
menu.add_item(move(menu_item));
|
||||
return make<Messages::WindowServer::AddMenuItemResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::PopupMenuResponse> ClientConnection::handle(const Messages::WindowServer::PopupMenu& message)
|
||||
{
|
||||
int menu_id = message.menu_id();
|
||||
auto position = message.screen_position();
|
||||
auto it = m_menus.find(menu_id);
|
||||
if (it == m_menus.end()) {
|
||||
did_misbehave("PopupMenu: Bad menu ID");
|
||||
return {};
|
||||
}
|
||||
auto& menu = *(*it).value;
|
||||
menu.popup(position);
|
||||
return make<Messages::WindowServer::PopupMenuResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::DismissMenuResponse> ClientConnection::handle(const Messages::WindowServer::DismissMenu& message)
|
||||
{
|
||||
int menu_id = message.menu_id();
|
||||
auto it = m_menus.find(menu_id);
|
||||
if (it == m_menus.end()) {
|
||||
did_misbehave("DismissMenu: Bad menu ID");
|
||||
return {};
|
||||
}
|
||||
auto& menu = *(*it).value;
|
||||
menu.close();
|
||||
return make<Messages::WindowServer::DismissMenuResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::UpdateMenuItemResponse> ClientConnection::handle(const Messages::WindowServer::UpdateMenuItem& message)
|
||||
{
|
||||
int menu_id = message.menu_id();
|
||||
auto it = m_menus.find(menu_id);
|
||||
if (it == m_menus.end()) {
|
||||
did_misbehave("UpdateMenuItem: Bad menu ID");
|
||||
return {};
|
||||
}
|
||||
auto& menu = *(*it).value;
|
||||
auto* menu_item = menu.item_with_identifier(message.identifier());
|
||||
if (!menu_item) {
|
||||
did_misbehave("UpdateMenuItem: Bad menu item identifier");
|
||||
return {};
|
||||
}
|
||||
menu_item->set_text(message.text());
|
||||
menu_item->set_shortcut_text(message.shortcut());
|
||||
menu_item->set_enabled(message.enabled());
|
||||
menu_item->set_checkable(message.checkable());
|
||||
menu_item->set_default(message.is_default());
|
||||
if (message.checkable())
|
||||
menu_item->set_checked(message.checked());
|
||||
return make<Messages::WindowServer::UpdateMenuItemResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::AddMenuSeparatorResponse> ClientConnection::handle(const Messages::WindowServer::AddMenuSeparator& message)
|
||||
{
|
||||
int menu_id = message.menu_id();
|
||||
auto it = m_menus.find(menu_id);
|
||||
if (it == m_menus.end()) {
|
||||
did_misbehave("AddMenuSeparator: Bad menu ID");
|
||||
return {};
|
||||
}
|
||||
auto& menu = *(*it).value;
|
||||
menu.add_item(make<MenuItem>(menu, MenuItem::Separator));
|
||||
return make<Messages::WindowServer::AddMenuSeparatorResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::MoveWindowToFrontResponse> ClientConnection::handle(const Messages::WindowServer::MoveWindowToFront& message)
|
||||
{
|
||||
auto it = m_windows.find(message.window_id());
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("MoveWindowToFront: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
WindowManager::the().move_to_front_and_make_active(*(*it).value);
|
||||
return make<Messages::WindowServer::MoveWindowToFrontResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetFullscreenResponse> ClientConnection::handle(const Messages::WindowServer::SetFullscreen& message)
|
||||
{
|
||||
auto it = m_windows.find(message.window_id());
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("SetFullscreen: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
it->value->set_fullscreen(message.fullscreen());
|
||||
return make<Messages::WindowServer::SetFullscreenResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetWindowOpacityResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowOpacity& message)
|
||||
{
|
||||
auto it = m_windows.find(message.window_id());
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("SetWindowOpacity: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
it->value->set_opacity(message.opacity());
|
||||
return make<Messages::WindowServer::SetWindowOpacityResponse>();
|
||||
}
|
||||
|
||||
void ClientConnection::handle(const Messages::WindowServer::AsyncSetWallpaper& message)
|
||||
{
|
||||
Compositor::the().set_wallpaper(message.path(), [&](bool success) {
|
||||
post_message(Messages::WindowClient::AsyncSetWallpaperFinished(success));
|
||||
});
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetBackgroundColorResponse> ClientConnection::handle(const Messages::WindowServer::SetBackgroundColor& message)
|
||||
{
|
||||
Compositor::the().set_background_color(message.background_color());
|
||||
return make<Messages::WindowServer::SetBackgroundColorResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetWallpaperModeResponse> ClientConnection::handle(const Messages::WindowServer::SetWallpaperMode& message)
|
||||
{
|
||||
Compositor::the().set_wallpaper_mode(message.mode());
|
||||
return make<Messages::WindowServer::SetWallpaperModeResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::GetWallpaperResponse> ClientConnection::handle(const Messages::WindowServer::GetWallpaper&)
|
||||
{
|
||||
return make<Messages::WindowServer::GetWallpaperResponse>(Compositor::the().wallpaper_path());
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetResolutionResponse> ClientConnection::handle(const Messages::WindowServer::SetResolution& message)
|
||||
{
|
||||
return make<Messages::WindowServer::SetResolutionResponse>(WindowManager::the().set_resolution(message.resolution().width(), message.resolution().height()), WindowManager::the().resolution());
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetWindowTitleResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowTitle& message)
|
||||
{
|
||||
auto it = m_windows.find(message.window_id());
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("SetWindowTitle: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
it->value->set_title(message.title());
|
||||
return make<Messages::WindowServer::SetWindowTitleResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::GetWindowTitleResponse> ClientConnection::handle(const Messages::WindowServer::GetWindowTitle& message)
|
||||
{
|
||||
auto it = m_windows.find(message.window_id());
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("GetWindowTitle: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
return make<Messages::WindowServer::GetWindowTitleResponse>(it->value->title());
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::IsMaximizedResponse> ClientConnection::handle(const Messages::WindowServer::IsMaximized& message)
|
||||
{
|
||||
auto it = m_windows.find(message.window_id());
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("IsMaximized: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
return make<Messages::WindowServer::IsMaximizedResponse>(it->value->is_maximized());
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetWindowIconBitmapResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowIconBitmap& message)
|
||||
{
|
||||
auto it = m_windows.find(message.window_id());
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("SetWindowIconBitmap: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
auto& window = *(*it).value;
|
||||
|
||||
if (message.icon().is_valid()) {
|
||||
window.set_icon(*message.icon().bitmap());
|
||||
} else {
|
||||
window.set_default_icon();
|
||||
}
|
||||
|
||||
window.frame().invalidate_title_bar();
|
||||
WindowManager::the().tell_wm_listeners_window_icon_changed(window);
|
||||
return make<Messages::WindowServer::SetWindowIconBitmapResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetWindowRectResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowRect& message)
|
||||
{
|
||||
int window_id = message.window_id();
|
||||
auto it = m_windows.find(window_id);
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("SetWindowRect: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
auto& window = *(*it).value;
|
||||
if (window.is_fullscreen()) {
|
||||
dbgln("ClientConnection: Ignoring SetWindowRect request for fullscreen window");
|
||||
return {};
|
||||
}
|
||||
|
||||
if (message.rect().location() != window.rect().location()) {
|
||||
window.set_default_positioned(false);
|
||||
}
|
||||
auto normalized_rect = normalize_window_rect(message.rect(), window.type());
|
||||
window.set_rect(normalized_rect);
|
||||
window.request_update(normalized_rect);
|
||||
return make<Messages::WindowServer::SetWindowRectResponse>(normalized_rect);
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::GetWindowRectResponse> ClientConnection::handle(const Messages::WindowServer::GetWindowRect& message)
|
||||
{
|
||||
int window_id = message.window_id();
|
||||
auto it = m_windows.find(window_id);
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("GetWindowRect: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
return make<Messages::WindowServer::GetWindowRectResponse>(it->value->rect());
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::GetWindowRectInMenubarResponse> ClientConnection::handle(const Messages::WindowServer::GetWindowRectInMenubar& message)
|
||||
{
|
||||
int window_id = message.window_id();
|
||||
auto it = m_windows.find(window_id);
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("GetWindowRectInMenubar: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
return make<Messages::WindowServer::GetWindowRectInMenubarResponse>(it->value->rect_in_menubar());
|
||||
}
|
||||
|
||||
Window* ClientConnection::window_from_id(i32 window_id)
|
||||
{
|
||||
auto it = m_windows.find(window_id);
|
||||
if (it == m_windows.end())
|
||||
return nullptr;
|
||||
return it->value.ptr();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::CreateWindowResponse> ClientConnection::handle(const Messages::WindowServer::CreateWindow& message)
|
||||
{
|
||||
Window* parent_window = nullptr;
|
||||
if (message.parent_window_id()) {
|
||||
parent_window = window_from_id(message.parent_window_id());
|
||||
if (!parent_window) {
|
||||
did_misbehave("CreateWindow with bad parent_window_id");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
int window_id = m_next_window_id++;
|
||||
auto window = Window::construct(*this, (WindowType)message.type(), window_id, message.modal(), message.minimizable(), message.frameless(), message.resizable(), message.fullscreen(), message.accessory(), parent_window);
|
||||
|
||||
window->set_has_alpha_channel(message.has_alpha_channel());
|
||||
window->set_title(message.title());
|
||||
if (!message.fullscreen()) {
|
||||
auto rect = message.rect();
|
||||
if (message.auto_position() && window->type() == WindowType::Normal) {
|
||||
rect = { WindowManager::the().get_recommended_window_position({ 100, 100 }), message.rect().size() };
|
||||
window->set_default_positioned(true);
|
||||
}
|
||||
auto normalized_rect = normalize_window_rect(rect, window->type());
|
||||
window->set_rect(normalized_rect);
|
||||
}
|
||||
if (window->type() == WindowType::Desktop) {
|
||||
window->set_rect(WindowManager::the().desktop_rect());
|
||||
window->recalculate_rect();
|
||||
}
|
||||
window->set_opacity(message.opacity());
|
||||
window->set_size_increment(message.size_increment());
|
||||
window->set_base_size(message.base_size());
|
||||
window->set_resize_aspect_ratio(message.resize_aspect_ratio());
|
||||
window->invalidate();
|
||||
if (window->type() == WindowType::MenuApplet)
|
||||
AppletManager::the().add_applet(*window);
|
||||
m_windows.set(window_id, move(window));
|
||||
return make<Messages::WindowServer::CreateWindowResponse>(window_id);
|
||||
}
|
||||
|
||||
void ClientConnection::destroy_window(Window& window, Vector<i32>& destroyed_window_ids)
|
||||
{
|
||||
for (auto& child_window : window.child_windows()) {
|
||||
if (!child_window)
|
||||
continue;
|
||||
ASSERT(child_window->window_id() != window.window_id());
|
||||
destroy_window(*child_window, destroyed_window_ids);
|
||||
}
|
||||
|
||||
for (auto& accessory_window : window.accessory_windows()) {
|
||||
if (!accessory_window)
|
||||
continue;
|
||||
ASSERT(accessory_window->window_id() != window.window_id());
|
||||
destroy_window(*accessory_window, destroyed_window_ids);
|
||||
}
|
||||
|
||||
destroyed_window_ids.append(window.window_id());
|
||||
|
||||
if (window.type() == WindowType::MenuApplet)
|
||||
AppletManager::the().remove_applet(window);
|
||||
|
||||
window.destroy();
|
||||
remove_child(window);
|
||||
m_windows.remove(window.window_id());
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::DestroyWindowResponse> ClientConnection::handle(const Messages::WindowServer::DestroyWindow& message)
|
||||
{
|
||||
auto it = m_windows.find(message.window_id());
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("DestroyWindow: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
auto& window = *(*it).value;
|
||||
Vector<i32> destroyed_window_ids;
|
||||
destroy_window(window, destroyed_window_ids);
|
||||
return make<Messages::WindowServer::DestroyWindowResponse>(destroyed_window_ids);
|
||||
}
|
||||
|
||||
void ClientConnection::post_paint_message(Window& window, bool ignore_occlusion)
|
||||
{
|
||||
auto rect_set = window.take_pending_paint_rects();
|
||||
if (window.is_minimized() || (!ignore_occlusion && window.is_occluded()))
|
||||
return;
|
||||
|
||||
post_message(Messages::WindowClient::Paint(window.window_id(), window.size(), rect_set.rects()));
|
||||
}
|
||||
|
||||
void ClientConnection::handle(const Messages::WindowServer::InvalidateRect& message)
|
||||
{
|
||||
auto it = m_windows.find(message.window_id());
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("InvalidateRect: Bad window ID");
|
||||
return;
|
||||
}
|
||||
auto& window = *(*it).value;
|
||||
for (size_t i = 0; i < message.rects().size(); ++i)
|
||||
window.request_update(message.rects()[i].intersected({ {}, window.size() }), message.ignore_occlusion());
|
||||
}
|
||||
|
||||
void ClientConnection::handle(const Messages::WindowServer::DidFinishPainting& message)
|
||||
{
|
||||
int window_id = message.window_id();
|
||||
auto it = m_windows.find(window_id);
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("DidFinishPainting: Bad window ID");
|
||||
return;
|
||||
}
|
||||
auto& window = *(*it).value;
|
||||
for (auto& rect : message.rects())
|
||||
window.invalidate(rect);
|
||||
|
||||
WindowSwitcher::the().refresh_if_needed();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetWindowBackingStoreResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowBackingStore& message)
|
||||
{
|
||||
int window_id = message.window_id();
|
||||
auto it = m_windows.find(window_id);
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("SetWindowBackingStore: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
auto& window = *(*it).value;
|
||||
if (window.last_backing_store() && window.last_backing_store()->shbuf_id() == message.shbuf_id()) {
|
||||
window.swap_backing_stores();
|
||||
} else {
|
||||
auto shared_buffer = SharedBuffer::create_from_shbuf_id(message.shbuf_id());
|
||||
if (!shared_buffer)
|
||||
return make<Messages::WindowServer::SetWindowBackingStoreResponse>();
|
||||
auto backing_store = Gfx::Bitmap::create_with_shared_buffer(
|
||||
message.has_alpha_channel() ? Gfx::BitmapFormat::RGBA32 : Gfx::BitmapFormat::RGB32,
|
||||
*shared_buffer,
|
||||
message.size());
|
||||
window.set_backing_store(move(backing_store));
|
||||
}
|
||||
|
||||
if (message.flush_immediately())
|
||||
window.invalidate(false);
|
||||
|
||||
return make<Messages::WindowServer::SetWindowBackingStoreResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetGlobalCursorTrackingResponse> ClientConnection::handle(const Messages::WindowServer::SetGlobalCursorTracking& message)
|
||||
{
|
||||
int window_id = message.window_id();
|
||||
auto it = m_windows.find(window_id);
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("SetGlobalCursorTracking: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
it->value->set_global_cursor_tracking_enabled(message.enabled());
|
||||
return make<Messages::WindowServer::SetGlobalCursorTrackingResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetWindowCursorResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowCursor& message)
|
||||
{
|
||||
auto it = m_windows.find(message.window_id());
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("SetWindowCursor: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
auto& window = *(*it).value;
|
||||
if (message.cursor_type() < 0 || message.cursor_type() >= (i32)Gfx::StandardCursor::__Count) {
|
||||
did_misbehave("SetWindowCursor: Bad cursor type");
|
||||
return {};
|
||||
}
|
||||
window.set_cursor(Cursor::create((Gfx::StandardCursor)message.cursor_type()));
|
||||
Compositor::the().invalidate_cursor();
|
||||
return make<Messages::WindowServer::SetWindowCursorResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetWindowCustomCursorResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowCustomCursor& message)
|
||||
{
|
||||
auto it = m_windows.find(message.window_id());
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("SetWindowCustomCursor: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
|
||||
auto& window = *(*it).value;
|
||||
if (!message.cursor().is_valid()) {
|
||||
did_misbehave("SetWindowCustomCursor: Bad cursor");
|
||||
return {};
|
||||
}
|
||||
|
||||
window.set_cursor(Cursor::create(*message.cursor().bitmap()));
|
||||
Compositor::the().invalidate_cursor();
|
||||
return make<Messages::WindowServer::SetWindowCustomCursorResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetWindowHasAlphaChannelResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowHasAlphaChannel& message)
|
||||
{
|
||||
auto it = m_windows.find(message.window_id());
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("SetWindowHasAlphaChannel: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
it->value->set_has_alpha_channel(message.has_alpha_channel());
|
||||
return make<Messages::WindowServer::SetWindowHasAlphaChannelResponse>();
|
||||
}
|
||||
|
||||
void ClientConnection::handle(const Messages::WindowServer::WM_SetActiveWindow& message)
|
||||
{
|
||||
auto* client = ClientConnection::from_client_id(message.client_id());
|
||||
if (!client) {
|
||||
did_misbehave("WM_SetActiveWindow: Bad client ID");
|
||||
return;
|
||||
}
|
||||
auto it = client->m_windows.find(message.window_id());
|
||||
if (it == client->m_windows.end()) {
|
||||
did_misbehave("WM_SetActiveWindow: Bad window ID");
|
||||
return;
|
||||
}
|
||||
auto& window = *(*it).value;
|
||||
WindowManager::the().minimize_windows(window, false);
|
||||
WindowManager::the().move_to_front_and_make_active(window);
|
||||
}
|
||||
|
||||
void ClientConnection::handle(const Messages::WindowServer::WM_PopupWindowMenu& message)
|
||||
{
|
||||
auto* client = ClientConnection::from_client_id(message.client_id());
|
||||
if (!client) {
|
||||
did_misbehave("WM_PopupWindowMenu: Bad client ID");
|
||||
return;
|
||||
}
|
||||
auto it = client->m_windows.find(message.window_id());
|
||||
if (it == client->m_windows.end()) {
|
||||
did_misbehave("WM_PopupWindowMenu: Bad window ID");
|
||||
return;
|
||||
}
|
||||
auto& window = *(*it).value;
|
||||
if (auto* modal_window = window.blocking_modal_window()) {
|
||||
modal_window->popup_window_menu(message.screen_position(), WindowMenuDefaultAction::BasedOnWindowState);
|
||||
} else {
|
||||
window.popup_window_menu(message.screen_position(), WindowMenuDefaultAction::BasedOnWindowState);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientConnection::handle(const Messages::WindowServer::WM_StartWindowResize& request)
|
||||
{
|
||||
auto* client = ClientConnection::from_client_id(request.client_id());
|
||||
if (!client) {
|
||||
did_misbehave("WM_StartWindowResize: Bad client ID");
|
||||
return;
|
||||
}
|
||||
auto it = client->m_windows.find(request.window_id());
|
||||
if (it == client->m_windows.end()) {
|
||||
did_misbehave("WM_StartWindowResize: Bad window ID");
|
||||
return;
|
||||
}
|
||||
auto& window = *(*it).value;
|
||||
// FIXME: We are cheating a bit here by using the current cursor location and hard-coding the left button.
|
||||
// Maybe the client should be allowed to specify what initiated this request?
|
||||
WindowManager::the().start_window_resize(window, Screen::the().cursor_location(), MouseButton::Left);
|
||||
}
|
||||
|
||||
void ClientConnection::handle(const Messages::WindowServer::WM_SetWindowMinimized& message)
|
||||
{
|
||||
auto* client = ClientConnection::from_client_id(message.client_id());
|
||||
if (!client) {
|
||||
did_misbehave("WM_SetWindowMinimized: Bad client ID");
|
||||
return;
|
||||
}
|
||||
auto it = client->m_windows.find(message.window_id());
|
||||
if (it == client->m_windows.end()) {
|
||||
did_misbehave("WM_SetWindowMinimized: Bad window ID");
|
||||
return;
|
||||
}
|
||||
auto& window = *(*it).value;
|
||||
WindowManager::the().minimize_windows(window, message.minimized());
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::GreetResponse> ClientConnection::handle(const Messages::WindowServer::Greet&)
|
||||
{
|
||||
return make<Messages::WindowServer::GreetResponse>(client_id(), Screen::the().rect(), Gfx::current_system_theme_buffer_id());
|
||||
}
|
||||
|
||||
void ClientConnection::handle(const Messages::WindowServer::WM_SetWindowTaskbarRect& message)
|
||||
{
|
||||
// Because the Taskbar (which should be the only user of this API) does not own the
|
||||
// window or the client id, there is a possibility that it may send this message for
|
||||
// a window or client that may have been destroyed already. This is not an error,
|
||||
// and we should not call did_misbehave() for either.
|
||||
auto* client = ClientConnection::from_client_id(message.client_id());
|
||||
if (!client)
|
||||
return;
|
||||
|
||||
auto it = client->m_windows.find(message.window_id());
|
||||
if (it == client->m_windows.end())
|
||||
return;
|
||||
|
||||
auto& window = *(*it).value;
|
||||
window.set_taskbar_rect(message.rect());
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::StartDragResponse> ClientConnection::handle(const Messages::WindowServer::StartDrag& message)
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
if (wm.dnd_client())
|
||||
return make<Messages::WindowServer::StartDragResponse>(false);
|
||||
|
||||
RefPtr<Gfx::Bitmap> bitmap;
|
||||
if (message.bitmap_id() != -1) {
|
||||
auto shared_buffer = SharedBuffer::create_from_shbuf_id(message.bitmap_id());
|
||||
ssize_t size_in_bytes = message.bitmap_size().area() * sizeof(Gfx::RGBA32);
|
||||
if (size_in_bytes > shared_buffer->size()) {
|
||||
did_misbehave("SetAppletBackingStore: Shared buffer is too small for applet size");
|
||||
return {};
|
||||
}
|
||||
bitmap = Gfx::Bitmap::create_with_shared_buffer(Gfx::BitmapFormat::RGBA32, *shared_buffer, message.bitmap_size());
|
||||
}
|
||||
|
||||
wm.start_dnd_drag(*this, message.text(), bitmap, Core::MimeData::construct(message.mime_data()));
|
||||
return make<Messages::WindowServer::StartDragResponse>(true);
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetSystemMenuResponse> ClientConnection::handle(const Messages::WindowServer::SetSystemMenu& message)
|
||||
{
|
||||
auto it = m_menus.find(message.menu_id());
|
||||
if (it == m_menus.end()) {
|
||||
did_misbehave("SetSystemMenu called with invalid menu ID");
|
||||
return {};
|
||||
}
|
||||
|
||||
auto& menu = it->value;
|
||||
MenuManager::the().set_system_menu(menu);
|
||||
return make<Messages::WindowServer::SetSystemMenuResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetSystemThemeResponse> ClientConnection::handle(const Messages::WindowServer::SetSystemTheme& message)
|
||||
{
|
||||
bool success = WindowManager::the().update_theme(message.theme_path(), message.theme_name());
|
||||
return make<Messages::WindowServer::SetSystemThemeResponse>(success);
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::GetSystemThemeResponse> ClientConnection::handle(const Messages::WindowServer::GetSystemTheme&)
|
||||
{
|
||||
auto wm_config = Core::ConfigFile::open("/etc/WindowServer/WindowServer.ini");
|
||||
auto name = wm_config->read_entry("Theme", "Name");
|
||||
return make<Messages::WindowServer::GetSystemThemeResponse>(name);
|
||||
}
|
||||
|
||||
void ClientConnection::boost()
|
||||
{
|
||||
// FIXME: Re-enable this when we have a solution for boosting.
|
||||
#if 0
|
||||
if (set_process_boost(client_pid(), 10) < 0)
|
||||
perror("boost: set_process_boost");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ClientConnection::deboost()
|
||||
{
|
||||
// FIXME: Re-enable this when we have a solution for boosting.
|
||||
#if 0
|
||||
if (set_process_boost(client_pid(), 0) < 0)
|
||||
perror("deboost: set_process_boost");
|
||||
#endif
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetWindowBaseSizeAndSizeIncrementResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowBaseSizeAndSizeIncrement& message)
|
||||
{
|
||||
auto it = m_windows.find(message.window_id());
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("SetWindowBaseSizeAndSizeIncrementResponse: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
|
||||
auto& window = *it->value;
|
||||
window.set_base_size(message.base_size());
|
||||
window.set_size_increment(message.size_increment());
|
||||
|
||||
return make<Messages::WindowServer::SetWindowBaseSizeAndSizeIncrementResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetWindowResizeAspectRatioResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowResizeAspectRatio& message)
|
||||
{
|
||||
auto it = m_windows.find(message.window_id());
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("SetWindowResizeAspectRatioResponse: Bad window ID");
|
||||
return {};
|
||||
}
|
||||
|
||||
auto& window = *it->value;
|
||||
window.set_resize_aspect_ratio(message.resize_aspect_ratio());
|
||||
|
||||
return make<Messages::WindowServer::SetWindowResizeAspectRatioResponse>();
|
||||
}
|
||||
|
||||
void ClientConnection::handle(const Messages::WindowServer::EnableDisplayLink&)
|
||||
{
|
||||
if (m_has_display_link)
|
||||
return;
|
||||
m_has_display_link = true;
|
||||
Compositor::the().increment_display_link_count({});
|
||||
}
|
||||
|
||||
void ClientConnection::handle(const Messages::WindowServer::DisableDisplayLink&)
|
||||
{
|
||||
if (!m_has_display_link)
|
||||
return;
|
||||
m_has_display_link = false;
|
||||
Compositor::the().decrement_display_link_count({});
|
||||
}
|
||||
|
||||
void ClientConnection::notify_display_link(Badge<Compositor>)
|
||||
{
|
||||
if (!m_has_display_link)
|
||||
return;
|
||||
|
||||
post_message(Messages::WindowClient::DisplayLinkNotification());
|
||||
}
|
||||
|
||||
void ClientConnection::handle(const Messages::WindowServer::SetWindowProgress& message)
|
||||
{
|
||||
auto it = m_windows.find(message.window_id());
|
||||
if (it == m_windows.end()) {
|
||||
did_misbehave("SetWindowProgress with bad window ID");
|
||||
return;
|
||||
}
|
||||
it->value->set_progress(message.progress());
|
||||
}
|
||||
|
||||
void ClientConnection::handle(const Messages::WindowServer::Pong&)
|
||||
{
|
||||
m_ping_timer = nullptr;
|
||||
set_unresponsive(false);
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::GetGlobalCursorPositionResponse> ClientConnection::handle(const Messages::WindowServer::GetGlobalCursorPosition&)
|
||||
{
|
||||
return make<Messages::WindowServer::GetGlobalCursorPositionResponse>(Screen::the().cursor_location());
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetMouseAccelerationResponse> ClientConnection::handle(const Messages::WindowServer::SetMouseAcceleration& message)
|
||||
{
|
||||
if (message.factor() < mouse_accel_min || message.factor() > mouse_accel_max) {
|
||||
did_misbehave("SetMouseAcceleration with bad acceleration factor");
|
||||
return {};
|
||||
}
|
||||
WindowManager::the().set_acceleration_factor(message.factor());
|
||||
return make<Messages::WindowServer::SetMouseAccelerationResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::GetMouseAccelerationResponse> ClientConnection::handle(const Messages::WindowServer::GetMouseAcceleration&)
|
||||
{
|
||||
return make<Messages::WindowServer::GetMouseAccelerationResponse>(Screen::the().acceleration_factor());
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WindowServer::SetScrollStepSizeResponse> ClientConnection::handle(const Messages::WindowServer::SetScrollStepSize& message)
|
||||
{
|
||||
if (message.step_size() < scroll_step_size_min) {
|
||||
did_misbehave("SetScrollStepSize with bad scroll step size");
|
||||
return {};
|
||||
}
|
||||
WindowManager::the().set_scroll_step_size(message.step_size());
|
||||
return make<Messages::WindowServer::SetScrollStepSizeResponse>();
|
||||
}
|
||||
OwnPtr<Messages::WindowServer::GetScrollStepSizeResponse> ClientConnection::handle(const Messages::WindowServer::GetScrollStepSize&)
|
||||
{
|
||||
return make<Messages::WindowServer::GetScrollStepSizeResponse>(Screen::the().scroll_step_size());
|
||||
}
|
||||
|
||||
void ClientConnection::set_unresponsive(bool unresponsive)
|
||||
{
|
||||
if (m_unresponsive == unresponsive)
|
||||
return;
|
||||
m_unresponsive = unresponsive;
|
||||
for (auto& it : m_windows) {
|
||||
auto& window = *it.value;
|
||||
window.invalidate();
|
||||
if (unresponsive)
|
||||
window.set_cursor(WindowManager::the().wait_cursor());
|
||||
}
|
||||
Compositor::the().invalidate_cursor();
|
||||
}
|
||||
|
||||
void ClientConnection::may_have_become_unresponsive()
|
||||
{
|
||||
post_message(Messages::WindowClient::Ping());
|
||||
m_ping_timer = Core::Timer::create_single_shot(1000, [this] {
|
||||
set_unresponsive(true);
|
||||
});
|
||||
}
|
||||
|
||||
void ClientConnection::did_become_responsive()
|
||||
{
|
||||
set_unresponsive(false);
|
||||
}
|
||||
|
||||
}
|
170
Userland/Services/WindowServer/ClientConnection.h
Normal file
170
Userland/Services/WindowServer/ClientConnection.h
Normal file
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Badge.h>
|
||||
#include <AK/Function.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibCore/Object.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibIPC/ClientConnection.h>
|
||||
#include <WindowServer/Event.h>
|
||||
#include <WindowServer/WindowClientEndpoint.h>
|
||||
#include <WindowServer/WindowServerEndpoint.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
class Compositor;
|
||||
class Window;
|
||||
class Menu;
|
||||
class MenuBar;
|
||||
|
||||
class ClientConnection final
|
||||
: public IPC::ClientConnection<WindowClientEndpoint, WindowServerEndpoint>
|
||||
, public WindowServerEndpoint {
|
||||
C_OBJECT(ClientConnection)
|
||||
public:
|
||||
~ClientConnection() override;
|
||||
|
||||
bool is_unresponsive() const { return m_unresponsive; }
|
||||
|
||||
void boost();
|
||||
void deboost();
|
||||
|
||||
static ClientConnection* from_client_id(int client_id);
|
||||
static void for_each_client(Function<void(ClientConnection&)>);
|
||||
|
||||
MenuBar* app_menubar() { return m_app_menubar.ptr(); }
|
||||
|
||||
void notify_about_new_screen_rect(const Gfx::IntRect&);
|
||||
void post_paint_message(Window&, bool ignore_occlusion = false);
|
||||
|
||||
Menu* find_menu_by_id(int menu_id)
|
||||
{
|
||||
auto menu = m_menus.get(menu_id);
|
||||
if (!menu.has_value())
|
||||
return nullptr;
|
||||
return const_cast<Menu*>(menu.value().ptr());
|
||||
}
|
||||
const Menu* find_menu_by_id(int menu_id) const
|
||||
{
|
||||
auto menu = m_menus.get(menu_id);
|
||||
if (!menu.has_value())
|
||||
return nullptr;
|
||||
return menu.value().ptr();
|
||||
}
|
||||
|
||||
void notify_display_link(Badge<Compositor>);
|
||||
|
||||
private:
|
||||
explicit ClientConnection(NonnullRefPtr<Core::LocalSocket>, int client_id);
|
||||
|
||||
// ^ClientConnection
|
||||
virtual void die() override;
|
||||
virtual void may_have_become_unresponsive() override;
|
||||
virtual void did_become_responsive() override;
|
||||
|
||||
void set_unresponsive(bool);
|
||||
void destroy_window(Window&, Vector<i32>& destroyed_window_ids);
|
||||
|
||||
virtual OwnPtr<Messages::WindowServer::GreetResponse> handle(const Messages::WindowServer::Greet&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::CreateMenubarResponse> handle(const Messages::WindowServer::CreateMenubar&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::DestroyMenubarResponse> handle(const Messages::WindowServer::DestroyMenubar&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::CreateMenuResponse> handle(const Messages::WindowServer::CreateMenu&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::DestroyMenuResponse> handle(const Messages::WindowServer::DestroyMenu&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::AddMenuToMenubarResponse> handle(const Messages::WindowServer::AddMenuToMenubar&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetApplicationMenubarResponse> handle(const Messages::WindowServer::SetApplicationMenubar&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::AddMenuItemResponse> handle(const Messages::WindowServer::AddMenuItem&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::AddMenuSeparatorResponse> handle(const Messages::WindowServer::AddMenuSeparator&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::UpdateMenuItemResponse> handle(const Messages::WindowServer::UpdateMenuItem&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::CreateWindowResponse> handle(const Messages::WindowServer::CreateWindow&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::DestroyWindowResponse> handle(const Messages::WindowServer::DestroyWindow&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetWindowTitleResponse> handle(const Messages::WindowServer::SetWindowTitle&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::GetWindowTitleResponse> handle(const Messages::WindowServer::GetWindowTitle&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::IsMaximizedResponse> handle(const Messages::WindowServer::IsMaximized&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetWindowRectResponse> handle(const Messages::WindowServer::SetWindowRect&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::GetWindowRectResponse> handle(const Messages::WindowServer::GetWindowRect&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::GetWindowRectInMenubarResponse> handle(const Messages::WindowServer::GetWindowRectInMenubar&) override;
|
||||
virtual void handle(const Messages::WindowServer::InvalidateRect&) override;
|
||||
virtual void handle(const Messages::WindowServer::DidFinishPainting&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetGlobalCursorTrackingResponse> handle(const Messages::WindowServer::SetGlobalCursorTracking&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetWindowOpacityResponse> handle(const Messages::WindowServer::SetWindowOpacity&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetWindowBackingStoreResponse> handle(const Messages::WindowServer::SetWindowBackingStore&) override;
|
||||
virtual void handle(const Messages::WindowServer::WM_SetActiveWindow&) override;
|
||||
virtual void handle(const Messages::WindowServer::WM_SetWindowMinimized&) override;
|
||||
virtual void handle(const Messages::WindowServer::WM_StartWindowResize&) override;
|
||||
virtual void handle(const Messages::WindowServer::WM_PopupWindowMenu&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetWindowHasAlphaChannelResponse> handle(const Messages::WindowServer::SetWindowHasAlphaChannel&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::MoveWindowToFrontResponse> handle(const Messages::WindowServer::MoveWindowToFront&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetFullscreenResponse> handle(const Messages::WindowServer::SetFullscreen&) override;
|
||||
virtual void handle(const Messages::WindowServer::AsyncSetWallpaper&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetBackgroundColorResponse> handle(const Messages::WindowServer::SetBackgroundColor&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetWallpaperModeResponse> handle(const Messages::WindowServer::SetWallpaperMode&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::GetWallpaperResponse> handle(const Messages::WindowServer::GetWallpaper&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetResolutionResponse> handle(const Messages::WindowServer::SetResolution&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetWindowCursorResponse> handle(const Messages::WindowServer::SetWindowCursor&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetWindowCustomCursorResponse> handle(const Messages::WindowServer::SetWindowCustomCursor&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::PopupMenuResponse> handle(const Messages::WindowServer::PopupMenu&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::DismissMenuResponse> handle(const Messages::WindowServer::DismissMenu&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetWindowIconBitmapResponse> handle(const Messages::WindowServer::SetWindowIconBitmap&) override;
|
||||
virtual void handle(const Messages::WindowServer::WM_SetWindowTaskbarRect&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::StartDragResponse> handle(const Messages::WindowServer::StartDrag&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetSystemMenuResponse> handle(const Messages::WindowServer::SetSystemMenu&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetSystemThemeResponse> handle(const Messages::WindowServer::SetSystemTheme&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::GetSystemThemeResponse> handle(const Messages::WindowServer::GetSystemTheme&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetWindowBaseSizeAndSizeIncrementResponse> handle(const Messages::WindowServer::SetWindowBaseSizeAndSizeIncrement&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetWindowResizeAspectRatioResponse> handle(const Messages::WindowServer::SetWindowResizeAspectRatio&) override;
|
||||
virtual void handle(const Messages::WindowServer::EnableDisplayLink&) override;
|
||||
virtual void handle(const Messages::WindowServer::DisableDisplayLink&) override;
|
||||
virtual void handle(const Messages::WindowServer::SetWindowProgress&) override;
|
||||
virtual void handle(const Messages::WindowServer::Pong&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::GetGlobalCursorPositionResponse> handle(const Messages::WindowServer::GetGlobalCursorPosition&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetMouseAccelerationResponse> handle(const Messages::WindowServer::SetMouseAcceleration&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::GetMouseAccelerationResponse> handle(const Messages::WindowServer::GetMouseAcceleration&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::SetScrollStepSizeResponse> handle(const Messages::WindowServer::SetScrollStepSize&) override;
|
||||
virtual OwnPtr<Messages::WindowServer::GetScrollStepSizeResponse> handle(const Messages::WindowServer::GetScrollStepSize&) override;
|
||||
|
||||
Window* window_from_id(i32 window_id);
|
||||
|
||||
HashMap<int, NonnullRefPtr<Window>> m_windows;
|
||||
HashMap<int, NonnullOwnPtr<MenuBar>> m_menubars;
|
||||
HashMap<int, NonnullRefPtr<Menu>> m_menus;
|
||||
WeakPtr<MenuBar> m_app_menubar;
|
||||
|
||||
RefPtr<Core::Timer> m_ping_timer;
|
||||
|
||||
int m_next_menubar_id { 10000 };
|
||||
int m_next_menu_id { 20000 };
|
||||
int m_next_window_id { 1982 };
|
||||
|
||||
bool m_has_display_link { false };
|
||||
bool m_unresponsive { false };
|
||||
};
|
||||
|
||||
}
|
1040
Userland/Services/WindowServer/Compositor.cpp
Normal file
1040
Userland/Services/WindowServer/Compositor.cpp
Normal file
File diff suppressed because it is too large
Load diff
135
Userland/Services/WindowServer/Compositor.h
Normal file
135
Userland/Services/WindowServer/Compositor.h
Normal file
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <LibCore/Object.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/DisjointRectSet.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
class ClientConnection;
|
||||
class Cursor;
|
||||
class Window;
|
||||
class WindowManager;
|
||||
|
||||
enum class WallpaperMode {
|
||||
Simple,
|
||||
Tile,
|
||||
Center,
|
||||
Scaled,
|
||||
Unchecked
|
||||
};
|
||||
|
||||
class Compositor final : public Core::Object {
|
||||
C_OBJECT(Compositor)
|
||||
public:
|
||||
static Compositor& the();
|
||||
|
||||
void compose();
|
||||
void invalidate_window();
|
||||
void invalidate_screen();
|
||||
void invalidate_screen(const Gfx::IntRect&);
|
||||
|
||||
bool set_resolution(int desired_width, int desired_height);
|
||||
|
||||
bool set_background_color(const String& background_color);
|
||||
|
||||
bool set_wallpaper_mode(const String& mode);
|
||||
|
||||
bool set_wallpaper(const String& path, Function<void(bool)>&& callback);
|
||||
String wallpaper_path() const { return m_wallpaper_path; }
|
||||
|
||||
void invalidate_cursor(bool = false);
|
||||
Gfx::IntRect current_cursor_rect() const;
|
||||
|
||||
void increment_display_link_count(Badge<ClientConnection>);
|
||||
void decrement_display_link_count(Badge<ClientConnection>);
|
||||
|
||||
void invalidate_occlusions() { m_occlusions_dirty = true; }
|
||||
|
||||
void did_construct_window_manager(Badge<WindowManager>);
|
||||
|
||||
private:
|
||||
Compositor();
|
||||
void init_bitmaps();
|
||||
void flip_buffers();
|
||||
void flush(const Gfx::IntRect&);
|
||||
void draw_menubar();
|
||||
void run_animations(Gfx::DisjointRectSet&);
|
||||
void notify_display_links();
|
||||
void start_compose_async_timer();
|
||||
void recompute_occlusions();
|
||||
bool any_opaque_window_above_this_one_contains_rect(const Window&, const Gfx::IntRect&);
|
||||
void change_cursor(const Cursor*);
|
||||
void draw_cursor(const Gfx::IntRect&);
|
||||
void restore_cursor_back();
|
||||
bool draw_geometry_label(Gfx::IntRect&);
|
||||
|
||||
RefPtr<Core::Timer> m_compose_timer;
|
||||
RefPtr<Core::Timer> m_immediate_compose_timer;
|
||||
bool m_flash_flush { false };
|
||||
bool m_buffers_are_flipped { false };
|
||||
bool m_screen_can_set_buffer { false };
|
||||
bool m_occlusions_dirty { true };
|
||||
bool m_invalidated_any { true };
|
||||
bool m_invalidated_window { false };
|
||||
bool m_invalidated_cursor { false };
|
||||
|
||||
RefPtr<Gfx::Bitmap> m_front_bitmap;
|
||||
RefPtr<Gfx::Bitmap> m_back_bitmap;
|
||||
RefPtr<Gfx::Bitmap> m_temp_bitmap;
|
||||
OwnPtr<Gfx::Painter> m_back_painter;
|
||||
OwnPtr<Gfx::Painter> m_front_painter;
|
||||
OwnPtr<Gfx::Painter> m_temp_painter;
|
||||
|
||||
Gfx::DisjointRectSet m_dirty_screen_rects;
|
||||
Gfx::DisjointRectSet m_opaque_wallpaper_rects;
|
||||
|
||||
RefPtr<Gfx::Bitmap> m_cursor_back_bitmap;
|
||||
OwnPtr<Gfx::Painter> m_cursor_back_painter;
|
||||
Gfx::IntRect m_last_cursor_rect;
|
||||
Gfx::IntRect m_last_dnd_rect;
|
||||
Gfx::IntRect m_last_geometry_label_damage_rect;
|
||||
|
||||
String m_wallpaper_path { "" };
|
||||
WallpaperMode m_wallpaper_mode { WallpaperMode::Unchecked };
|
||||
RefPtr<Gfx::Bitmap> m_wallpaper;
|
||||
|
||||
const Cursor* m_current_cursor { nullptr };
|
||||
unsigned m_current_cursor_frame { 0 };
|
||||
RefPtr<Core::Timer> m_cursor_timer;
|
||||
|
||||
RefPtr<Core::Timer> m_display_link_notify_timer;
|
||||
size_t m_display_link_count { 0 };
|
||||
|
||||
Optional<Gfx::Color> m_custom_background_color;
|
||||
};
|
||||
|
||||
}
|
185
Userland/Services/WindowServer/Cursor.cpp
Normal file
185
Userland/Services/WindowServer/Cursor.cpp
Normal file
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <WindowServer/Cursor.h>
|
||||
#include <WindowServer/WindowManager.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
CursorParams CursorParams::parse_from_file_name(const StringView& cursor_path, const Gfx::IntPoint& default_hotspot)
|
||||
{
|
||||
LexicalPath path(cursor_path);
|
||||
if (!path.is_valid()) {
|
||||
dbgln("Cannot parse invalid cursor path, use default cursor params");
|
||||
return { default_hotspot };
|
||||
}
|
||||
auto file_title = path.title();
|
||||
auto last_dot_in_title = StringView(file_title).find_last_of('.');
|
||||
if (!last_dot_in_title.has_value() || last_dot_in_title.value() == 0) {
|
||||
// No encoded params in filename. Not an error, we'll just use defaults
|
||||
return { default_hotspot };
|
||||
}
|
||||
auto params_str = file_title.substring_view(last_dot_in_title.value() + 1);
|
||||
|
||||
CursorParams params(default_hotspot);
|
||||
for (size_t i = 0; i + 1 < params_str.length();) {
|
||||
auto property = params_str[i++];
|
||||
|
||||
auto value = [&]() -> Optional<size_t> {
|
||||
size_t k = i;
|
||||
while (k < params_str.length()) {
|
||||
auto ch = params_str[k];
|
||||
if (ch < '0' || ch > '9')
|
||||
break;
|
||||
k++;
|
||||
}
|
||||
if (k == i)
|
||||
return {};
|
||||
auto parsed_number = params_str.substring_view(i, k - i).to_uint();
|
||||
if (!parsed_number.has_value())
|
||||
return {};
|
||||
i = k;
|
||||
return parsed_number.value();
|
||||
}();
|
||||
if (!value.has_value()) {
|
||||
dbg() << "Failed to parse value for property '" << property << "' from parsed cursor path: " << cursor_path;
|
||||
return { default_hotspot };
|
||||
}
|
||||
switch (property) {
|
||||
case 'x':
|
||||
params.m_hotspot.set_x(value.value());
|
||||
params.m_have_hotspot = true;
|
||||
break;
|
||||
case 'y':
|
||||
params.m_hotspot.set_y(value.value());
|
||||
params.m_have_hotspot = true;
|
||||
break;
|
||||
case 'f':
|
||||
if (value.value() > 1)
|
||||
params.m_frames = value.value();
|
||||
break;
|
||||
case 't':
|
||||
if (value.value() >= 100 && value.value() <= 1000)
|
||||
params.m_frame_ms = value.value();
|
||||
else
|
||||
dbgln("Cursor frame rate outside of valid range (100-1000ms)");
|
||||
break;
|
||||
default:
|
||||
dbg() << "Ignore unknown property '" << property << "' with value " << value.value() << " parsed from cursor path: " << cursor_path;
|
||||
return { default_hotspot };
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
CursorParams CursorParams::constrained(const Gfx::Bitmap& bitmap) const
|
||||
{
|
||||
CursorParams params(*this);
|
||||
auto rect = bitmap.rect();
|
||||
if (params.m_frames > 1) {
|
||||
if (rect.width() % params.m_frames == 0) {
|
||||
rect.set_width(rect.width() / (int)params.m_frames);
|
||||
} else {
|
||||
dbg() << "Cannot divide cursor dimensions " << rect << " into " << params.m_frames << " frames";
|
||||
params.m_frames = 1;
|
||||
}
|
||||
}
|
||||
if (params.m_have_hotspot)
|
||||
params.m_hotspot = params.m_hotspot.constrained(rect);
|
||||
else
|
||||
params.m_hotspot = rect.center();
|
||||
return params;
|
||||
}
|
||||
|
||||
Cursor::Cursor(NonnullRefPtr<Gfx::Bitmap>&& bitmap, const CursorParams& cursor_params)
|
||||
: m_bitmap(move(bitmap))
|
||||
, m_params(cursor_params.constrained(*m_bitmap))
|
||||
, m_rect(m_bitmap->rect())
|
||||
{
|
||||
if (m_params.frames() > 1) {
|
||||
ASSERT(m_rect.width() % m_params.frames() == 0);
|
||||
m_rect.set_width(m_rect.width() / m_params.frames());
|
||||
}
|
||||
}
|
||||
|
||||
Cursor::~Cursor()
|
||||
{
|
||||
}
|
||||
|
||||
NonnullRefPtr<Cursor> Cursor::create(NonnullRefPtr<Gfx::Bitmap>&& bitmap)
|
||||
{
|
||||
auto hotspot = bitmap->rect().center();
|
||||
return adopt(*new Cursor(move(bitmap), CursorParams(hotspot)));
|
||||
}
|
||||
|
||||
NonnullRefPtr<Cursor> Cursor::create(NonnullRefPtr<Gfx::Bitmap>&& bitmap, const StringView& filename)
|
||||
{
|
||||
auto default_hotspot = bitmap->rect().center();
|
||||
return adopt(*new Cursor(move(bitmap), CursorParams::parse_from_file_name(filename, default_hotspot)));
|
||||
}
|
||||
|
||||
RefPtr<Cursor> Cursor::create(Gfx::StandardCursor standard_cursor)
|
||||
{
|
||||
switch (standard_cursor) {
|
||||
case Gfx::StandardCursor::None:
|
||||
return nullptr;
|
||||
case Gfx::StandardCursor::Hidden:
|
||||
return WindowManager::the().hidden_cursor();
|
||||
case Gfx::StandardCursor::Arrow:
|
||||
return WindowManager::the().arrow_cursor();
|
||||
case Gfx::StandardCursor::Crosshair:
|
||||
return WindowManager::the().crosshair_cursor();
|
||||
case Gfx::StandardCursor::IBeam:
|
||||
return WindowManager::the().i_beam_cursor();
|
||||
case Gfx::StandardCursor::ResizeHorizontal:
|
||||
return WindowManager::the().resize_horizontally_cursor();
|
||||
case Gfx::StandardCursor::ResizeVertical:
|
||||
return WindowManager::the().resize_vertically_cursor();
|
||||
case Gfx::StandardCursor::ResizeDiagonalTLBR:
|
||||
return WindowManager::the().resize_diagonally_tlbr_cursor();
|
||||
case Gfx::StandardCursor::ResizeDiagonalBLTR:
|
||||
return WindowManager::the().resize_diagonally_bltr_cursor();
|
||||
case Gfx::StandardCursor::ResizeColumn:
|
||||
return WindowManager::the().resize_column_cursor();
|
||||
case Gfx::StandardCursor::ResizeRow:
|
||||
return WindowManager::the().resize_row_cursor();
|
||||
case Gfx::StandardCursor::Hand:
|
||||
return WindowManager::the().hand_cursor();
|
||||
case Gfx::StandardCursor::Help:
|
||||
return WindowManager::the().help_cursor();
|
||||
case Gfx::StandardCursor::Drag:
|
||||
return WindowManager::the().drag_cursor();
|
||||
case Gfx::StandardCursor::Move:
|
||||
return WindowManager::the().move_cursor();
|
||||
case Gfx::StandardCursor::Wait:
|
||||
return WindowManager::the().wait_cursor();
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
81
Userland/Services/WindowServer/Cursor.h
Normal file
81
Userland/Services/WindowServer/Cursor.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/StandardCursor.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
class CursorParams {
|
||||
public:
|
||||
static CursorParams parse_from_file_name(const StringView&, const Gfx::IntPoint&);
|
||||
CursorParams(const Gfx::IntPoint& hotspot)
|
||||
: m_hotspot(hotspot)
|
||||
{
|
||||
}
|
||||
CursorParams constrained(const Gfx::Bitmap&) const;
|
||||
|
||||
const Gfx::IntPoint& hotspot() const { return m_hotspot; }
|
||||
unsigned frames() const { return m_frames; }
|
||||
unsigned frame_ms() const { return m_frame_ms; }
|
||||
|
||||
private:
|
||||
CursorParams() = default;
|
||||
Gfx::IntPoint m_hotspot;
|
||||
unsigned m_frames { 1 };
|
||||
unsigned m_frame_ms { 0 };
|
||||
bool m_have_hotspot { false };
|
||||
};
|
||||
|
||||
class Cursor : public RefCounted<Cursor> {
|
||||
public:
|
||||
static NonnullRefPtr<Cursor> create(NonnullRefPtr<Gfx::Bitmap>&&, const StringView&);
|
||||
static NonnullRefPtr<Cursor> create(NonnullRefPtr<Gfx::Bitmap>&&);
|
||||
static RefPtr<Cursor> create(Gfx::StandardCursor);
|
||||
~Cursor();
|
||||
|
||||
const CursorParams& params() const { return m_params; }
|
||||
const Gfx::Bitmap& bitmap() const { return *m_bitmap; }
|
||||
|
||||
Gfx::IntRect source_rect(unsigned frame) const
|
||||
{
|
||||
return m_rect.translated(frame * m_rect.width(), 0);
|
||||
}
|
||||
|
||||
Gfx::IntRect rect() const { return m_rect; }
|
||||
Gfx::IntSize size() const { return m_rect.size(); }
|
||||
|
||||
private:
|
||||
Cursor(NonnullRefPtr<Gfx::Bitmap>&&, const CursorParams&);
|
||||
|
||||
RefPtr<Gfx::Bitmap> m_bitmap;
|
||||
CursorParams m_params;
|
||||
Gfx::IntRect m_rect;
|
||||
};
|
||||
|
||||
}
|
166
Userland/Services/WindowServer/Event.h
Normal file
166
Userland/Services/WindowServer/Event.h
Normal file
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <Kernel/API/KeyCode.h>
|
||||
#include <LibCore/Event.h>
|
||||
#include <LibCore/MimeData.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <WindowServer/Cursor.h>
|
||||
#include <WindowServer/WindowType.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
class Event : public Core::Event {
|
||||
public:
|
||||
enum Type {
|
||||
Invalid = 3000,
|
||||
MouseMove,
|
||||
MouseDown,
|
||||
MouseDoubleClick,
|
||||
MouseUp,
|
||||
MouseWheel,
|
||||
WindowEntered,
|
||||
WindowLeft,
|
||||
KeyDown,
|
||||
KeyUp,
|
||||
WindowActivated,
|
||||
WindowDeactivated,
|
||||
WindowInputEntered,
|
||||
WindowInputLeft,
|
||||
WindowCloseRequest,
|
||||
WindowResized,
|
||||
};
|
||||
|
||||
Event() { }
|
||||
explicit Event(Type type)
|
||||
: Core::Event(type)
|
||||
{
|
||||
}
|
||||
virtual ~Event() { }
|
||||
|
||||
bool is_mouse_event() const { return type() == MouseMove || type() == MouseDown || type() == MouseDoubleClick || type() == MouseUp || type() == MouseWheel; }
|
||||
bool is_key_event() const { return type() == KeyUp || type() == KeyDown; }
|
||||
};
|
||||
|
||||
enum class MouseButton : u8 {
|
||||
None = 0,
|
||||
Left = 1,
|
||||
Right = 2,
|
||||
Middle = 4,
|
||||
Back = 8,
|
||||
Forward = 16,
|
||||
};
|
||||
|
||||
class KeyEvent final : public Event {
|
||||
public:
|
||||
KeyEvent(Type type, int key, u32 code_point, u8 modifiers, u32 scancode)
|
||||
: Event(type)
|
||||
, m_key(key)
|
||||
, m_code_point(code_point)
|
||||
, m_modifiers(modifiers)
|
||||
, m_scancode(scancode)
|
||||
{
|
||||
}
|
||||
|
||||
int key() const { return m_key; }
|
||||
bool ctrl() const { return m_modifiers & Mod_Ctrl; }
|
||||
bool alt() const { return m_modifiers & Mod_Alt; }
|
||||
bool shift() const { return m_modifiers & Mod_Shift; }
|
||||
bool logo() const { return m_modifiers & Mod_Logo; }
|
||||
u8 modifiers() const { return m_modifiers; }
|
||||
u32 code_point() const { return m_code_point; }
|
||||
u32 scancode() const { return m_scancode; }
|
||||
|
||||
private:
|
||||
friend class EventLoop;
|
||||
friend class Screen;
|
||||
int m_key { 0 };
|
||||
u32 m_code_point { 0 };
|
||||
u8 m_modifiers { 0 };
|
||||
u32 m_scancode { 0 };
|
||||
};
|
||||
|
||||
class MouseEvent final : public Event {
|
||||
public:
|
||||
MouseEvent(Type type, const Gfx::IntPoint& position, unsigned buttons, MouseButton button, unsigned modifiers, int wheel_delta = 0)
|
||||
: Event(type)
|
||||
, m_position(position)
|
||||
, m_buttons(buttons)
|
||||
, m_button(button)
|
||||
, m_modifiers(modifiers)
|
||||
, m_wheel_delta(wheel_delta)
|
||||
{
|
||||
}
|
||||
|
||||
const Gfx::IntPoint& position() const { return m_position; }
|
||||
int x() const { return m_position.x(); }
|
||||
int y() const { return m_position.y(); }
|
||||
MouseButton button() const { return m_button; }
|
||||
unsigned buttons() const { return m_buttons; }
|
||||
unsigned modifiers() const { return m_modifiers; }
|
||||
int wheel_delta() const { return m_wheel_delta; }
|
||||
bool is_drag() const { return m_drag; }
|
||||
|
||||
Vector<String> mime_types() const
|
||||
{
|
||||
if (!m_mime_data)
|
||||
return {};
|
||||
return m_mime_data->formats();
|
||||
}
|
||||
|
||||
void set_drag(bool b) { m_drag = b; }
|
||||
void set_mime_data(const Core::MimeData& mime_data) { m_mime_data = mime_data; }
|
||||
|
||||
MouseEvent translated(const Gfx::IntPoint& delta) const { return MouseEvent((Type)type(), m_position.translated(delta), m_buttons, m_button, m_modifiers, m_wheel_delta); }
|
||||
|
||||
private:
|
||||
Gfx::IntPoint m_position;
|
||||
unsigned m_buttons { 0 };
|
||||
MouseButton m_button { MouseButton::None };
|
||||
unsigned m_modifiers { 0 };
|
||||
int m_wheel_delta { 0 };
|
||||
bool m_drag { false };
|
||||
RefPtr<const Core::MimeData> m_mime_data;
|
||||
};
|
||||
|
||||
class ResizeEvent final : public Event {
|
||||
public:
|
||||
ResizeEvent(const Gfx::IntRect& rect)
|
||||
: Event(Event::WindowResized)
|
||||
, m_rect(rect)
|
||||
{
|
||||
}
|
||||
|
||||
const Gfx::IntRect& rect() const { return m_rect; }
|
||||
|
||||
private:
|
||||
Gfx::IntRect m_rect;
|
||||
};
|
||||
|
||||
}
|
149
Userland/Services/WindowServer/EventLoop.cpp
Normal file
149
Userland/Services/WindowServer/EventLoop.cpp
Normal file
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <Kernel/API/MousePacket.h>
|
||||
#include <LibCore/LocalSocket.h>
|
||||
#include <LibCore/Object.h>
|
||||
#include <WindowServer/ClientConnection.h>
|
||||
#include <WindowServer/Cursor.h>
|
||||
#include <WindowServer/Event.h>
|
||||
#include <WindowServer/EventLoop.h>
|
||||
#include <WindowServer/Screen.h>
|
||||
#include <WindowServer/WindowManager.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
//#define WSMESSAGELOOP_DEBUG
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
EventLoop::EventLoop()
|
||||
: m_server(Core::LocalServer::construct())
|
||||
{
|
||||
m_keyboard_fd = open("/dev/keyboard", O_RDONLY | O_NONBLOCK | O_CLOEXEC);
|
||||
m_mouse_fd = open("/dev/mouse", O_RDONLY | O_NONBLOCK | O_CLOEXEC);
|
||||
|
||||
bool ok = m_server->take_over_from_system_server();
|
||||
ASSERT(ok);
|
||||
|
||||
m_server->on_ready_to_accept = [this] {
|
||||
auto client_socket = m_server->accept();
|
||||
if (!client_socket) {
|
||||
dbgln("WindowServer: accept failed.");
|
||||
return;
|
||||
}
|
||||
static int s_next_client_id = 0;
|
||||
int client_id = ++s_next_client_id;
|
||||
IPC::new_client_connection<ClientConnection>(client_socket.release_nonnull(), client_id);
|
||||
};
|
||||
|
||||
ASSERT(m_keyboard_fd >= 0);
|
||||
ASSERT(m_mouse_fd >= 0);
|
||||
|
||||
m_keyboard_notifier = Core::Notifier::construct(m_keyboard_fd, Core::Notifier::Read);
|
||||
m_keyboard_notifier->on_ready_to_read = [this] { drain_keyboard(); };
|
||||
|
||||
m_mouse_notifier = Core::Notifier::construct(m_mouse_fd, Core::Notifier::Read);
|
||||
m_mouse_notifier->on_ready_to_read = [this] { drain_mouse(); };
|
||||
}
|
||||
|
||||
EventLoop::~EventLoop()
|
||||
{
|
||||
}
|
||||
|
||||
void EventLoop::drain_mouse()
|
||||
{
|
||||
auto& screen = Screen::the();
|
||||
MousePacket state;
|
||||
state.buttons = screen.mouse_button_state();
|
||||
unsigned buttons = state.buttons;
|
||||
MousePacket packets[32];
|
||||
|
||||
ssize_t nread = read(m_mouse_fd, &packets, sizeof(packets));
|
||||
if (nread < 0) {
|
||||
perror("EventLoop::drain_mouse read");
|
||||
return;
|
||||
}
|
||||
size_t npackets = nread / sizeof(MousePacket);
|
||||
if (!npackets)
|
||||
return;
|
||||
for (size_t i = 0; i < npackets; ++i) {
|
||||
auto& packet = packets[i];
|
||||
#ifdef WSMESSAGELOOP_DEBUG
|
||||
dbgln("EventLoop: Mouse X {}, Y {}, Z {}, relative={}", packet.x, packet.y, packet.z, packet.is_relative);
|
||||
#endif
|
||||
buttons = packet.buttons;
|
||||
|
||||
state.is_relative = packet.is_relative;
|
||||
if (packet.is_relative) {
|
||||
state.x += packet.x;
|
||||
state.y -= packet.y;
|
||||
state.z += packet.z;
|
||||
} else {
|
||||
state.x = packet.x;
|
||||
state.y = packet.y;
|
||||
state.z += packet.z;
|
||||
}
|
||||
|
||||
if (buttons != state.buttons) {
|
||||
state.buttons = buttons;
|
||||
#ifdef WSMESSAGELOOP_DEBUG
|
||||
dbgln("EventLoop: Mouse Button Event");
|
||||
#endif
|
||||
screen.on_receive_mouse_data(state);
|
||||
if (state.is_relative) {
|
||||
state.x = 0;
|
||||
state.y = 0;
|
||||
state.z = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (state.is_relative && (state.x || state.y || state.z))
|
||||
screen.on_receive_mouse_data(state);
|
||||
if (!state.is_relative)
|
||||
screen.on_receive_mouse_data(state);
|
||||
}
|
||||
|
||||
void EventLoop::drain_keyboard()
|
||||
{
|
||||
auto& screen = Screen::the();
|
||||
for (;;) {
|
||||
::KeyEvent event;
|
||||
ssize_t nread = read(m_keyboard_fd, (u8*)&event, sizeof(::KeyEvent));
|
||||
if (nread == 0)
|
||||
break;
|
||||
ASSERT(nread == sizeof(::KeyEvent));
|
||||
screen.on_receive_keyboard_data(event);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
57
Userland/Services/WindowServer/EventLoop.h
Normal file
57
Userland/Services/WindowServer/EventLoop.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/LocalServer.h>
|
||||
#include <LibCore/Notifier.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
class ClientConnection;
|
||||
|
||||
class EventLoop {
|
||||
public:
|
||||
EventLoop();
|
||||
virtual ~EventLoop();
|
||||
|
||||
int exec() { return m_event_loop.exec(); }
|
||||
|
||||
private:
|
||||
void drain_mouse();
|
||||
void drain_keyboard();
|
||||
|
||||
Core::EventLoop m_event_loop;
|
||||
int m_keyboard_fd { -1 };
|
||||
RefPtr<Core::Notifier> m_keyboard_notifier;
|
||||
int m_mouse_fd { -1 };
|
||||
RefPtr<Core::Notifier> m_mouse_notifier;
|
||||
RefPtr<Core::LocalServer> m_server;
|
||||
};
|
||||
|
||||
}
|
568
Userland/Services/WindowServer/Menu.cpp
Normal file
568
Userland/Services/WindowServer/Menu.cpp
Normal file
|
@ -0,0 +1,568 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020, Shannon Booth <shannon.ml.booth@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "Menu.h"
|
||||
#include "Event.h"
|
||||
#include "MenuItem.h"
|
||||
#include "MenuManager.h"
|
||||
#include "Screen.h"
|
||||
#include "Window.h"
|
||||
#include "WindowManager.h"
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/CharacterBitmap.h>
|
||||
#include <LibGfx/Font.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <LibGfx/StylePainter.h>
|
||||
#include <LibGfx/Triangle.h>
|
||||
#include <WindowServer/ClientConnection.h>
|
||||
#include <WindowServer/WindowClientEndpoint.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
Menu::Menu(ClientConnection* client, int menu_id, const String& name)
|
||||
: Core::Object(client)
|
||||
, m_client(client)
|
||||
, m_menu_id(menu_id)
|
||||
, m_name(move(name))
|
||||
{
|
||||
}
|
||||
|
||||
Menu::~Menu()
|
||||
{
|
||||
}
|
||||
|
||||
void Menu::set_title_font(const Gfx::Font& font)
|
||||
{
|
||||
m_title_font = &font;
|
||||
}
|
||||
|
||||
const Gfx::Font& Menu::title_font() const
|
||||
{
|
||||
return *m_title_font;
|
||||
}
|
||||
|
||||
const Gfx::Font& Menu::font() const
|
||||
{
|
||||
return Gfx::FontDatabase::default_font();
|
||||
}
|
||||
|
||||
static const char* s_checked_bitmap_data = {
|
||||
" "
|
||||
" # "
|
||||
" ## "
|
||||
" ### "
|
||||
" ## ### "
|
||||
" ##### "
|
||||
" ### "
|
||||
" # "
|
||||
" "
|
||||
};
|
||||
|
||||
static const char* s_submenu_arrow_bitmap_data = {
|
||||
" "
|
||||
" # "
|
||||
" ## "
|
||||
" ### "
|
||||
" #### "
|
||||
" ### "
|
||||
" ## "
|
||||
" # "
|
||||
" "
|
||||
};
|
||||
|
||||
static Gfx::CharacterBitmap* s_checked_bitmap;
|
||||
static const int s_checked_bitmap_width = 9;
|
||||
static const int s_checked_bitmap_height = 9;
|
||||
static const int s_submenu_arrow_bitmap_width = 9;
|
||||
static const int s_submenu_arrow_bitmap_height = 9;
|
||||
static const int s_item_icon_width = 16;
|
||||
static const int s_stripe_width = 23;
|
||||
|
||||
int Menu::content_width() const
|
||||
{
|
||||
int widest_text = 0;
|
||||
int widest_shortcut = 0;
|
||||
for (auto& item : m_items) {
|
||||
if (item.type() != MenuItem::Text)
|
||||
continue;
|
||||
auto& use_font = item.is_default() ? Gfx::FontDatabase::default_bold_font() : font();
|
||||
int text_width = use_font.width(item.text());
|
||||
if (!item.shortcut_text().is_empty()) {
|
||||
int shortcut_width = use_font.width(item.shortcut_text());
|
||||
widest_shortcut = max(shortcut_width, widest_shortcut);
|
||||
}
|
||||
widest_text = max(widest_text, text_width);
|
||||
}
|
||||
|
||||
int widest_item = widest_text + s_stripe_width;
|
||||
if (widest_shortcut)
|
||||
widest_item += padding_between_text_and_shortcut() + widest_shortcut;
|
||||
|
||||
return max(widest_item, rect_in_menubar().width()) + horizontal_padding() + frame_thickness() * 2;
|
||||
}
|
||||
|
||||
void Menu::redraw()
|
||||
{
|
||||
if (!menu_window())
|
||||
return;
|
||||
draw();
|
||||
menu_window()->invalidate();
|
||||
}
|
||||
|
||||
Window& Menu::ensure_menu_window()
|
||||
{
|
||||
if (m_menu_window)
|
||||
return *m_menu_window;
|
||||
|
||||
int width = this->content_width();
|
||||
|
||||
Gfx::IntPoint next_item_location(frame_thickness(), frame_thickness());
|
||||
for (auto& item : m_items) {
|
||||
int height = 0;
|
||||
if (item.type() == MenuItem::Text)
|
||||
height = item_height();
|
||||
else if (item.type() == MenuItem::Separator)
|
||||
height = 8;
|
||||
item.set_rect({ next_item_location, { width - frame_thickness() * 2, height } });
|
||||
next_item_location.move_by(0, height);
|
||||
}
|
||||
|
||||
int window_height_available = Screen::the().height() - MenuManager::the().menubar_rect().height() - frame_thickness() * 2;
|
||||
int max_window_height = (window_height_available / item_height()) * item_height() + frame_thickness() * 2;
|
||||
int content_height = m_items.is_empty() ? 0 : (m_items.last().rect().bottom() + 1) + frame_thickness();
|
||||
int window_height = min(max_window_height, content_height);
|
||||
if (window_height < content_height) {
|
||||
m_scrollable = true;
|
||||
m_max_scroll_offset = item_count() - window_height / item_height() + 2;
|
||||
}
|
||||
|
||||
auto window = Window::construct(*this, WindowType::Menu);
|
||||
window->set_rect(0, 0, width, window_height);
|
||||
m_menu_window = move(window);
|
||||
draw();
|
||||
|
||||
return *m_menu_window;
|
||||
}
|
||||
|
||||
int Menu::visible_item_count() const
|
||||
{
|
||||
if (!is_scrollable())
|
||||
return m_items.size();
|
||||
ASSERT(m_menu_window);
|
||||
// Make space for up/down arrow indicators
|
||||
return m_menu_window->height() / item_height() - 2;
|
||||
}
|
||||
|
||||
void Menu::draw()
|
||||
{
|
||||
auto palette = WindowManager::the().palette();
|
||||
m_theme_index_at_last_paint = MenuManager::the().theme_index();
|
||||
|
||||
ASSERT(menu_window());
|
||||
ASSERT(menu_window()->backing_store());
|
||||
Gfx::Painter painter(*menu_window()->backing_store());
|
||||
|
||||
Gfx::IntRect rect { {}, menu_window()->size() };
|
||||
Gfx::StylePainter::paint_window_frame(painter, rect, palette);
|
||||
painter.fill_rect(rect.shrunken(6, 6), palette.menu_base());
|
||||
int width = this->content_width();
|
||||
|
||||
if (!s_checked_bitmap)
|
||||
s_checked_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_checked_bitmap_data, s_checked_bitmap_width, s_checked_bitmap_height).leak_ref();
|
||||
|
||||
bool has_checkable_items = false;
|
||||
bool has_items_with_icon = false;
|
||||
for (auto& item : m_items) {
|
||||
has_checkable_items = has_checkable_items | item.is_checkable();
|
||||
has_items_with_icon = has_items_with_icon | !!item.icon();
|
||||
}
|
||||
|
||||
Gfx::IntRect stripe_rect { frame_thickness(), frame_thickness(), s_stripe_width, menu_window()->height() - frame_thickness() * 2 };
|
||||
painter.fill_rect(stripe_rect, palette.menu_stripe());
|
||||
painter.draw_line(stripe_rect.top_right(), stripe_rect.bottom_right(), palette.menu_stripe().darkened());
|
||||
|
||||
int visible_item_count = this->visible_item_count();
|
||||
|
||||
if (is_scrollable()) {
|
||||
bool can_go_up = m_scroll_offset > 0;
|
||||
bool can_go_down = m_scroll_offset < m_max_scroll_offset;
|
||||
Gfx::IntRect up_indicator_rect { frame_thickness(), frame_thickness(), content_width(), item_height() };
|
||||
painter.draw_text(up_indicator_rect, "\xE2\xAC\x86", Gfx::TextAlignment::Center, can_go_up ? palette.menu_base_text() : palette.color(ColorRole::DisabledText));
|
||||
Gfx::IntRect down_indicator_rect { frame_thickness(), menu_window()->height() - item_height() - frame_thickness(), content_width(), item_height() };
|
||||
painter.draw_text(down_indicator_rect, "\xE2\xAC\x87", Gfx::TextAlignment::Center, can_go_down ? palette.menu_base_text() : palette.color(ColorRole::DisabledText));
|
||||
}
|
||||
|
||||
for (int i = 0; i < visible_item_count; ++i) {
|
||||
auto& item = m_items.at(m_scroll_offset + i);
|
||||
if (item.type() == MenuItem::Text) {
|
||||
Color text_color = palette.menu_base_text();
|
||||
if (&item == hovered_item() && item.is_enabled()) {
|
||||
painter.fill_rect(item.rect(), palette.menu_selection());
|
||||
painter.draw_rect(item.rect(), palette.menu_selection().darkened());
|
||||
text_color = palette.menu_selection_text();
|
||||
} else if (!item.is_enabled()) {
|
||||
text_color = Color::MidGray;
|
||||
}
|
||||
Gfx::IntRect text_rect = item.rect().translated(stripe_rect.width() + 6, 0);
|
||||
if (item.is_checkable()) {
|
||||
if (item.is_exclusive()) {
|
||||
Gfx::IntRect radio_rect { item.rect().x() + 5, 0, 12, 12 };
|
||||
radio_rect.center_vertically_within(text_rect);
|
||||
Gfx::StylePainter::paint_radio_button(painter, radio_rect, palette, item.is_checked(), false);
|
||||
} else {
|
||||
Gfx::IntRect checkmark_rect { item.rect().x() + 7, 0, s_checked_bitmap_width, s_checked_bitmap_height };
|
||||
checkmark_rect.center_vertically_within(text_rect);
|
||||
Gfx::IntRect checkbox_rect = checkmark_rect.inflated(4, 4);
|
||||
painter.fill_rect(checkbox_rect, palette.base());
|
||||
Gfx::StylePainter::paint_frame(painter, checkbox_rect, palette, Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2);
|
||||
if (item.is_checked()) {
|
||||
painter.draw_bitmap(checkmark_rect.location(), *s_checked_bitmap, palette.button_text());
|
||||
}
|
||||
}
|
||||
} else if (item.icon()) {
|
||||
Gfx::IntRect icon_rect { item.rect().x() + 3, 0, s_item_icon_width, s_item_icon_width };
|
||||
icon_rect.center_vertically_within(text_rect);
|
||||
|
||||
if (&item == hovered_item() && item.is_enabled()) {
|
||||
auto shadow_color = palette.menu_selection().darkened(0.7f);
|
||||
painter.blit_filtered(icon_rect.location().translated(1, 1), *item.icon(), item.icon()->rect(), [&shadow_color](auto) {
|
||||
return shadow_color;
|
||||
});
|
||||
icon_rect.move_by(-1, -1);
|
||||
}
|
||||
if (item.is_enabled())
|
||||
painter.blit(icon_rect.location(), *item.icon(), item.icon()->rect());
|
||||
else
|
||||
painter.blit_disabled(icon_rect.location(), *item.icon(), item.icon()->rect(), palette);
|
||||
}
|
||||
auto& previous_font = painter.font();
|
||||
if (item.is_default())
|
||||
painter.set_font(Gfx::FontDatabase::default_bold_font());
|
||||
painter.draw_text(text_rect, item.text(), Gfx::TextAlignment::CenterLeft, text_color);
|
||||
if (!item.shortcut_text().is_empty()) {
|
||||
painter.draw_text(item.rect().translated(-right_padding(), 0), item.shortcut_text(), Gfx::TextAlignment::CenterRight, text_color);
|
||||
}
|
||||
painter.set_font(previous_font);
|
||||
if (item.is_submenu()) {
|
||||
static auto& submenu_arrow_bitmap = Gfx::CharacterBitmap::create_from_ascii(s_submenu_arrow_bitmap_data, s_submenu_arrow_bitmap_width, s_submenu_arrow_bitmap_height).leak_ref();
|
||||
Gfx::IntRect submenu_arrow_rect {
|
||||
item.rect().right() - s_submenu_arrow_bitmap_width - 2,
|
||||
0,
|
||||
s_submenu_arrow_bitmap_width,
|
||||
s_submenu_arrow_bitmap_height
|
||||
};
|
||||
submenu_arrow_rect.center_vertically_within(item.rect());
|
||||
painter.draw_bitmap(submenu_arrow_rect.location(), submenu_arrow_bitmap, text_color);
|
||||
}
|
||||
} else if (item.type() == MenuItem::Separator) {
|
||||
Gfx::IntPoint p1(item.rect().translated(stripe_rect.width() + 4, 0).x(), item.rect().center().y() - 1);
|
||||
Gfx::IntPoint p2(width - 7, item.rect().center().y() - 1);
|
||||
painter.draw_line(p1, p2, palette.threed_shadow1());
|
||||
painter.draw_line(p1.translated(0, 1), p2.translated(0, 1), palette.threed_highlight());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem* Menu::hovered_item() const
|
||||
{
|
||||
if (m_hovered_item_index == -1)
|
||||
return nullptr;
|
||||
return const_cast<MenuItem*>(&item(m_hovered_item_index));
|
||||
}
|
||||
|
||||
void Menu::update_for_new_hovered_item(bool make_input)
|
||||
{
|
||||
if (hovered_item() && hovered_item()->is_submenu()) {
|
||||
ASSERT(menu_window());
|
||||
MenuManager::the().close_everyone_not_in_lineage(*hovered_item()->submenu());
|
||||
hovered_item()->submenu()->do_popup(hovered_item()->rect().top_right().translated(menu_window()->rect().location()), make_input);
|
||||
} else {
|
||||
MenuManager::the().close_everyone_not_in_lineage(*this);
|
||||
ensure_menu_window().set_visible(true);
|
||||
}
|
||||
redraw();
|
||||
}
|
||||
|
||||
void Menu::open_hovered_item()
|
||||
{
|
||||
ASSERT(menu_window());
|
||||
ASSERT(menu_window()->is_visible());
|
||||
if (!hovered_item())
|
||||
return;
|
||||
if (hovered_item()->is_enabled())
|
||||
did_activate(*hovered_item());
|
||||
clear_hovered_item();
|
||||
}
|
||||
|
||||
void Menu::descend_into_submenu_at_hovered_item()
|
||||
{
|
||||
ASSERT(hovered_item());
|
||||
auto submenu = hovered_item()->submenu();
|
||||
ASSERT(submenu);
|
||||
MenuManager::the().open_menu(*submenu, false);
|
||||
submenu->set_hovered_item(0);
|
||||
ASSERT(submenu->hovered_item()->type() != MenuItem::Separator);
|
||||
}
|
||||
|
||||
void Menu::handle_mouse_move_event(const MouseEvent& mouse_event)
|
||||
{
|
||||
ASSERT(menu_window());
|
||||
MenuManager::the().set_current_menu(this);
|
||||
if (hovered_item() && hovered_item()->is_submenu()) {
|
||||
|
||||
auto item = *hovered_item();
|
||||
auto submenu_top_left = item.rect().location() + Gfx::IntPoint { item.rect().width(), 0 };
|
||||
auto submenu_bottom_left = submenu_top_left + Gfx::IntPoint { 0, item.submenu()->menu_window()->height() };
|
||||
|
||||
auto safe_hover_triangle = Gfx::Triangle { m_last_position_in_hover, submenu_top_left, submenu_bottom_left };
|
||||
m_last_position_in_hover = mouse_event.position();
|
||||
|
||||
// Don't update the hovered item if mouse is moving towards a submenu
|
||||
if (safe_hover_triangle.contains(mouse_event.position()))
|
||||
return;
|
||||
}
|
||||
|
||||
int index = item_index_at(mouse_event.position());
|
||||
if (m_hovered_item_index == index)
|
||||
return;
|
||||
m_hovered_item_index = index;
|
||||
|
||||
update_for_new_hovered_item();
|
||||
return;
|
||||
}
|
||||
|
||||
void Menu::event(Core::Event& event)
|
||||
{
|
||||
if (event.type() == Event::MouseMove) {
|
||||
handle_mouse_move_event(static_cast<const MouseEvent&>(event));
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type() == Event::MouseUp) {
|
||||
open_hovered_item();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type() == Event::MouseWheel && is_scrollable()) {
|
||||
ASSERT(menu_window());
|
||||
auto& mouse_event = static_cast<const MouseEvent&>(event);
|
||||
m_scroll_offset += mouse_event.wheel_delta();
|
||||
m_scroll_offset = clamp(m_scroll_offset, 0, m_max_scroll_offset);
|
||||
|
||||
int index = item_index_at(mouse_event.position());
|
||||
if (m_hovered_item_index == index)
|
||||
return;
|
||||
|
||||
m_hovered_item_index = index;
|
||||
update_for_new_hovered_item();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type() == Event::KeyDown) {
|
||||
auto key = static_cast<KeyEvent&>(event).key();
|
||||
|
||||
if (!(key == Key_Up || key == Key_Down || key == Key_Left || key == Key_Right || key == Key_Return))
|
||||
return;
|
||||
|
||||
ASSERT(menu_window());
|
||||
ASSERT(menu_window()->is_visible());
|
||||
|
||||
// Default to the first item on key press if one has not been selected yet
|
||||
if (!hovered_item()) {
|
||||
m_hovered_item_index = 0;
|
||||
update_for_new_hovered_item(key == Key_Right);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == Key_Up) {
|
||||
ASSERT(m_items.at(0).type() != MenuItem::Separator);
|
||||
|
||||
if (is_scrollable() && m_hovered_item_index == 0)
|
||||
return;
|
||||
|
||||
auto original_index = m_hovered_item_index;
|
||||
do {
|
||||
if (m_hovered_item_index == 0)
|
||||
m_hovered_item_index = m_items.size() - 1;
|
||||
else
|
||||
--m_hovered_item_index;
|
||||
if (m_hovered_item_index == original_index)
|
||||
return;
|
||||
} while (hovered_item()->type() == MenuItem::Separator || !hovered_item()->is_enabled());
|
||||
|
||||
ASSERT(m_hovered_item_index >= 0 && m_hovered_item_index <= static_cast<int>(m_items.size()) - 1);
|
||||
|
||||
if (is_scrollable() && m_hovered_item_index < m_scroll_offset)
|
||||
--m_scroll_offset;
|
||||
|
||||
update_for_new_hovered_item();
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == Key_Down) {
|
||||
ASSERT(m_items.at(0).type() != MenuItem::Separator);
|
||||
|
||||
if (is_scrollable() && m_hovered_item_index == static_cast<int>(m_items.size()) - 1)
|
||||
return;
|
||||
|
||||
auto original_index = m_hovered_item_index;
|
||||
do {
|
||||
if (m_hovered_item_index == static_cast<int>(m_items.size()) - 1)
|
||||
m_hovered_item_index = 0;
|
||||
else
|
||||
++m_hovered_item_index;
|
||||
if (m_hovered_item_index == original_index)
|
||||
return;
|
||||
} while (hovered_item()->type() == MenuItem::Separator || !hovered_item()->is_enabled());
|
||||
|
||||
ASSERT(m_hovered_item_index >= 0 && m_hovered_item_index <= static_cast<int>(m_items.size()) - 1);
|
||||
|
||||
if (is_scrollable() && m_hovered_item_index >= (m_scroll_offset + visible_item_count()))
|
||||
++m_scroll_offset;
|
||||
|
||||
update_for_new_hovered_item();
|
||||
return;
|
||||
}
|
||||
}
|
||||
Core::Object::event(event);
|
||||
}
|
||||
|
||||
void Menu::clear_hovered_item()
|
||||
{
|
||||
if (!hovered_item())
|
||||
return;
|
||||
m_hovered_item_index = -1;
|
||||
redraw();
|
||||
}
|
||||
|
||||
void Menu::did_activate(MenuItem& item)
|
||||
{
|
||||
if (item.type() == MenuItem::Type::Separator)
|
||||
return;
|
||||
|
||||
if (on_item_activation)
|
||||
on_item_activation(item);
|
||||
|
||||
MenuManager::the().close_bar();
|
||||
|
||||
if (m_client)
|
||||
m_client->post_message(Messages::WindowClient::MenuItemActivated(m_menu_id, item.identifier()));
|
||||
}
|
||||
|
||||
bool Menu::activate_default()
|
||||
{
|
||||
for (auto& item : m_items) {
|
||||
if (item.type() == MenuItem::Type::Separator)
|
||||
continue;
|
||||
if (item.is_enabled() && item.is_default()) {
|
||||
did_activate(item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
MenuItem* Menu::item_with_identifier(unsigned identifier)
|
||||
{
|
||||
for (auto& item : m_items) {
|
||||
if (item.identifier() == identifier)
|
||||
return &item;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int Menu::item_index_at(const Gfx::IntPoint& position)
|
||||
{
|
||||
int i = 0;
|
||||
for (auto& item : m_items) {
|
||||
if (item.rect().contains(position))
|
||||
return i;
|
||||
++i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Menu::close()
|
||||
{
|
||||
MenuManager::the().close_menu_and_descendants(*this);
|
||||
}
|
||||
|
||||
void Menu::redraw_if_theme_changed()
|
||||
{
|
||||
if (m_theme_index_at_last_paint != MenuManager::the().theme_index())
|
||||
redraw();
|
||||
}
|
||||
|
||||
void Menu::popup(const Gfx::IntPoint& position)
|
||||
{
|
||||
do_popup(position, true);
|
||||
}
|
||||
|
||||
void Menu::do_popup(const Gfx::IntPoint& position, bool make_input)
|
||||
{
|
||||
if (is_empty()) {
|
||||
dbgln("Menu: Empty menu popup");
|
||||
return;
|
||||
}
|
||||
|
||||
auto& window = ensure_menu_window();
|
||||
redraw_if_theme_changed();
|
||||
|
||||
const int margin = 30;
|
||||
Gfx::IntPoint adjusted_pos = position;
|
||||
|
||||
if (adjusted_pos.x() + window.width() >= Screen::the().width() - margin) {
|
||||
adjusted_pos = adjusted_pos.translated(-window.width(), 0);
|
||||
}
|
||||
if (adjusted_pos.y() + window.height() >= Screen::the().height() - margin) {
|
||||
adjusted_pos = adjusted_pos.translated(0, -window.height());
|
||||
}
|
||||
|
||||
if (adjusted_pos.y() < MenuManager::the().menubar_rect().height())
|
||||
adjusted_pos.set_y(MenuManager::the().menubar_rect().height());
|
||||
|
||||
window.move_to(adjusted_pos);
|
||||
window.set_visible(true);
|
||||
MenuManager::the().open_menu(*this, make_input);
|
||||
WindowManager::the().did_popup_a_menu({});
|
||||
}
|
||||
|
||||
bool Menu::is_menu_ancestor_of(const Menu& other) const
|
||||
{
|
||||
for (auto& item : m_items) {
|
||||
if (!item.is_submenu())
|
||||
continue;
|
||||
auto& submenu = *item.submenu();
|
||||
if (&submenu == &other)
|
||||
return true;
|
||||
if (submenu.is_menu_ancestor_of(other))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
169
Userland/Services/WindowServer/Menu.h
Normal file
169
Userland/Services/WindowServer/Menu.h
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/NonnullOwnPtrVector.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibCore/Object.h>
|
||||
#include <LibGfx/Font.h>
|
||||
#include <LibGfx/FontDatabase.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <WindowServer/Cursor.h>
|
||||
#include <WindowServer/MenuItem.h>
|
||||
#include <WindowServer/Window.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
class ClientConnection;
|
||||
class MenuBar;
|
||||
class Event;
|
||||
|
||||
class Menu final : public Core::Object {
|
||||
C_OBJECT(Menu)
|
||||
public:
|
||||
Menu(ClientConnection*, int menu_id, const String& name);
|
||||
virtual ~Menu() override;
|
||||
|
||||
ClientConnection* client() { return m_client; }
|
||||
const ClientConnection* client() const { return m_client; }
|
||||
int menu_id() const { return m_menu_id; }
|
||||
|
||||
MenuBar* menubar() { return m_menubar; }
|
||||
const MenuBar* menubar() const { return m_menubar; }
|
||||
void set_menubar(MenuBar* menubar) { m_menubar = menubar; }
|
||||
|
||||
bool is_empty() const { return m_items.is_empty(); }
|
||||
int item_count() const { return m_items.size(); }
|
||||
const MenuItem& item(int index) const { return m_items.at(index); }
|
||||
MenuItem& item(int index) { return m_items.at(index); }
|
||||
|
||||
void add_item(NonnullOwnPtr<MenuItem>&& item) { m_items.append(move(item)); }
|
||||
|
||||
String name() const { return m_name; }
|
||||
|
||||
template<typename Callback>
|
||||
void for_each_item(Callback callback) const
|
||||
{
|
||||
for (auto& item : m_items)
|
||||
callback(item);
|
||||
}
|
||||
|
||||
Gfx::IntRect text_rect_in_menubar() const { return m_text_rect_in_menubar; }
|
||||
void set_text_rect_in_menubar(const Gfx::IntRect& rect) { m_text_rect_in_menubar = rect; }
|
||||
|
||||
Gfx::IntRect rect_in_menubar() const { return m_rect_in_menubar; }
|
||||
void set_rect_in_menubar(const Gfx::IntRect& rect) { m_rect_in_menubar = rect; }
|
||||
|
||||
Window* menu_window() { return m_menu_window.ptr(); }
|
||||
Window& ensure_menu_window();
|
||||
|
||||
Window* window_menu_of() { return m_window_menu_of; }
|
||||
void set_window_menu_of(Window& window) { m_window_menu_of = window; }
|
||||
bool is_window_menu_open() { return m_is_window_menu_open; }
|
||||
void set_window_menu_open(bool is_open) { m_is_window_menu_open = is_open; }
|
||||
|
||||
bool activate_default();
|
||||
|
||||
int content_width() const;
|
||||
|
||||
int item_height() const { return 20; }
|
||||
int frame_thickness() const { return 3; }
|
||||
int horizontal_padding() const { return left_padding() + right_padding(); }
|
||||
int left_padding() const { return 14; }
|
||||
int right_padding() const { return 14; }
|
||||
|
||||
void draw();
|
||||
const Gfx::Font& font() const;
|
||||
const Gfx::Font& title_font() const;
|
||||
void set_title_font(const Gfx::Font& font);
|
||||
|
||||
MenuItem* item_with_identifier(unsigned);
|
||||
void redraw();
|
||||
|
||||
MenuItem* hovered_item() const;
|
||||
|
||||
void set_hovered_item(int index)
|
||||
{
|
||||
m_hovered_item_index = index;
|
||||
update_for_new_hovered_item();
|
||||
}
|
||||
|
||||
void clear_hovered_item();
|
||||
|
||||
Function<void(MenuItem&)> on_item_activation;
|
||||
|
||||
void close();
|
||||
|
||||
void popup(const Gfx::IntPoint&);
|
||||
void do_popup(const Gfx::IntPoint&, bool);
|
||||
|
||||
bool is_menu_ancestor_of(const Menu&) const;
|
||||
|
||||
void redraw_if_theme_changed();
|
||||
|
||||
bool is_scrollable() const { return m_scrollable; }
|
||||
int scroll_offset() const { return m_scroll_offset; }
|
||||
|
||||
void descend_into_submenu_at_hovered_item();
|
||||
void open_hovered_item();
|
||||
|
||||
private:
|
||||
virtual void event(Core::Event&) override;
|
||||
|
||||
RefPtr<Gfx::Font> m_title_font { &Gfx::FontDatabase::default_font() };
|
||||
|
||||
void handle_mouse_move_event(const MouseEvent&);
|
||||
int visible_item_count() const;
|
||||
|
||||
int item_index_at(const Gfx::IntPoint&);
|
||||
int padding_between_text_and_shortcut() const { return 50; }
|
||||
void did_activate(MenuItem&);
|
||||
void update_for_new_hovered_item(bool make_input = false);
|
||||
|
||||
ClientConnection* m_client { nullptr };
|
||||
int m_menu_id { 0 };
|
||||
String m_name;
|
||||
Gfx::IntRect m_rect_in_menubar;
|
||||
Gfx::IntRect m_text_rect_in_menubar;
|
||||
MenuBar* m_menubar { nullptr };
|
||||
NonnullOwnPtrVector<MenuItem> m_items;
|
||||
RefPtr<Window> m_menu_window;
|
||||
|
||||
WeakPtr<Window> m_window_menu_of;
|
||||
bool m_is_window_menu_open = { false };
|
||||
Gfx::IntPoint m_last_position_in_hover;
|
||||
int m_theme_index_at_last_paint { -1 };
|
||||
int m_hovered_item_index { -1 };
|
||||
|
||||
bool m_scrollable { false };
|
||||
int m_scroll_offset { 0 };
|
||||
int m_max_scroll_offset { 0 };
|
||||
};
|
||||
|
||||
}
|
55
Userland/Services/WindowServer/MenuBar.cpp
Normal file
55
Userland/Services/WindowServer/MenuBar.cpp
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "MenuBar.h"
|
||||
#include "Menu.h"
|
||||
#include "MenuItem.h"
|
||||
#include <LibGfx/Bitmap.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
MenuBar::MenuBar(ClientConnection& client, int menubar_id)
|
||||
: m_client(client)
|
||||
, m_menubar_id(menubar_id)
|
||||
{
|
||||
}
|
||||
|
||||
MenuBar::~MenuBar()
|
||||
{
|
||||
}
|
||||
|
||||
void MenuBar::add_menu(Menu& menu)
|
||||
{
|
||||
menu.set_menubar(this);
|
||||
|
||||
// NOTE: We assume that the first menu is the App menu, which has a bold font.
|
||||
if (m_menus.is_empty())
|
||||
menu.set_title_font(Gfx::FontDatabase::default_bold_font());
|
||||
|
||||
m_menus.append(&menu);
|
||||
}
|
||||
|
||||
}
|
61
Userland/Services/WindowServer/MenuBar.h
Normal file
61
Userland/Services/WindowServer/MenuBar.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Menu.h"
|
||||
#include <AK/Vector.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <AK/Weakable.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
class MenuBar : public Weakable<MenuBar> {
|
||||
public:
|
||||
MenuBar(ClientConnection& client, int menubar_id);
|
||||
~MenuBar();
|
||||
|
||||
ClientConnection& client() { return m_client; }
|
||||
const ClientConnection& client() const { return m_client; }
|
||||
int menubar_id() const { return m_menubar_id; }
|
||||
void add_menu(Menu&);
|
||||
|
||||
template<typename Callback>
|
||||
void for_each_menu(Callback callback)
|
||||
{
|
||||
for (auto& menu : m_menus) {
|
||||
if (callback(*menu) == IterationDecision::Break)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ClientConnection& m_client;
|
||||
int m_menubar_id { 0 };
|
||||
Vector<Menu*> m_menus;
|
||||
};
|
||||
|
||||
}
|
111
Userland/Services/WindowServer/MenuItem.cpp
Normal file
111
Userland/Services/WindowServer/MenuItem.cpp
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "MenuItem.h"
|
||||
#include "ClientConnection.h"
|
||||
#include "Menu.h"
|
||||
#include "WindowManager.h"
|
||||
#include <LibGfx/Bitmap.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
MenuItem::MenuItem(Menu& menu, unsigned identifier, const String& text, const String& shortcut_text, bool enabled, bool checkable, bool checked, const Gfx::Bitmap* icon)
|
||||
: m_menu(menu)
|
||||
, m_type(Text)
|
||||
, m_enabled(enabled)
|
||||
, m_checkable(checkable)
|
||||
, m_checked(checked)
|
||||
, m_identifier(identifier)
|
||||
, m_text(text)
|
||||
, m_shortcut_text(shortcut_text)
|
||||
, m_icon(icon)
|
||||
{
|
||||
}
|
||||
|
||||
MenuItem::MenuItem(Menu& menu, Type type)
|
||||
: m_menu(menu)
|
||||
, m_type(type)
|
||||
{
|
||||
}
|
||||
|
||||
MenuItem::~MenuItem()
|
||||
{
|
||||
}
|
||||
|
||||
void MenuItem::set_enabled(bool enabled)
|
||||
{
|
||||
if (m_enabled == enabled)
|
||||
return;
|
||||
m_enabled = enabled;
|
||||
m_menu.redraw();
|
||||
}
|
||||
|
||||
void MenuItem::set_checked(bool checked)
|
||||
{
|
||||
if (m_checked == checked)
|
||||
return;
|
||||
m_checked = checked;
|
||||
m_menu.redraw();
|
||||
}
|
||||
|
||||
void MenuItem::set_default(bool is_default)
|
||||
{
|
||||
if (m_default == is_default)
|
||||
return;
|
||||
m_default = is_default;
|
||||
m_menu.redraw();
|
||||
}
|
||||
|
||||
Menu* MenuItem::submenu()
|
||||
{
|
||||
ASSERT(is_submenu());
|
||||
ASSERT(m_menu.client());
|
||||
return m_menu.client()->find_menu_by_id(m_submenu_id);
|
||||
}
|
||||
|
||||
const Menu* MenuItem::submenu() const
|
||||
{
|
||||
ASSERT(is_submenu());
|
||||
ASSERT(m_menu.client());
|
||||
return m_menu.client()->find_menu_by_id(m_submenu_id);
|
||||
}
|
||||
|
||||
Gfx::IntRect MenuItem::rect() const
|
||||
{
|
||||
if (!m_menu.is_scrollable())
|
||||
return m_rect;
|
||||
return m_rect.translated(0, m_menu.item_height() - (m_menu.scroll_offset() * m_menu.item_height()));
|
||||
}
|
||||
|
||||
void MenuItem::set_icon(const Gfx::Bitmap* icon)
|
||||
{
|
||||
if (m_icon == icon)
|
||||
return;
|
||||
m_icon = icon;
|
||||
m_menu.redraw();
|
||||
}
|
||||
|
||||
}
|
104
Userland/Services/WindowServer/MenuItem.h
Normal file
104
Userland/Services/WindowServer/MenuItem.h
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
class Menu;
|
||||
|
||||
class MenuItem {
|
||||
public:
|
||||
enum Type {
|
||||
None,
|
||||
Text,
|
||||
Separator,
|
||||
};
|
||||
|
||||
MenuItem(Menu&, unsigned identifier, const String& text, const String& shortcut_text = {}, bool enabled = true, bool checkable = false, bool checked = false, const Gfx::Bitmap* icon = nullptr);
|
||||
MenuItem(Menu&, Type);
|
||||
~MenuItem();
|
||||
|
||||
Type type() const { return m_type; }
|
||||
|
||||
bool is_enabled() const { return m_enabled; }
|
||||
void set_enabled(bool);
|
||||
|
||||
bool is_checkable() const { return m_checkable; }
|
||||
void set_checkable(bool checkable) { m_checkable = checkable; }
|
||||
|
||||
bool is_checked() const { return m_checked; }
|
||||
void set_checked(bool);
|
||||
|
||||
bool is_default() const { return m_default; }
|
||||
void set_default(bool);
|
||||
|
||||
String text() const { return m_text; }
|
||||
void set_text(const String& text) { m_text = text; }
|
||||
|
||||
String shortcut_text() const { return m_shortcut_text; }
|
||||
void set_shortcut_text(const String& text) { m_shortcut_text = text; }
|
||||
|
||||
void set_rect(const Gfx::IntRect& rect) { m_rect = rect; }
|
||||
Gfx::IntRect rect() const;
|
||||
|
||||
unsigned identifier() const { return m_identifier; }
|
||||
|
||||
const Gfx::Bitmap* icon() const { return m_icon; }
|
||||
void set_icon(const Gfx::Bitmap*);
|
||||
|
||||
bool is_submenu() const { return m_submenu_id != -1; }
|
||||
int submenu_id() const { return m_submenu_id; }
|
||||
void set_submenu_id(int submenu_id) { m_submenu_id = submenu_id; }
|
||||
|
||||
Menu* submenu();
|
||||
const Menu* submenu() const;
|
||||
|
||||
bool is_exclusive() const { return m_exclusive; }
|
||||
void set_exclusive(bool exclusive) { m_exclusive = exclusive; }
|
||||
|
||||
private:
|
||||
Menu& m_menu;
|
||||
Type m_type { None };
|
||||
bool m_enabled { true };
|
||||
bool m_checkable { false };
|
||||
bool m_checked { false };
|
||||
bool m_default { false };
|
||||
unsigned m_identifier { 0 };
|
||||
String m_text;
|
||||
String m_shortcut_text;
|
||||
Gfx::IntRect m_rect;
|
||||
RefPtr<Gfx::Bitmap> m_icon;
|
||||
int m_submenu_id { -1 };
|
||||
bool m_exclusive { false };
|
||||
};
|
||||
|
||||
}
|
497
Userland/Services/WindowServer/MenuManager.cpp
Normal file
497
Userland/Services/WindowServer/MenuManager.cpp
Normal file
|
@ -0,0 +1,497 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020, Shannon Booth <shannon.ml.booth@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/Badge.h>
|
||||
#include <AK/QuickSort.h>
|
||||
#include <LibCore/DirIterator.h>
|
||||
#include <LibGfx/Font.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <WindowServer/AppletManager.h>
|
||||
#include <WindowServer/MenuManager.h>
|
||||
#include <WindowServer/Screen.h>
|
||||
#include <WindowServer/WindowManager.h>
|
||||
#include <unistd.h>
|
||||
|
||||
//#define DEBUG_MENUS
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
static MenuManager* s_the;
|
||||
static constexpr int s_search_timeout = 3000;
|
||||
|
||||
MenuManager& MenuManager::the()
|
||||
{
|
||||
ASSERT(s_the);
|
||||
return *s_the;
|
||||
}
|
||||
|
||||
MenuManager::MenuManager()
|
||||
{
|
||||
s_the = this;
|
||||
m_needs_window_resize = true;
|
||||
|
||||
// NOTE: This ensures that the system menu has the correct dimensions.
|
||||
set_current_menubar(nullptr);
|
||||
|
||||
m_window = Window::construct(*this, WindowType::Menubar);
|
||||
m_window->set_rect(menubar_rect());
|
||||
|
||||
m_search_timer = Core::Timer::create_single_shot(0, [this] {
|
||||
m_current_search.clear();
|
||||
});
|
||||
}
|
||||
|
||||
MenuManager::~MenuManager()
|
||||
{
|
||||
}
|
||||
|
||||
bool MenuManager::is_open(const Menu& menu) const
|
||||
{
|
||||
for (size_t i = 0; i < m_open_menu_stack.size(); ++i) {
|
||||
if (&menu == m_open_menu_stack[i].ptr())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MenuManager::draw()
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
auto palette = wm.palette();
|
||||
auto menubar_rect = this->menubar_rect();
|
||||
|
||||
if (m_needs_window_resize) {
|
||||
m_window->set_rect(menubar_rect);
|
||||
AppletManager::the().calculate_applet_rects(window());
|
||||
m_needs_window_resize = false;
|
||||
}
|
||||
|
||||
Gfx::Painter painter(*window().backing_store());
|
||||
|
||||
painter.fill_rect(menubar_rect, palette.window());
|
||||
painter.draw_line({ 0, menubar_rect.bottom() - 1 }, { menubar_rect.right(), menubar_rect.bottom() - 1 }, palette.threed_shadow1());
|
||||
painter.draw_line({ 0, menubar_rect.bottom() }, { menubar_rect.right(), menubar_rect.bottom() }, palette.threed_shadow2());
|
||||
|
||||
for_each_active_menubar_menu([&](Menu& menu) {
|
||||
Color text_color = palette.window_text();
|
||||
if (is_open(menu)) {
|
||||
painter.fill_rect(menu.rect_in_menubar(), palette.menu_selection());
|
||||
painter.draw_rect(menu.rect_in_menubar(), palette.menu_selection().darkened());
|
||||
text_color = palette.menu_selection_text();
|
||||
}
|
||||
painter.draw_text(
|
||||
menu.text_rect_in_menubar(),
|
||||
menu.name(),
|
||||
menu.title_font(),
|
||||
Gfx::TextAlignment::CenterLeft,
|
||||
text_color);
|
||||
//painter.draw_rect(menu.text_rect_in_menubar(), Color::Magenta);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
AppletManager::the().draw();
|
||||
}
|
||||
|
||||
void MenuManager::refresh()
|
||||
{
|
||||
if (!m_window)
|
||||
return;
|
||||
draw();
|
||||
window().invalidate();
|
||||
}
|
||||
|
||||
void MenuManager::event(Core::Event& event)
|
||||
{
|
||||
if (static_cast<Event&>(event).is_mouse_event()) {
|
||||
handle_mouse_event(static_cast<MouseEvent&>(event));
|
||||
return;
|
||||
}
|
||||
|
||||
if (static_cast<Event&>(event).is_key_event()) {
|
||||
auto& key_event = static_cast<const KeyEvent&>(event);
|
||||
|
||||
if (key_event.type() == Event::KeyUp && key_event.key() == Key_Escape) {
|
||||
close_everyone();
|
||||
return;
|
||||
}
|
||||
|
||||
if (key_event.key() == Key_Backspace) {
|
||||
m_current_search.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_current_menu && event.type() == Event::KeyDown
|
||||
&& ((key_event.key() >= Key_A && key_event.key() <= Key_Z)
|
||||
|| (key_event.key() >= Key_0 && key_event.key() <= Key_9))) {
|
||||
m_current_search.append_code_point(key_event.code_point());
|
||||
m_search_timer->restart(s_search_timeout);
|
||||
for (int i = 0; i < m_current_menu->item_count(); ++i) {
|
||||
auto text = m_current_menu->item(i).text();
|
||||
if (text.to_lowercase().starts_with(m_current_search.to_string().to_lowercase())) {
|
||||
m_current_menu->set_hovered_item(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type() == Event::KeyDown) {
|
||||
|
||||
if (key_event.key() == Key_Left) {
|
||||
auto it = m_open_menu_stack.find_if([&](const auto& other) { return m_current_menu == other.ptr(); });
|
||||
ASSERT(!it.is_end());
|
||||
|
||||
// Going "back" a menu should be the previous menu in the stack
|
||||
if (it.index() > 0)
|
||||
set_current_menu(m_open_menu_stack.at(it.index() - 1));
|
||||
close_everyone_not_in_lineage(*m_current_menu);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key_event.key() == Key_Right) {
|
||||
auto hovered_item = m_current_menu->hovered_item();
|
||||
if (hovered_item && hovered_item->is_submenu())
|
||||
m_current_menu->descend_into_submenu_at_hovered_item();
|
||||
return;
|
||||
}
|
||||
|
||||
if (key_event.key() == Key_Return) {
|
||||
auto hovered_item = m_current_menu->hovered_item();
|
||||
if (!hovered_item || !hovered_item->is_enabled())
|
||||
return;
|
||||
if (hovered_item->is_submenu())
|
||||
m_current_menu->descend_into_submenu_at_hovered_item();
|
||||
else
|
||||
m_current_menu->open_hovered_item();
|
||||
return;
|
||||
}
|
||||
m_current_menu->dispatch_event(event);
|
||||
}
|
||||
}
|
||||
|
||||
return Core::Object::event(event);
|
||||
}
|
||||
|
||||
void MenuManager::handle_mouse_event(MouseEvent& mouse_event)
|
||||
{
|
||||
auto* active_window = WindowManager::the().active_window();
|
||||
bool handled_menubar_event = false;
|
||||
for_each_active_menubar_menu([&](Menu& menu) {
|
||||
if (menu.rect_in_menubar().contains(mouse_event.position())) {
|
||||
handled_menubar_event = &menu == m_system_menu || !active_window || !active_window->is_modal();
|
||||
if (handled_menubar_event)
|
||||
handle_menu_mouse_event(menu, mouse_event);
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
if (handled_menubar_event)
|
||||
return;
|
||||
|
||||
if (has_open_menu()) {
|
||||
auto* topmost_menu = m_open_menu_stack.last().ptr();
|
||||
ASSERT(topmost_menu);
|
||||
auto* window = topmost_menu->menu_window();
|
||||
if (!window) {
|
||||
dbgln("MenuManager::handle_mouse_event: No menu window");
|
||||
return;
|
||||
}
|
||||
ASSERT(window->is_visible());
|
||||
|
||||
bool event_is_inside_current_menu = window->rect().contains(mouse_event.position());
|
||||
if (event_is_inside_current_menu) {
|
||||
WindowManager::the().set_hovered_window(window);
|
||||
auto translated_event = mouse_event.translated(-window->position());
|
||||
WindowManager::the().deliver_mouse_event(*window, translated_event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (topmost_menu->hovered_item())
|
||||
topmost_menu->clear_hovered_item();
|
||||
if (mouse_event.type() == Event::MouseDown || mouse_event.type() == Event::MouseUp) {
|
||||
auto* window_menu_of = topmost_menu->window_menu_of();
|
||||
if (window_menu_of) {
|
||||
bool event_is_inside_taskbar_button = window_menu_of->taskbar_rect().contains(mouse_event.position());
|
||||
if (event_is_inside_taskbar_button && !topmost_menu->is_window_menu_open()) {
|
||||
topmost_menu->set_window_menu_open(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mouse_event.type() == Event::MouseDown) {
|
||||
close_bar();
|
||||
topmost_menu->set_window_menu_open(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (mouse_event.type() == Event::MouseMove) {
|
||||
for (auto& menu : m_open_menu_stack) {
|
||||
if (!menu)
|
||||
continue;
|
||||
if (!menu->menu_window()->rect().contains(mouse_event.position()))
|
||||
continue;
|
||||
WindowManager::the().set_hovered_window(menu->menu_window());
|
||||
auto translated_event = mouse_event.translated(-menu->menu_window()->position());
|
||||
WindowManager::the().deliver_mouse_event(*menu->menu_window(), translated_event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
AppletManager::the().dispatch_event(static_cast<Event&>(mouse_event));
|
||||
}
|
||||
|
||||
void MenuManager::handle_menu_mouse_event(Menu& menu, const MouseEvent& event)
|
||||
{
|
||||
bool is_hover_with_any_menu_open = event.type() == MouseEvent::MouseMove
|
||||
&& has_open_menu()
|
||||
&& (m_open_menu_stack.first()->menubar() || m_open_menu_stack.first() == m_system_menu.ptr());
|
||||
bool is_mousedown_with_left_button = event.type() == MouseEvent::MouseDown && event.button() == MouseButton::Left;
|
||||
bool should_open_menu = &menu != m_current_menu && (is_hover_with_any_menu_open || is_mousedown_with_left_button);
|
||||
|
||||
if (is_mousedown_with_left_button)
|
||||
m_bar_open = !m_bar_open;
|
||||
|
||||
if (should_open_menu && m_bar_open) {
|
||||
close_everyone();
|
||||
open_menu(menu);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_bar_open)
|
||||
close_everyone();
|
||||
}
|
||||
|
||||
void MenuManager::set_needs_window_resize()
|
||||
{
|
||||
m_needs_window_resize = true;
|
||||
}
|
||||
|
||||
void MenuManager::close_all_menus_from_client(Badge<ClientConnection>, ClientConnection& client)
|
||||
{
|
||||
if (!has_open_menu())
|
||||
return;
|
||||
if (m_open_menu_stack.first()->client() != &client)
|
||||
return;
|
||||
close_everyone();
|
||||
}
|
||||
|
||||
void MenuManager::close_everyone()
|
||||
{
|
||||
for (auto& menu : m_open_menu_stack) {
|
||||
ASSERT(menu);
|
||||
if (menu->menu_window())
|
||||
menu->menu_window()->set_visible(false);
|
||||
menu->clear_hovered_item();
|
||||
}
|
||||
m_open_menu_stack.clear();
|
||||
m_current_search.clear();
|
||||
clear_current_menu();
|
||||
refresh();
|
||||
}
|
||||
|
||||
void MenuManager::close_everyone_not_in_lineage(Menu& menu)
|
||||
{
|
||||
Vector<Menu*> menus_to_close;
|
||||
for (auto& open_menu : m_open_menu_stack) {
|
||||
if (!open_menu)
|
||||
continue;
|
||||
if (&menu == open_menu.ptr() || open_menu->is_menu_ancestor_of(menu))
|
||||
continue;
|
||||
menus_to_close.append(open_menu);
|
||||
}
|
||||
close_menus(menus_to_close);
|
||||
}
|
||||
|
||||
void MenuManager::close_menus(const Vector<Menu*>& menus)
|
||||
{
|
||||
for (auto& menu : menus) {
|
||||
if (menu == m_current_menu)
|
||||
clear_current_menu();
|
||||
if (menu->menu_window())
|
||||
menu->menu_window()->set_visible(false);
|
||||
menu->clear_hovered_item();
|
||||
m_open_menu_stack.remove_first_matching([&](auto& entry) {
|
||||
return entry == menu;
|
||||
});
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
|
||||
static void collect_menu_subtree(Menu& menu, Vector<Menu*>& menus)
|
||||
{
|
||||
menus.append(&menu);
|
||||
for (int i = 0; i < menu.item_count(); ++i) {
|
||||
auto& item = menu.item(i);
|
||||
if (!item.is_submenu())
|
||||
continue;
|
||||
collect_menu_subtree(*item.submenu(), menus);
|
||||
}
|
||||
}
|
||||
|
||||
void MenuManager::close_menu_and_descendants(Menu& menu)
|
||||
{
|
||||
Vector<Menu*> menus_to_close;
|
||||
collect_menu_subtree(menu, menus_to_close);
|
||||
close_menus(menus_to_close);
|
||||
}
|
||||
|
||||
void MenuManager::toggle_menu(Menu& menu)
|
||||
{
|
||||
if (is_open(menu)) {
|
||||
close_menu_and_descendants(menu);
|
||||
return;
|
||||
}
|
||||
open_menu(menu);
|
||||
}
|
||||
|
||||
void MenuManager::open_menu(Menu& menu, bool as_current_menu)
|
||||
{
|
||||
if (is_open(menu)) {
|
||||
if (as_current_menu || current_menu() != &menu) {
|
||||
// This menu is already open. If requested, or if the current
|
||||
// window doesn't match this one, then set it to this
|
||||
set_current_menu(&menu);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!menu.is_empty()) {
|
||||
menu.redraw_if_theme_changed();
|
||||
if (!menu.menu_window()) {
|
||||
auto& menu_window = menu.ensure_menu_window();
|
||||
menu_window.move_to({ menu.rect_in_menubar().x(), menu.rect_in_menubar().bottom() + 2 });
|
||||
}
|
||||
menu.menu_window()->set_visible(true);
|
||||
}
|
||||
|
||||
if (m_open_menu_stack.find_if([&menu](auto& other) { return &menu == other.ptr(); }).is_end())
|
||||
m_open_menu_stack.append(menu);
|
||||
|
||||
if (as_current_menu || !current_menu()) {
|
||||
// Only make this menu the current menu if requested, or if no
|
||||
// other menu is current
|
||||
set_current_menu(&menu);
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
void MenuManager::clear_current_menu()
|
||||
{
|
||||
Menu* previous_current_menu = m_current_menu;
|
||||
m_current_menu = nullptr;
|
||||
if (previous_current_menu) {
|
||||
// When closing the last menu, restore the previous active input window
|
||||
auto& wm = WindowManager::the();
|
||||
wm.restore_active_input_window(m_previous_input_window);
|
||||
}
|
||||
}
|
||||
|
||||
void MenuManager::set_current_menu(Menu* menu)
|
||||
{
|
||||
if (!menu) {
|
||||
clear_current_menu();
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT(is_open(*menu));
|
||||
if (menu == m_current_menu) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_current_search.clear();
|
||||
|
||||
Menu* previous_current_menu = m_current_menu;
|
||||
m_current_menu = menu;
|
||||
|
||||
auto& wm = WindowManager::the();
|
||||
if (!previous_current_menu) {
|
||||
// When opening the first menu, store the current active input window
|
||||
if (auto* active_input = wm.active_input_window())
|
||||
m_previous_input_window = *active_input;
|
||||
else
|
||||
m_previous_input_window = nullptr;
|
||||
}
|
||||
|
||||
wm.set_active_input_window(m_current_menu->menu_window());
|
||||
}
|
||||
|
||||
void MenuManager::close_bar()
|
||||
{
|
||||
close_everyone();
|
||||
m_bar_open = false;
|
||||
}
|
||||
|
||||
Gfx::IntRect MenuManager::menubar_rect() const
|
||||
{
|
||||
return { 0, 0, Screen::the().rect().width(), 19 };
|
||||
}
|
||||
|
||||
void MenuManager::set_current_menubar(MenuBar* menubar)
|
||||
{
|
||||
if (menubar)
|
||||
m_current_menubar = *menubar;
|
||||
else
|
||||
m_current_menubar = nullptr;
|
||||
#ifdef DEBUG_MENUS
|
||||
dbg() << "[WM] Current menubar is now " << menubar;
|
||||
#endif
|
||||
Gfx::IntPoint next_menu_location { MenuManager::menubar_menu_margin() / 2, 0 };
|
||||
for_each_active_menubar_menu([&](Menu& menu) {
|
||||
int text_width = menu.title_font().width(menu.name());
|
||||
menu.set_rect_in_menubar({ next_menu_location.x() - MenuManager::menubar_menu_margin() / 2, 0, text_width + MenuManager::menubar_menu_margin(), menubar_rect().height() - 1 });
|
||||
|
||||
Gfx::IntRect text_rect { next_menu_location.translated(0, 1), { text_width, menubar_rect().height() - 3 } };
|
||||
|
||||
menu.set_text_rect_in_menubar(text_rect);
|
||||
next_menu_location.move_by(menu.rect_in_menubar().width(), 0);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
refresh();
|
||||
}
|
||||
|
||||
void MenuManager::close_menubar(MenuBar& menubar)
|
||||
{
|
||||
if (current_menubar() == &menubar)
|
||||
set_current_menubar(nullptr);
|
||||
}
|
||||
|
||||
void MenuManager::set_system_menu(Menu& menu)
|
||||
{
|
||||
m_system_menu = menu;
|
||||
set_current_menubar(m_current_menubar);
|
||||
}
|
||||
|
||||
void MenuManager::did_change_theme()
|
||||
{
|
||||
++m_theme_index;
|
||||
refresh();
|
||||
}
|
||||
|
||||
}
|
123
Userland/Services/WindowServer/MenuManager.h
Normal file
123
Userland/Services/WindowServer/MenuManager.h
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Menu.h"
|
||||
#include "MenuBar.h"
|
||||
#include "Window.h"
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibCore/Object.h>
|
||||
#include <LibCore/Timer.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
class MenuManager final : public Core::Object {
|
||||
C_OBJECT(MenuManager)
|
||||
public:
|
||||
static MenuManager& the();
|
||||
|
||||
MenuManager();
|
||||
virtual ~MenuManager() override;
|
||||
|
||||
void refresh();
|
||||
|
||||
bool is_open(const Menu&) const;
|
||||
bool has_open_menu() const { return !m_open_menu_stack.is_empty(); }
|
||||
|
||||
Gfx::IntRect menubar_rect() const;
|
||||
static int menubar_menu_margin() { return 16; }
|
||||
|
||||
void set_needs_window_resize();
|
||||
|
||||
Menu* current_menu() { return m_current_menu.ptr(); }
|
||||
void set_current_menu(Menu*);
|
||||
void clear_current_menu();
|
||||
void open_menu(Menu&, bool as_current_menu = true);
|
||||
void toggle_menu(Menu&);
|
||||
|
||||
MenuBar* current_menubar() { return m_current_menubar.ptr(); }
|
||||
void set_current_menubar(MenuBar*);
|
||||
void close_menubar(MenuBar&);
|
||||
|
||||
void close_bar();
|
||||
void close_everyone();
|
||||
void close_everyone_not_in_lineage(Menu&);
|
||||
void close_menu_and_descendants(Menu&);
|
||||
|
||||
void close_all_menus_from_client(Badge<ClientConnection>, ClientConnection&);
|
||||
|
||||
Menu* system_menu() { return m_system_menu; }
|
||||
void set_system_menu(Menu&);
|
||||
|
||||
int theme_index() const { return m_theme_index; }
|
||||
|
||||
Window& window() { return *m_window; }
|
||||
|
||||
template<typename Callback>
|
||||
void for_each_active_menubar_menu(Callback callback)
|
||||
{
|
||||
if (system_menu()) {
|
||||
if (callback(*system_menu()) == IterationDecision::Break)
|
||||
return;
|
||||
}
|
||||
if (m_current_menubar)
|
||||
m_current_menubar->for_each_menu(callback);
|
||||
}
|
||||
|
||||
void did_change_theme();
|
||||
|
||||
private:
|
||||
void close_menus(const Vector<Menu*>&);
|
||||
|
||||
const Window& window() const { return *m_window; }
|
||||
|
||||
virtual void event(Core::Event&) override;
|
||||
void handle_mouse_event(MouseEvent&);
|
||||
void handle_menu_mouse_event(Menu&, const MouseEvent&);
|
||||
|
||||
void draw();
|
||||
|
||||
RefPtr<Window> m_window;
|
||||
|
||||
WeakPtr<Menu> m_current_menu;
|
||||
WeakPtr<Window> m_previous_input_window;
|
||||
Vector<WeakPtr<Menu>> m_open_menu_stack;
|
||||
|
||||
RefPtr<Core::Timer> m_search_timer;
|
||||
StringBuilder m_current_search;
|
||||
WeakPtr<Menu> m_system_menu;
|
||||
|
||||
bool m_needs_window_resize { false };
|
||||
bool m_bar_open { false };
|
||||
|
||||
int m_theme_index { 0 };
|
||||
|
||||
WeakPtr<MenuBar> m_current_menubar;
|
||||
};
|
||||
|
||||
}
|
186
Userland/Services/WindowServer/Screen.cpp
Normal file
186
Userland/Services/WindowServer/Screen.cpp
Normal file
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "Screen.h"
|
||||
#include "Compositor.h"
|
||||
#include "Event.h"
|
||||
#include "EventLoop.h"
|
||||
#include "WindowManager.h"
|
||||
#include <Kernel/API/FB.h>
|
||||
#include <Kernel/API/MousePacket.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
static Screen* s_the;
|
||||
|
||||
Screen& Screen::the()
|
||||
{
|
||||
ASSERT(s_the);
|
||||
return *s_the;
|
||||
}
|
||||
|
||||
Screen::Screen(unsigned desired_width, unsigned desired_height)
|
||||
{
|
||||
ASSERT(!s_the);
|
||||
s_the = this;
|
||||
m_framebuffer_fd = open("/dev/fb0", O_RDWR | O_CLOEXEC);
|
||||
if (m_framebuffer_fd < 0) {
|
||||
perror("failed to open /dev/fb0");
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
if (fb_set_buffer(m_framebuffer_fd, 0) == 0) {
|
||||
m_can_set_buffer = true;
|
||||
}
|
||||
|
||||
set_resolution(desired_width, desired_height);
|
||||
m_cursor_location = rect().center();
|
||||
}
|
||||
|
||||
Screen::~Screen()
|
||||
{
|
||||
close(m_framebuffer_fd);
|
||||
}
|
||||
|
||||
bool Screen::set_resolution(int width, int height)
|
||||
{
|
||||
FBResolution resolution { 0, (unsigned)width, (unsigned)height };
|
||||
int rc = fb_set_resolution(m_framebuffer_fd, &resolution);
|
||||
#ifdef WSSCREEN_DEBUG
|
||||
dbg() << "fb_set_resolution() - return code " << rc;
|
||||
#endif
|
||||
if (rc == 0) {
|
||||
on_change_resolution(resolution.pitch, resolution.width, resolution.height);
|
||||
return true;
|
||||
}
|
||||
if (rc == -1) {
|
||||
dbg() << "Invalid resolution " << width << "x" << height;
|
||||
on_change_resolution(resolution.pitch, resolution.width, resolution.height);
|
||||
return false;
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
void Screen::on_change_resolution(int pitch, int width, int height)
|
||||
{
|
||||
if (m_framebuffer) {
|
||||
size_t previous_size_in_bytes = m_size_in_bytes;
|
||||
int rc = munmap(m_framebuffer, previous_size_in_bytes);
|
||||
ASSERT(rc == 0);
|
||||
}
|
||||
|
||||
int rc = fb_get_size_in_bytes(m_framebuffer_fd, &m_size_in_bytes);
|
||||
ASSERT(rc == 0);
|
||||
|
||||
m_framebuffer = (Gfx::RGBA32*)mmap(nullptr, m_size_in_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, m_framebuffer_fd, 0);
|
||||
ASSERT(m_framebuffer && m_framebuffer != (void*)-1);
|
||||
|
||||
m_pitch = pitch;
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
|
||||
m_cursor_location.constrain(rect());
|
||||
}
|
||||
|
||||
void Screen::set_buffer(int index)
|
||||
{
|
||||
ASSERT(m_can_set_buffer);
|
||||
int rc = fb_set_buffer(m_framebuffer_fd, index);
|
||||
ASSERT(rc == 0);
|
||||
}
|
||||
|
||||
void Screen::set_acceleration_factor(double factor)
|
||||
{
|
||||
ASSERT(factor >= mouse_accel_min && factor <= mouse_accel_max);
|
||||
m_acceleration_factor = factor;
|
||||
}
|
||||
|
||||
void Screen::set_scroll_step_size(unsigned step_size)
|
||||
{
|
||||
ASSERT(step_size >= scroll_step_size_min);
|
||||
m_scroll_step_size = step_size;
|
||||
}
|
||||
|
||||
void Screen::on_receive_mouse_data(const MousePacket& packet)
|
||||
{
|
||||
auto prev_location = m_cursor_location;
|
||||
if (packet.is_relative) {
|
||||
m_cursor_location.move_by(packet.x * m_acceleration_factor, packet.y * m_acceleration_factor);
|
||||
#ifdef WSSCREEN_DEBUG
|
||||
dbgln("Screen: New Relative mouse point @ {}", m_cursor_location);
|
||||
#endif
|
||||
} else {
|
||||
m_cursor_location = { packet.x * m_width / 0xffff, packet.y * m_height / 0xffff };
|
||||
#ifdef WSSCREEN_DEBUG
|
||||
dbgln("Screen: New Absolute mouse point @ {}", m_cursor_location);
|
||||
#endif
|
||||
}
|
||||
|
||||
m_cursor_location.constrain(rect());
|
||||
|
||||
unsigned buttons = packet.buttons;
|
||||
unsigned prev_buttons = m_mouse_button_state;
|
||||
m_mouse_button_state = buttons;
|
||||
unsigned changed_buttons = prev_buttons ^ buttons;
|
||||
auto post_mousedown_or_mouseup_if_needed = [&](MouseButton button) {
|
||||
if (!(changed_buttons & (unsigned)button))
|
||||
return;
|
||||
auto message = make<MouseEvent>(buttons & (unsigned)button ? Event::MouseDown : Event::MouseUp, m_cursor_location, buttons, button, m_modifiers);
|
||||
Core::EventLoop::current().post_event(WindowManager::the(), move(message));
|
||||
};
|
||||
post_mousedown_or_mouseup_if_needed(MouseButton::Left);
|
||||
post_mousedown_or_mouseup_if_needed(MouseButton::Right);
|
||||
post_mousedown_or_mouseup_if_needed(MouseButton::Middle);
|
||||
post_mousedown_or_mouseup_if_needed(MouseButton::Back);
|
||||
post_mousedown_or_mouseup_if_needed(MouseButton::Forward);
|
||||
if (m_cursor_location != prev_location) {
|
||||
auto message = make<MouseEvent>(Event::MouseMove, m_cursor_location, buttons, MouseButton::None, m_modifiers);
|
||||
if (WindowManager::the().dnd_client())
|
||||
message->set_mime_data(WindowManager::the().dnd_mime_data());
|
||||
Core::EventLoop::current().post_event(WindowManager::the(), move(message));
|
||||
}
|
||||
|
||||
if (packet.z) {
|
||||
auto message = make<MouseEvent>(Event::MouseWheel, m_cursor_location, buttons, MouseButton::None, m_modifiers, packet.z * m_scroll_step_size);
|
||||
Core::EventLoop::current().post_event(WindowManager::the(), move(message));
|
||||
}
|
||||
|
||||
if (m_cursor_location != prev_location)
|
||||
Compositor::the().invalidate_cursor();
|
||||
}
|
||||
|
||||
void Screen::on_receive_keyboard_data(::KeyEvent kernel_event)
|
||||
{
|
||||
m_modifiers = kernel_event.modifiers();
|
||||
auto message = make<KeyEvent>(kernel_event.is_press() ? Event::KeyDown : Event::KeyUp, kernel_event.key, kernel_event.code_point, kernel_event.modifiers(), kernel_event.scancode);
|
||||
Core::EventLoop::current().post_event(WindowManager::the(), move(message));
|
||||
}
|
||||
|
||||
}
|
98
Userland/Services/WindowServer/Screen.h
Normal file
98
Userland/Services/WindowServer/Screen.h
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Kernel/API/KeyCode.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <LibGfx/Size.h>
|
||||
|
||||
struct MousePacket;
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
const double mouse_accel_max = 3.5;
|
||||
const double mouse_accel_min = 0.5;
|
||||
const unsigned scroll_step_size_min = 1;
|
||||
|
||||
class Screen {
|
||||
public:
|
||||
Screen(unsigned width, unsigned height);
|
||||
~Screen();
|
||||
|
||||
bool set_resolution(int width, int height);
|
||||
bool can_set_buffer() { return m_can_set_buffer; }
|
||||
void set_buffer(int index);
|
||||
|
||||
int width() const { return m_width; }
|
||||
int height() const { return m_height; }
|
||||
size_t pitch() const { return m_pitch; }
|
||||
Gfx::RGBA32* scanline(int y);
|
||||
|
||||
static Screen& the();
|
||||
|
||||
Gfx::IntSize size() const { return { width(), height() }; }
|
||||
Gfx::IntRect rect() const { return { 0, 0, width(), height() }; }
|
||||
|
||||
Gfx::IntPoint cursor_location() const { return m_cursor_location; }
|
||||
unsigned mouse_button_state() const { return m_mouse_button_state; }
|
||||
|
||||
double acceleration_factor() const { return m_acceleration_factor; }
|
||||
void set_acceleration_factor(double);
|
||||
|
||||
unsigned scroll_step_size() const { return m_scroll_step_size; }
|
||||
void set_scroll_step_size(unsigned);
|
||||
|
||||
void on_receive_mouse_data(const MousePacket&);
|
||||
void on_receive_keyboard_data(::KeyEvent);
|
||||
|
||||
private:
|
||||
void on_change_resolution(int pitch, int width, int height);
|
||||
|
||||
size_t m_size_in_bytes;
|
||||
|
||||
Gfx::RGBA32* m_framebuffer { nullptr };
|
||||
bool m_can_set_buffer { false };
|
||||
|
||||
int m_pitch { 0 };
|
||||
int m_width { 0 };
|
||||
int m_height { 0 };
|
||||
int m_framebuffer_fd { -1 };
|
||||
|
||||
Gfx::IntPoint m_cursor_location;
|
||||
unsigned m_mouse_button_state { 0 };
|
||||
unsigned m_modifiers { 0 };
|
||||
double m_acceleration_factor { 1.0 };
|
||||
unsigned m_scroll_step_size { 1 };
|
||||
};
|
||||
|
||||
inline Gfx::RGBA32* Screen::scanline(int y)
|
||||
{
|
||||
return reinterpret_cast<Gfx::RGBA32*>(((u8*)m_framebuffer) + (y * m_pitch));
|
||||
}
|
||||
|
||||
}
|
776
Userland/Services/WindowServer/Window.cpp
Normal file
776
Userland/Services/WindowServer/Window.cpp
Normal file
|
@ -0,0 +1,776 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "Window.h"
|
||||
#include "AppletManager.h"
|
||||
#include "ClientConnection.h"
|
||||
#include "Compositor.h"
|
||||
#include "Event.h"
|
||||
#include "EventLoop.h"
|
||||
#include "Screen.h"
|
||||
#include "WindowManager.h"
|
||||
#include <AK/Badge.h>
|
||||
#include <WindowServer/WindowClientEndpoint.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
static String default_window_icon_path()
|
||||
{
|
||||
return "/res/icons/16x16/window.png";
|
||||
}
|
||||
|
||||
static Gfx::Bitmap& default_window_icon()
|
||||
{
|
||||
static Gfx::Bitmap* s_icon;
|
||||
if (!s_icon)
|
||||
s_icon = Gfx::Bitmap::load_from_file(default_window_icon_path()).leak_ref();
|
||||
return *s_icon;
|
||||
}
|
||||
|
||||
static Gfx::Bitmap& minimize_icon()
|
||||
{
|
||||
static Gfx::Bitmap* s_icon;
|
||||
if (!s_icon)
|
||||
s_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/downward-triangle.png").leak_ref();
|
||||
return *s_icon;
|
||||
}
|
||||
|
||||
static Gfx::Bitmap& maximize_icon()
|
||||
{
|
||||
static Gfx::Bitmap* s_icon;
|
||||
if (!s_icon)
|
||||
s_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/upward-triangle.png").leak_ref();
|
||||
return *s_icon;
|
||||
}
|
||||
|
||||
static Gfx::Bitmap& restore_icon()
|
||||
{
|
||||
static Gfx::Bitmap* s_icon;
|
||||
if (!s_icon)
|
||||
s_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-restore.png").leak_ref();
|
||||
return *s_icon;
|
||||
}
|
||||
|
||||
static Gfx::Bitmap& close_icon()
|
||||
{
|
||||
static Gfx::Bitmap* s_icon;
|
||||
if (!s_icon)
|
||||
s_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-close.png").leak_ref();
|
||||
return *s_icon;
|
||||
}
|
||||
|
||||
Window::Window(Core::Object& parent, WindowType type)
|
||||
: Core::Object(&parent)
|
||||
, m_type(type)
|
||||
, m_icon(default_window_icon())
|
||||
, m_frame(*this)
|
||||
{
|
||||
WindowManager::the().add_window(*this);
|
||||
}
|
||||
|
||||
Window::Window(ClientConnection& client, WindowType window_type, int window_id, bool modal, bool minimizable, bool frameless, bool resizable, bool fullscreen, bool accessory, Window* parent_window)
|
||||
: Core::Object(&client)
|
||||
, m_client(&client)
|
||||
, m_type(window_type)
|
||||
, m_modal(modal)
|
||||
, m_minimizable(minimizable)
|
||||
, m_frameless(frameless)
|
||||
, m_resizable(resizable)
|
||||
, m_fullscreen(fullscreen)
|
||||
, m_accessory(accessory)
|
||||
, m_window_id(window_id)
|
||||
, m_client_id(client.client_id())
|
||||
, m_icon(default_window_icon())
|
||||
, m_frame(*this)
|
||||
{
|
||||
// FIXME: This should not be hard-coded here.
|
||||
if (m_type == WindowType::Taskbar) {
|
||||
m_wm_event_mask = WMEventMask::WindowStateChanges | WMEventMask::WindowRemovals | WMEventMask::WindowIconChanges;
|
||||
m_listens_to_wm_events = true;
|
||||
}
|
||||
|
||||
if (parent_window)
|
||||
set_parent_window(*parent_window);
|
||||
WindowManager::the().add_window(*this);
|
||||
}
|
||||
|
||||
Window::~Window()
|
||||
{
|
||||
// Detach from client at the start of teardown since we don't want
|
||||
// to confuse things by trying to send messages to it.
|
||||
m_client = nullptr;
|
||||
|
||||
WindowManager::the().remove_window(*this);
|
||||
}
|
||||
|
||||
void Window::destroy()
|
||||
{
|
||||
m_destroyed = true;
|
||||
set_visible(false);
|
||||
}
|
||||
|
||||
void Window::set_title(const String& title)
|
||||
{
|
||||
if (m_title == title)
|
||||
return;
|
||||
m_title = title;
|
||||
frame().invalidate_title_bar();
|
||||
WindowManager::the().notify_title_changed(*this);
|
||||
}
|
||||
|
||||
void Window::set_rect(const Gfx::IntRect& rect)
|
||||
{
|
||||
ASSERT(!rect.is_empty());
|
||||
if (m_rect == rect)
|
||||
return;
|
||||
auto old_rect = m_rect;
|
||||
m_rect = rect;
|
||||
if (!m_client && (!m_backing_store || old_rect.size() != rect.size())) {
|
||||
m_backing_store = Gfx::Bitmap::create(Gfx::BitmapFormat::RGB32, m_rect.size());
|
||||
}
|
||||
|
||||
invalidate(true);
|
||||
m_frame.notify_window_rect_changed(old_rect, rect); // recomputes occlusions
|
||||
}
|
||||
|
||||
void Window::set_rect_without_repaint(const Gfx::IntRect& rect)
|
||||
{
|
||||
ASSERT(!rect.is_empty());
|
||||
if (m_rect == rect)
|
||||
return;
|
||||
auto old_rect = m_rect;
|
||||
m_rect = rect;
|
||||
|
||||
if (old_rect.size() == m_rect.size()) {
|
||||
auto delta = m_rect.location() - old_rect.location();
|
||||
for (auto& child_window : m_child_windows) {
|
||||
if (child_window)
|
||||
child_window->move_by(delta);
|
||||
}
|
||||
}
|
||||
|
||||
invalidate(true);
|
||||
m_frame.notify_window_rect_changed(old_rect, rect); // recomputes occlusions
|
||||
}
|
||||
|
||||
void Window::handle_mouse_event(const MouseEvent& event)
|
||||
{
|
||||
set_automatic_cursor_tracking_enabled(event.buttons() != 0);
|
||||
|
||||
switch (event.type()) {
|
||||
case Event::MouseMove:
|
||||
m_client->post_message(Messages::WindowClient::MouseMove(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta(), event.is_drag(), event.mime_types()));
|
||||
break;
|
||||
case Event::MouseDown:
|
||||
m_client->post_message(Messages::WindowClient::MouseDown(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta()));
|
||||
break;
|
||||
case Event::MouseDoubleClick:
|
||||
m_client->post_message(Messages::WindowClient::MouseDoubleClick(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta()));
|
||||
break;
|
||||
case Event::MouseUp:
|
||||
m_client->post_message(Messages::WindowClient::MouseUp(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta()));
|
||||
break;
|
||||
case Event::MouseWheel:
|
||||
m_client->post_message(Messages::WindowClient::MouseWheel(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta()));
|
||||
break;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::update_menu_item_text(PopupMenuItem item)
|
||||
{
|
||||
if (m_window_menu) {
|
||||
m_window_menu->item((int)item).set_text(item == PopupMenuItem::Minimize ? (m_minimized ? "Unminimize" : "Minimize") : (m_maximized ? "Restore" : "Maximize"));
|
||||
m_window_menu->redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::update_menu_item_enabled(PopupMenuItem item)
|
||||
{
|
||||
if (m_window_menu) {
|
||||
m_window_menu->item((int)item).set_enabled(item == PopupMenuItem::Minimize ? m_minimizable : m_resizable);
|
||||
m_window_menu->redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::set_minimized(bool minimized)
|
||||
{
|
||||
if (m_minimized == minimized)
|
||||
return;
|
||||
if (minimized && !m_minimizable)
|
||||
return;
|
||||
m_minimized = minimized;
|
||||
update_menu_item_text(PopupMenuItem::Minimize);
|
||||
Compositor::the().invalidate_occlusions();
|
||||
Compositor::the().invalidate_screen(frame().rect());
|
||||
if (!blocking_modal_window())
|
||||
start_minimize_animation();
|
||||
if (!minimized)
|
||||
request_update({ {}, size() });
|
||||
WindowManager::the().notify_minimization_state_changed(*this);
|
||||
}
|
||||
|
||||
void Window::set_minimizable(bool minimizable)
|
||||
{
|
||||
if (m_minimizable == minimizable)
|
||||
return;
|
||||
m_minimizable = minimizable;
|
||||
update_menu_item_enabled(PopupMenuItem::Minimize);
|
||||
// TODO: Hide/show (or alternatively change enabled state of) window minimize button dynamically depending on value of m_minimizable
|
||||
}
|
||||
|
||||
void Window::set_taskbar_rect(const Gfx::IntRect& rect)
|
||||
{
|
||||
m_taskbar_rect = rect;
|
||||
m_have_taskbar_rect = !m_taskbar_rect.is_empty();
|
||||
}
|
||||
|
||||
void Window::start_minimize_animation()
|
||||
{
|
||||
if (!m_have_taskbar_rect) {
|
||||
// If this is a modal window, it may not have its own taskbar
|
||||
// button, so there is no rectangle. In that case, walk the
|
||||
// modal stack until we find a window that may have one
|
||||
WindowManager::the().for_each_window_in_modal_stack(*this, [&](Window& w, bool) {
|
||||
if (w.has_taskbar_rect()) {
|
||||
// We purposely do NOT set m_have_taskbar_rect to true here
|
||||
// because we want to only copy the rectangle from the
|
||||
// window that has it, but since this window wouldn't receive
|
||||
// any updates down the road we want to query it again
|
||||
// next time we want to start the animation
|
||||
m_taskbar_rect = w.taskbar_rect();
|
||||
|
||||
ASSERT(!m_have_taskbar_rect); // should remain unset!
|
||||
return IterationDecision::Break;
|
||||
};
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
m_minimize_animation_step = 0;
|
||||
}
|
||||
|
||||
void Window::set_opacity(float opacity)
|
||||
{
|
||||
if (m_opacity == opacity)
|
||||
return;
|
||||
bool was_opaque = is_opaque();
|
||||
m_opacity = opacity;
|
||||
if (was_opaque != is_opaque())
|
||||
Compositor::the().invalidate_occlusions();
|
||||
Compositor::the().invalidate_screen(frame().rect());
|
||||
WindowManager::the().notify_opacity_changed(*this);
|
||||
}
|
||||
|
||||
void Window::set_occluded(bool occluded)
|
||||
{
|
||||
if (m_occluded == occluded)
|
||||
return;
|
||||
m_occluded = occluded;
|
||||
WindowManager::the().notify_occlusion_state_changed(*this);
|
||||
}
|
||||
|
||||
void Window::set_maximized(bool maximized)
|
||||
{
|
||||
if (m_maximized == maximized)
|
||||
return;
|
||||
if (maximized && (!is_resizable() || resize_aspect_ratio().has_value()))
|
||||
return;
|
||||
set_tiled(WindowTileType::None);
|
||||
m_maximized = maximized;
|
||||
update_menu_item_text(PopupMenuItem::Maximize);
|
||||
if (maximized) {
|
||||
m_unmaximized_rect = m_rect;
|
||||
set_rect(WindowManager::the().maximized_window_rect(*this));
|
||||
} else {
|
||||
set_rect(m_unmaximized_rect);
|
||||
}
|
||||
m_frame.did_set_maximized({}, maximized);
|
||||
Core::EventLoop::current().post_event(*this, make<ResizeEvent>(m_rect));
|
||||
set_default_positioned(false);
|
||||
}
|
||||
|
||||
void Window::set_resizable(bool resizable)
|
||||
{
|
||||
if (m_resizable == resizable)
|
||||
return;
|
||||
m_resizable = resizable;
|
||||
update_menu_item_enabled(PopupMenuItem::Maximize);
|
||||
// TODO: Hide/show (or alternatively change enabled state of) window maximize button dynamically depending on value of is_resizable()
|
||||
}
|
||||
|
||||
void Window::event(Core::Event& event)
|
||||
{
|
||||
if (!m_client) {
|
||||
ASSERT(parent());
|
||||
event.ignore();
|
||||
return;
|
||||
}
|
||||
|
||||
if (blocking_modal_window()) {
|
||||
// We still want to handle the WindowDeactivated event below when a new modal is
|
||||
// created to notify its parent window, despite it being "blocked by modal window".
|
||||
if (event.type() != Event::WindowDeactivated)
|
||||
return;
|
||||
}
|
||||
|
||||
if (static_cast<Event&>(event).is_mouse_event())
|
||||
return handle_mouse_event(static_cast<const MouseEvent&>(event));
|
||||
|
||||
switch (event.type()) {
|
||||
case Event::WindowEntered:
|
||||
m_client->post_message(Messages::WindowClient::WindowEntered(m_window_id));
|
||||
break;
|
||||
case Event::WindowLeft:
|
||||
m_client->post_message(Messages::WindowClient::WindowLeft(m_window_id));
|
||||
break;
|
||||
case Event::KeyDown:
|
||||
m_client->post_message(
|
||||
Messages::WindowClient::KeyDown(m_window_id,
|
||||
(u32) static_cast<const KeyEvent&>(event).code_point(),
|
||||
(u32) static_cast<const KeyEvent&>(event).key(),
|
||||
static_cast<const KeyEvent&>(event).modifiers(),
|
||||
(u32) static_cast<const KeyEvent&>(event).scancode()));
|
||||
break;
|
||||
case Event::KeyUp:
|
||||
m_client->post_message(
|
||||
Messages::WindowClient::KeyUp(m_window_id,
|
||||
(u32) static_cast<const KeyEvent&>(event).code_point(),
|
||||
(u32) static_cast<const KeyEvent&>(event).key(),
|
||||
static_cast<const KeyEvent&>(event).modifiers(),
|
||||
(u32) static_cast<const KeyEvent&>(event).scancode()));
|
||||
break;
|
||||
case Event::WindowActivated:
|
||||
m_client->post_message(Messages::WindowClient::WindowActivated(m_window_id));
|
||||
break;
|
||||
case Event::WindowDeactivated:
|
||||
m_client->post_message(Messages::WindowClient::WindowDeactivated(m_window_id));
|
||||
break;
|
||||
case Event::WindowInputEntered:
|
||||
m_client->post_message(Messages::WindowClient::WindowInputEntered(m_window_id));
|
||||
break;
|
||||
case Event::WindowInputLeft:
|
||||
m_client->post_message(Messages::WindowClient::WindowInputLeft(m_window_id));
|
||||
break;
|
||||
case Event::WindowCloseRequest:
|
||||
m_client->post_message(Messages::WindowClient::WindowCloseRequest(m_window_id));
|
||||
break;
|
||||
case Event::WindowResized:
|
||||
m_client->post_message(Messages::WindowClient::WindowResized(m_window_id, static_cast<const ResizeEvent&>(event).rect()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Window::set_global_cursor_tracking_enabled(bool enabled)
|
||||
{
|
||||
m_global_cursor_tracking_enabled = enabled;
|
||||
}
|
||||
|
||||
void Window::set_visible(bool b)
|
||||
{
|
||||
if (m_visible == b)
|
||||
return;
|
||||
m_visible = b;
|
||||
|
||||
Compositor::the().invalidate_occlusions();
|
||||
if (m_visible)
|
||||
invalidate(true);
|
||||
else
|
||||
Compositor::the().invalidate_screen(frame().rect());
|
||||
}
|
||||
|
||||
void Window::invalidate(bool invalidate_frame)
|
||||
{
|
||||
m_invalidated = true;
|
||||
m_invalidated_all = true;
|
||||
m_invalidated_frame |= invalidate_frame;
|
||||
m_dirty_rects.clear();
|
||||
Compositor::the().invalidate_window();
|
||||
}
|
||||
|
||||
void Window::invalidate(const Gfx::IntRect& rect, bool with_frame)
|
||||
{
|
||||
if (type() == WindowType::MenuApplet) {
|
||||
AppletManager::the().invalidate_applet(*this, rect);
|
||||
return;
|
||||
}
|
||||
|
||||
if (invalidate_no_notify(rect, with_frame))
|
||||
Compositor::the().invalidate_window();
|
||||
}
|
||||
|
||||
bool Window::invalidate_no_notify(const Gfx::IntRect& rect, bool with_frame)
|
||||
{
|
||||
if (rect.is_empty())
|
||||
return false;
|
||||
if (m_invalidated_all) {
|
||||
m_invalidated_frame |= with_frame;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto outer_rect = frame().rect();
|
||||
auto inner_rect = rect;
|
||||
inner_rect.move_by(position());
|
||||
// FIXME: This seems slightly wrong; the inner rect shouldn't intersect the border part of the outer rect.
|
||||
inner_rect.intersect(outer_rect);
|
||||
if (inner_rect.is_empty())
|
||||
return false;
|
||||
|
||||
m_invalidated = true;
|
||||
m_invalidated_frame |= with_frame;
|
||||
m_dirty_rects.add(inner_rect.translated(-outer_rect.location()));
|
||||
return true;
|
||||
}
|
||||
|
||||
void Window::prepare_dirty_rects()
|
||||
{
|
||||
if (m_invalidated_all) {
|
||||
if (m_invalidated_frame)
|
||||
m_dirty_rects = frame().rect();
|
||||
else
|
||||
m_dirty_rects = rect();
|
||||
} else {
|
||||
m_dirty_rects.move_by(frame().rect().location());
|
||||
}
|
||||
}
|
||||
|
||||
void Window::clear_dirty_rects()
|
||||
{
|
||||
m_invalidated_all = false;
|
||||
m_invalidated_frame = false;
|
||||
m_invalidated = false;
|
||||
m_dirty_rects.clear_with_capacity();
|
||||
}
|
||||
|
||||
bool Window::is_active() const
|
||||
{
|
||||
return WindowManager::the().active_window() == this;
|
||||
}
|
||||
|
||||
Window* Window::blocking_modal_window()
|
||||
{
|
||||
// A window is blocked if any immediate child, or any child further
|
||||
// down the chain is modal
|
||||
for (auto& window : m_child_windows) {
|
||||
if (window && !window->is_destroyed()) {
|
||||
if (window->is_modal())
|
||||
return window;
|
||||
|
||||
if (auto* blocking_window = window->blocking_modal_window())
|
||||
return blocking_window;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Window::set_default_icon()
|
||||
{
|
||||
m_icon = default_window_icon();
|
||||
}
|
||||
|
||||
void Window::request_update(const Gfx::IntRect& rect, bool ignore_occlusion)
|
||||
{
|
||||
if (rect.is_empty())
|
||||
return;
|
||||
if (m_pending_paint_rects.is_empty()) {
|
||||
deferred_invoke([this, ignore_occlusion](auto&) {
|
||||
client()->post_paint_message(*this, ignore_occlusion);
|
||||
});
|
||||
}
|
||||
m_pending_paint_rects.add(rect);
|
||||
}
|
||||
|
||||
void Window::ensure_window_menu()
|
||||
{
|
||||
if (!m_window_menu) {
|
||||
m_window_menu = Menu::construct(nullptr, -1, "(Window Menu)");
|
||||
m_window_menu->set_window_menu_of(*this);
|
||||
|
||||
auto minimize_item = make<MenuItem>(*m_window_menu, 1, m_minimized ? "Unminimize" : "Minimize");
|
||||
m_window_menu_minimize_item = minimize_item.ptr();
|
||||
m_window_menu->add_item(move(minimize_item));
|
||||
|
||||
auto maximize_item = make<MenuItem>(*m_window_menu, 2, m_maximized ? "Restore" : "Maximize");
|
||||
m_window_menu_maximize_item = maximize_item.ptr();
|
||||
m_window_menu->add_item(move(maximize_item));
|
||||
|
||||
m_window_menu->add_item(make<MenuItem>(*m_window_menu, MenuItem::Type::Separator));
|
||||
|
||||
auto close_item = make<MenuItem>(*m_window_menu, 3, "Close");
|
||||
m_window_menu_close_item = close_item.ptr();
|
||||
m_window_menu_close_item->set_icon(&close_icon());
|
||||
m_window_menu_close_item->set_default(true);
|
||||
m_window_menu->add_item(move(close_item));
|
||||
|
||||
m_window_menu->item((int)PopupMenuItem::Minimize).set_enabled(m_minimizable);
|
||||
m_window_menu->item((int)PopupMenuItem::Maximize).set_enabled(m_resizable);
|
||||
|
||||
m_window_menu->on_item_activation = [&](auto& item) {
|
||||
switch (item.identifier()) {
|
||||
case 1:
|
||||
WindowManager::the().minimize_windows(*this, !m_minimized);
|
||||
if (!m_minimized)
|
||||
WindowManager::the().move_to_front_and_make_active(*this);
|
||||
break;
|
||||
case 2:
|
||||
WindowManager::the().maximize_windows(*this, !m_maximized);
|
||||
WindowManager::the().move_to_front_and_make_active(*this);
|
||||
break;
|
||||
case 3:
|
||||
request_close();
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void Window::popup_window_menu(const Gfx::IntPoint& position, WindowMenuDefaultAction default_action)
|
||||
{
|
||||
ensure_window_menu();
|
||||
if (default_action == WindowMenuDefaultAction::BasedOnWindowState) {
|
||||
// When clicked on the task bar, determine the default action
|
||||
if (!is_active() && !is_minimized())
|
||||
default_action = WindowMenuDefaultAction::None;
|
||||
else if (is_minimized())
|
||||
default_action = WindowMenuDefaultAction::Unminimize;
|
||||
else
|
||||
default_action = WindowMenuDefaultAction::Minimize;
|
||||
}
|
||||
m_window_menu_minimize_item->set_default(default_action == WindowMenuDefaultAction::Minimize || default_action == WindowMenuDefaultAction::Unminimize);
|
||||
m_window_menu_minimize_item->set_icon(m_minimized ? nullptr : &minimize_icon());
|
||||
m_window_menu_maximize_item->set_default(default_action == WindowMenuDefaultAction::Maximize || default_action == WindowMenuDefaultAction::Restore);
|
||||
m_window_menu_maximize_item->set_icon(m_maximized ? &restore_icon() : &maximize_icon());
|
||||
m_window_menu_close_item->set_default(default_action == WindowMenuDefaultAction::Close);
|
||||
|
||||
m_window_menu->popup(position);
|
||||
}
|
||||
|
||||
void Window::window_menu_activate_default()
|
||||
{
|
||||
ensure_window_menu();
|
||||
m_window_menu->activate_default();
|
||||
}
|
||||
|
||||
void Window::request_close()
|
||||
{
|
||||
Event close_request(Event::WindowCloseRequest);
|
||||
event(close_request);
|
||||
}
|
||||
|
||||
void Window::set_fullscreen(bool fullscreen)
|
||||
{
|
||||
if (m_fullscreen == fullscreen)
|
||||
return;
|
||||
m_fullscreen = fullscreen;
|
||||
Gfx::IntRect new_window_rect = m_rect;
|
||||
if (m_fullscreen) {
|
||||
m_saved_nonfullscreen_rect = m_rect;
|
||||
new_window_rect = Screen::the().rect();
|
||||
} else if (!m_saved_nonfullscreen_rect.is_empty()) {
|
||||
new_window_rect = m_saved_nonfullscreen_rect;
|
||||
}
|
||||
|
||||
Core::EventLoop::current().post_event(*this, make<ResizeEvent>(new_window_rect));
|
||||
set_rect(new_window_rect);
|
||||
}
|
||||
|
||||
Gfx::IntRect Window::tiled_rect(WindowTileType tiled) const
|
||||
{
|
||||
int frame_width = (m_frame.rect().width() - m_rect.width()) / 2;
|
||||
int title_bar_height = m_frame.title_bar_rect().height();
|
||||
int menu_height = WindowManager::the().maximized_window_rect(*this).y();
|
||||
int max_height = WindowManager::the().maximized_window_rect(*this).height();
|
||||
|
||||
switch (tiled) {
|
||||
case WindowTileType::None:
|
||||
return m_untiled_rect;
|
||||
case WindowTileType::Left:
|
||||
return Gfx::IntRect(0,
|
||||
menu_height,
|
||||
Screen::the().width() / 2 - frame_width,
|
||||
max_height);
|
||||
case WindowTileType::Right:
|
||||
return Gfx::IntRect(Screen::the().width() / 2 + frame_width,
|
||||
menu_height,
|
||||
Screen::the().width() / 2 - frame_width,
|
||||
max_height);
|
||||
case WindowTileType::Top:
|
||||
return Gfx::IntRect(0,
|
||||
menu_height,
|
||||
Screen::the().width() - frame_width,
|
||||
(max_height - title_bar_height) / 2 - frame_width);
|
||||
case WindowTileType::Bottom:
|
||||
return Gfx::IntRect(0,
|
||||
menu_height + (title_bar_height + max_height) / 2 + frame_width,
|
||||
Screen::the().width() - frame_width,
|
||||
(max_height - title_bar_height) / 2 - frame_width);
|
||||
case WindowTileType::TopLeft:
|
||||
return Gfx::IntRect(0,
|
||||
menu_height,
|
||||
Screen::the().width() / 2 - frame_width,
|
||||
(max_height - title_bar_height) / 2 - frame_width);
|
||||
case WindowTileType::TopRight:
|
||||
return Gfx::IntRect(Screen::the().width() / 2 + frame_width,
|
||||
menu_height,
|
||||
Screen::the().width() / 2 - frame_width,
|
||||
(max_height - title_bar_height) / 2 - frame_width);
|
||||
case WindowTileType::BottomLeft:
|
||||
return Gfx::IntRect(0,
|
||||
menu_height + (title_bar_height + max_height) / 2 + frame_width,
|
||||
Screen::the().width() / 2 - frame_width,
|
||||
(max_height - title_bar_height) / 2);
|
||||
case WindowTileType::BottomRight:
|
||||
return Gfx::IntRect(Screen::the().width() / 2 + frame_width,
|
||||
menu_height + (title_bar_height + max_height) / 2 + frame_width,
|
||||
Screen::the().width() / 2 - frame_width,
|
||||
(max_height - title_bar_height) / 2);
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::set_tiled(WindowTileType tiled)
|
||||
{
|
||||
if (m_tiled == tiled)
|
||||
return;
|
||||
|
||||
if (resize_aspect_ratio().has_value())
|
||||
return;
|
||||
|
||||
m_tiled = tiled;
|
||||
if (tiled != WindowTileType::None)
|
||||
m_untiled_rect = m_rect;
|
||||
set_rect(tiled_rect(tiled));
|
||||
Core::EventLoop::current().post_event(*this, make<ResizeEvent>(m_rect));
|
||||
}
|
||||
|
||||
void Window::detach_client(Badge<ClientConnection>)
|
||||
{
|
||||
m_client = nullptr;
|
||||
}
|
||||
|
||||
void Window::recalculate_rect()
|
||||
{
|
||||
if (!is_resizable())
|
||||
return;
|
||||
|
||||
bool send_event = true;
|
||||
if (m_tiled != WindowTileType::None) {
|
||||
set_rect(tiled_rect(m_tiled));
|
||||
} else if (is_maximized()) {
|
||||
set_rect(WindowManager::the().maximized_window_rect(*this));
|
||||
} else if (type() == WindowType::Desktop) {
|
||||
set_rect(WindowManager::the().desktop_rect());
|
||||
} else {
|
||||
send_event = false;
|
||||
}
|
||||
|
||||
if (send_event) {
|
||||
Core::EventLoop::current().post_event(*this, make<ResizeEvent>(m_rect));
|
||||
}
|
||||
}
|
||||
|
||||
void Window::add_child_window(Window& child_window)
|
||||
{
|
||||
m_child_windows.append(child_window);
|
||||
}
|
||||
|
||||
void Window::add_accessory_window(Window& accessory_window)
|
||||
{
|
||||
m_accessory_windows.append(accessory_window);
|
||||
}
|
||||
|
||||
void Window::set_parent_window(Window& parent_window)
|
||||
{
|
||||
ASSERT(!m_parent_window);
|
||||
m_parent_window = parent_window;
|
||||
if (m_accessory)
|
||||
parent_window.add_accessory_window(*this);
|
||||
else
|
||||
parent_window.add_child_window(*this);
|
||||
}
|
||||
|
||||
bool Window::is_accessory() const
|
||||
{
|
||||
if (!m_accessory)
|
||||
return false;
|
||||
if (parent_window() != nullptr)
|
||||
return true;
|
||||
|
||||
// If accessory window was unparented, convert to a regular window
|
||||
const_cast<Window*>(this)->set_accessory(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Window::is_accessory_of(Window& window) const
|
||||
{
|
||||
if (!is_accessory())
|
||||
return false;
|
||||
return parent_window() == &window;
|
||||
}
|
||||
|
||||
void Window::modal_unparented()
|
||||
{
|
||||
m_modal = false;
|
||||
WindowManager::the().notify_modal_unparented(*this);
|
||||
}
|
||||
|
||||
bool Window::is_modal() const
|
||||
{
|
||||
if (!m_modal)
|
||||
return false;
|
||||
if (!m_parent_window) {
|
||||
const_cast<Window*>(this)->modal_unparented();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Window::set_progress(int progress)
|
||||
{
|
||||
if (m_progress == progress)
|
||||
return;
|
||||
|
||||
m_progress = progress;
|
||||
WindowManager::the().notify_progress_changed(*this);
|
||||
}
|
||||
|
||||
bool Window::is_descendant_of(Window& window) const
|
||||
{
|
||||
for (auto* parent = parent_window(); parent; parent = parent->parent_window()) {
|
||||
if (parent == &window)
|
||||
return true;
|
||||
for (auto& accessory : parent->accessory_windows()) {
|
||||
if (accessory == &window)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
363
Userland/Services/WindowServer/Window.h
Normal file
363
Userland/Services/WindowServer/Window.h
Normal file
|
@ -0,0 +1,363 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/InlineLinkedList.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibCore/Object.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/DisjointRectSet.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <WindowServer/WindowFrame.h>
|
||||
#include <WindowServer/WindowType.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
class ClientConnection;
|
||||
class Cursor;
|
||||
class Menu;
|
||||
class MenuItem;
|
||||
class MouseEvent;
|
||||
|
||||
enum WMEventMask {
|
||||
WindowRectChanges = 1 << 0,
|
||||
WindowStateChanges = 1 << 1,
|
||||
WindowIconChanges = 1 << 2,
|
||||
WindowRemovals = 1 << 3,
|
||||
};
|
||||
|
||||
enum class WindowTileType {
|
||||
None = 0,
|
||||
Left,
|
||||
Right,
|
||||
Top,
|
||||
Bottom,
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomLeft,
|
||||
BottomRight
|
||||
};
|
||||
|
||||
enum class PopupMenuItem {
|
||||
Minimize = 0,
|
||||
Maximize,
|
||||
};
|
||||
|
||||
enum class WindowMenuDefaultAction {
|
||||
None = 0,
|
||||
BasedOnWindowState,
|
||||
Close,
|
||||
Minimize,
|
||||
Unminimize,
|
||||
Maximize,
|
||||
Restore
|
||||
};
|
||||
|
||||
class Window final : public Core::Object
|
||||
, public InlineLinkedListNode<Window> {
|
||||
C_OBJECT(Window)
|
||||
public:
|
||||
Window(ClientConnection&, WindowType, int window_id, bool modal, bool minimizable, bool frameless, bool resizable, bool fullscreen, bool accessory, Window* parent_window = nullptr);
|
||||
Window(Core::Object&, WindowType);
|
||||
virtual ~Window() override;
|
||||
|
||||
void popup_window_menu(const Gfx::IntPoint&, WindowMenuDefaultAction);
|
||||
void window_menu_activate_default();
|
||||
void request_close();
|
||||
|
||||
unsigned wm_event_mask() const { return m_wm_event_mask; }
|
||||
void set_wm_event_mask(unsigned mask) { m_wm_event_mask = mask; }
|
||||
|
||||
bool is_minimized() const { return m_minimized; }
|
||||
void set_minimized(bool);
|
||||
|
||||
bool is_minimizable() const { return m_minimizable; }
|
||||
void set_minimizable(bool);
|
||||
|
||||
bool is_resizable() const { return m_resizable && !m_fullscreen; }
|
||||
void set_resizable(bool);
|
||||
|
||||
bool is_maximized() const { return m_maximized; }
|
||||
void set_maximized(bool);
|
||||
|
||||
bool is_fullscreen() const { return m_fullscreen; }
|
||||
void set_fullscreen(bool);
|
||||
|
||||
WindowTileType tiled() const { return m_tiled; }
|
||||
void set_tiled(WindowTileType);
|
||||
|
||||
bool is_occluded() const { return m_occluded; }
|
||||
void set_occluded(bool);
|
||||
|
||||
bool is_movable() const
|
||||
{
|
||||
return m_type == WindowType::Normal;
|
||||
}
|
||||
|
||||
WindowFrame& frame() { return m_frame; }
|
||||
const WindowFrame& frame() const { return m_frame; }
|
||||
|
||||
Window* blocking_modal_window();
|
||||
|
||||
bool listens_to_wm_events() const { return m_listens_to_wm_events; }
|
||||
|
||||
ClientConnection* client() { return m_client; }
|
||||
const ClientConnection* client() const { return m_client; }
|
||||
|
||||
WindowType type() const { return m_type; }
|
||||
int window_id() const { return m_window_id; }
|
||||
|
||||
bool is_internal() const { return m_client_id == -1; }
|
||||
i32 client_id() const { return m_client_id; }
|
||||
|
||||
String title() const { return m_title; }
|
||||
void set_title(const String&);
|
||||
|
||||
float opacity() const { return m_opacity; }
|
||||
void set_opacity(float);
|
||||
|
||||
int x() const { return m_rect.x(); }
|
||||
int y() const { return m_rect.y(); }
|
||||
int width() const { return m_rect.width(); }
|
||||
int height() const { return m_rect.height(); }
|
||||
|
||||
bool is_active() const;
|
||||
|
||||
bool is_visible() const { return m_visible; }
|
||||
void set_visible(bool);
|
||||
|
||||
bool is_modal() const;
|
||||
bool is_modal_dont_unparent() const { return m_modal && m_parent_window; }
|
||||
|
||||
Gfx::IntRect rect() const { return m_rect; }
|
||||
void set_rect(const Gfx::IntRect&);
|
||||
void set_rect(int x, int y, int width, int height) { set_rect({ x, y, width, height }); }
|
||||
void set_rect_without_repaint(const Gfx::IntRect&);
|
||||
|
||||
void set_taskbar_rect(const Gfx::IntRect&);
|
||||
const Gfx::IntRect& taskbar_rect() const { return m_taskbar_rect; }
|
||||
|
||||
void move_to(const Gfx::IntPoint& position) { set_rect({ position, size() }); }
|
||||
void move_to(int x, int y) { move_to({ x, y }); }
|
||||
|
||||
void move_by(const Gfx::IntPoint& delta) { set_position_without_repaint(position().translated(delta)); }
|
||||
|
||||
Gfx::IntPoint position() const { return m_rect.location(); }
|
||||
void set_position(const Gfx::IntPoint& position) { set_rect({ position.x(), position.y(), width(), height() }); }
|
||||
void set_position_without_repaint(const Gfx::IntPoint& position) { set_rect_without_repaint({ position.x(), position.y(), width(), height() }); }
|
||||
|
||||
Gfx::IntSize size() const { return m_rect.size(); }
|
||||
|
||||
void invalidate(bool with_frame = true);
|
||||
void invalidate(const Gfx::IntRect&, bool with_frame = false);
|
||||
bool invalidate_no_notify(const Gfx::IntRect& rect, bool with_frame = false);
|
||||
|
||||
void prepare_dirty_rects();
|
||||
void clear_dirty_rects();
|
||||
Gfx::DisjointRectSet& dirty_rects() { return m_dirty_rects; }
|
||||
|
||||
virtual void event(Core::Event&) override;
|
||||
|
||||
// Only used by WindowType::MenuApplet. Perhaps it could be a Window subclass? I don't know.
|
||||
void set_rect_in_menubar(const Gfx::IntRect& rect) { m_rect_in_menubar = rect; }
|
||||
const Gfx::IntRect& rect_in_menubar() const { return m_rect_in_menubar; }
|
||||
|
||||
const Gfx::Bitmap* backing_store() const { return m_backing_store.ptr(); }
|
||||
Gfx::Bitmap* backing_store() { return m_backing_store.ptr(); }
|
||||
|
||||
void set_backing_store(RefPtr<Gfx::Bitmap>&& backing_store)
|
||||
{
|
||||
m_last_backing_store = move(m_backing_store);
|
||||
m_backing_store = move(backing_store);
|
||||
}
|
||||
|
||||
void swap_backing_stores()
|
||||
{
|
||||
swap(m_backing_store, m_last_backing_store);
|
||||
}
|
||||
|
||||
Gfx::Bitmap* last_backing_store() { return m_last_backing_store.ptr(); }
|
||||
|
||||
void set_global_cursor_tracking_enabled(bool);
|
||||
void set_automatic_cursor_tracking_enabled(bool enabled) { m_automatic_cursor_tracking_enabled = enabled; }
|
||||
bool global_cursor_tracking() const { return m_global_cursor_tracking_enabled || m_automatic_cursor_tracking_enabled; }
|
||||
|
||||
bool has_alpha_channel() const { return m_has_alpha_channel; }
|
||||
void set_has_alpha_channel(bool value) { m_has_alpha_channel = value; }
|
||||
|
||||
Gfx::IntSize size_increment() const { return m_size_increment; }
|
||||
void set_size_increment(const Gfx::IntSize& increment) { m_size_increment = increment; }
|
||||
|
||||
const Optional<Gfx::IntSize>& resize_aspect_ratio() const { return m_resize_aspect_ratio; }
|
||||
void set_resize_aspect_ratio(const Optional<Gfx::IntSize>& ratio) { m_resize_aspect_ratio = ratio; }
|
||||
|
||||
Gfx::IntSize base_size() const { return m_base_size; }
|
||||
void set_base_size(const Gfx::IntSize& size) { m_base_size = size; }
|
||||
|
||||
const Gfx::Bitmap& icon() const { return *m_icon; }
|
||||
void set_icon(NonnullRefPtr<Gfx::Bitmap>&& icon) { m_icon = move(icon); }
|
||||
|
||||
void set_default_icon();
|
||||
|
||||
const Cursor* cursor() const { return m_cursor.ptr(); }
|
||||
void set_cursor(RefPtr<Cursor> cursor) { m_cursor = move(cursor); }
|
||||
|
||||
void request_update(const Gfx::IntRect&, bool ignore_occlusion = false);
|
||||
Gfx::DisjointRectSet take_pending_paint_rects() { return move(m_pending_paint_rects); }
|
||||
|
||||
bool has_taskbar_rect() const { return m_have_taskbar_rect; };
|
||||
bool in_minimize_animation() const { return m_minimize_animation_step != -1; }
|
||||
int minimize_animation_index() const { return m_minimize_animation_step; }
|
||||
void step_minimize_animation() { m_minimize_animation_step += 1; }
|
||||
void start_minimize_animation();
|
||||
void end_minimize_animation() { m_minimize_animation_step = -1; }
|
||||
|
||||
Gfx::IntRect tiled_rect(WindowTileType) const;
|
||||
void recalculate_rect();
|
||||
|
||||
// For InlineLinkedList.
|
||||
// FIXME: Maybe make a ListHashSet and then WindowManager can just use that.
|
||||
Window* m_next { nullptr };
|
||||
Window* m_prev { nullptr };
|
||||
|
||||
void detach_client(Badge<ClientConnection>);
|
||||
|
||||
Window* parent_window() { return m_parent_window; }
|
||||
const Window* parent_window() const { return m_parent_window; }
|
||||
|
||||
void set_parent_window(Window&);
|
||||
|
||||
Vector<WeakPtr<Window>>& child_windows() { return m_child_windows; }
|
||||
const Vector<WeakPtr<Window>>& child_windows() const { return m_child_windows; }
|
||||
|
||||
Vector<WeakPtr<Window>>& accessory_windows() { return m_accessory_windows; }
|
||||
const Vector<WeakPtr<Window>>& accessory_windows() const { return m_accessory_windows; }
|
||||
|
||||
bool is_descendant_of(Window&) const;
|
||||
|
||||
void set_accessory(bool accessory) { m_accessory = accessory; }
|
||||
bool is_accessory() const;
|
||||
bool is_accessory_of(Window&) const;
|
||||
|
||||
void set_frameless(bool frameless) { m_frameless = frameless; }
|
||||
bool is_frameless() const { return m_frameless; }
|
||||
|
||||
int progress() const { return m_progress; }
|
||||
void set_progress(int);
|
||||
|
||||
bool is_destroyed() const { return m_destroyed; }
|
||||
void destroy();
|
||||
|
||||
bool default_positioned() const { return m_default_positioned; }
|
||||
void set_default_positioned(bool p) { m_default_positioned = p; }
|
||||
|
||||
bool is_invalidated() const { return m_invalidated; }
|
||||
|
||||
bool is_opaque() const
|
||||
{
|
||||
if (opacity() < 1.0f)
|
||||
return false;
|
||||
if (has_alpha_channel())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
Gfx::DisjointRectSet& opaque_rects() { return m_opaque_rects; }
|
||||
Gfx::DisjointRectSet& transparency_rects() { return m_transparency_rects; }
|
||||
Gfx::DisjointRectSet& transparency_wallpaper_rects() { return m_transparency_wallpaper_rects; }
|
||||
|
||||
private:
|
||||
void handle_mouse_event(const MouseEvent&);
|
||||
void update_menu_item_text(PopupMenuItem item);
|
||||
void update_menu_item_enabled(PopupMenuItem item);
|
||||
void add_child_window(Window&);
|
||||
void add_accessory_window(Window&);
|
||||
void ensure_window_menu();
|
||||
void modal_unparented();
|
||||
|
||||
ClientConnection* m_client { nullptr };
|
||||
|
||||
WeakPtr<Window> m_parent_window;
|
||||
Vector<WeakPtr<Window>> m_child_windows;
|
||||
Vector<WeakPtr<Window>> m_accessory_windows;
|
||||
|
||||
String m_title;
|
||||
Gfx::IntRect m_rect;
|
||||
Gfx::IntRect m_saved_nonfullscreen_rect;
|
||||
Gfx::IntRect m_taskbar_rect;
|
||||
Gfx::DisjointRectSet m_dirty_rects;
|
||||
Gfx::DisjointRectSet m_opaque_rects;
|
||||
Gfx::DisjointRectSet m_transparency_rects;
|
||||
Gfx::DisjointRectSet m_transparency_wallpaper_rects;
|
||||
WindowType m_type { WindowType::Normal };
|
||||
bool m_global_cursor_tracking_enabled { false };
|
||||
bool m_automatic_cursor_tracking_enabled { false };
|
||||
bool m_visible { true };
|
||||
bool m_has_alpha_channel { false };
|
||||
bool m_modal { false };
|
||||
bool m_minimizable { false };
|
||||
bool m_frameless { false };
|
||||
bool m_resizable { false };
|
||||
Optional<Gfx::IntSize> m_resize_aspect_ratio {};
|
||||
bool m_listens_to_wm_events { false };
|
||||
bool m_minimized { false };
|
||||
bool m_maximized { false };
|
||||
bool m_fullscreen { false };
|
||||
bool m_accessory { false };
|
||||
bool m_destroyed { false };
|
||||
bool m_default_positioned { false };
|
||||
bool m_have_taskbar_rect { false };
|
||||
bool m_invalidated { true };
|
||||
bool m_invalidated_all { true };
|
||||
bool m_invalidated_frame { true };
|
||||
WindowTileType m_tiled { WindowTileType::None };
|
||||
Gfx::IntRect m_untiled_rect;
|
||||
bool m_occluded { false };
|
||||
RefPtr<Gfx::Bitmap> m_backing_store;
|
||||
RefPtr<Gfx::Bitmap> m_last_backing_store;
|
||||
int m_window_id { -1 };
|
||||
i32 m_client_id { -1 };
|
||||
float m_opacity { 1 };
|
||||
Gfx::IntSize m_size_increment;
|
||||
Gfx::IntSize m_base_size;
|
||||
NonnullRefPtr<Gfx::Bitmap> m_icon;
|
||||
RefPtr<Cursor> m_cursor;
|
||||
WindowFrame m_frame;
|
||||
unsigned m_wm_event_mask { 0 };
|
||||
Gfx::DisjointRectSet m_pending_paint_rects;
|
||||
Gfx::IntRect m_unmaximized_rect;
|
||||
Gfx::IntRect m_rect_in_menubar;
|
||||
RefPtr<Menu> m_window_menu;
|
||||
MenuItem* m_window_menu_minimize_item { nullptr };
|
||||
MenuItem* m_window_menu_maximize_item { nullptr };
|
||||
MenuItem* m_window_menu_close_item { nullptr };
|
||||
int m_minimize_animation_step { -1 };
|
||||
int m_progress { -1 };
|
||||
};
|
||||
|
||||
}
|
42
Userland/Services/WindowServer/WindowClient.ipc
Normal file
42
Userland/Services/WindowServer/WindowClient.ipc
Normal file
|
@ -0,0 +1,42 @@
|
|||
endpoint WindowClient = 4
|
||||
{
|
||||
Paint(i32 window_id, Gfx::IntSize window_size, Vector<Gfx::IntRect> rects) =|
|
||||
MouseMove(i32 window_id, Gfx::IntPoint mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta, bool is_drag, Vector<String> mime_types) =|
|
||||
MouseDown(i32 window_id, Gfx::IntPoint mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta) =|
|
||||
MouseDoubleClick(i32 window_id, Gfx::IntPoint mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta) =|
|
||||
MouseUp(i32 window_id, Gfx::IntPoint mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta) =|
|
||||
MouseWheel(i32 window_id, Gfx::IntPoint mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta) =|
|
||||
WindowEntered(i32 window_id) =|
|
||||
WindowLeft(i32 window_id) =|
|
||||
WindowInputEntered(i32 window_id) =|
|
||||
WindowInputLeft(i32 window_id) =|
|
||||
KeyDown(i32 window_id, u32 code_point, u32 key, u32 modifiers, u32 scancode) =|
|
||||
KeyUp(i32 window_id, u32 code_point, u32 key, u32 modifiers, u32 scancode) =|
|
||||
WindowActivated(i32 window_id) =|
|
||||
WindowDeactivated(i32 window_id) =|
|
||||
WindowStateChanged(i32 window_id, bool minimized, bool occluded) =|
|
||||
WindowCloseRequest(i32 window_id) =|
|
||||
WindowResized(i32 window_id, Gfx::IntRect new_rect) =|
|
||||
|
||||
MenuItemActivated(i32 menu_id, i32 identifier) =|
|
||||
|
||||
ScreenRectChanged(Gfx::IntRect rect) =|
|
||||
|
||||
WM_WindowRemoved(i32 wm_id, i32 client_id, i32 window_id) =|
|
||||
WM_WindowStateChanged(i32 wm_id, i32 client_id, i32 window_id, i32 parent_client_id, i32 parent_window_id, bool is_active, bool is_minimized, bool is_modal, bool is_frameless, i32 window_type, [UTF8] String title, Gfx::IntRect rect, i32 progress) =|
|
||||
WM_WindowIconBitmapChanged(i32 wm_id, i32 client_id, i32 window_id, i32 icon_buffer_id, Gfx::IntSize icon_size) =|
|
||||
WM_WindowRectChanged(i32 wm_id, i32 client_id, i32 window_id, Gfx::IntRect rect) =|
|
||||
|
||||
AsyncSetWallpaperFinished(bool success) =|
|
||||
|
||||
DragAccepted() =|
|
||||
DragCancelled() =|
|
||||
|
||||
DragDropped(i32 window_id, Gfx::IntPoint mouse_position, [UTF8] String text, HashMap<String,ByteBuffer> mime_data) =|
|
||||
|
||||
UpdateSystemTheme(i32 system_theme_buffer_id) =|
|
||||
|
||||
DisplayLinkNotification() =|
|
||||
|
||||
Ping() =|
|
||||
}
|
382
Userland/Services/WindowServer/WindowFrame.cpp
Normal file
382
Userland/Services/WindowServer/WindowFrame.cpp
Normal file
|
@ -0,0 +1,382 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "ClientConnection.h"
|
||||
#include <AK/Badge.h>
|
||||
#include <LibGfx/Font.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <LibGfx/StylePainter.h>
|
||||
#include <LibGfx/WindowTheme.h>
|
||||
#include <WindowServer/Button.h>
|
||||
#include <WindowServer/Compositor.h>
|
||||
#include <WindowServer/Event.h>
|
||||
#include <WindowServer/Window.h>
|
||||
#include <WindowServer/WindowFrame.h>
|
||||
#include <WindowServer/WindowManager.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
static Gfx::WindowTheme::WindowType to_theme_window_type(WindowType type)
|
||||
{
|
||||
switch (type) {
|
||||
case WindowType::Normal:
|
||||
return Gfx::WindowTheme::WindowType::Normal;
|
||||
case WindowType::Notification:
|
||||
return Gfx::WindowTheme::WindowType::Notification;
|
||||
default:
|
||||
return Gfx::WindowTheme::WindowType::Other;
|
||||
}
|
||||
}
|
||||
|
||||
static Gfx::Bitmap* s_minimize_icon;
|
||||
static Gfx::Bitmap* s_maximize_icon;
|
||||
static Gfx::Bitmap* s_restore_icon;
|
||||
static Gfx::Bitmap* s_close_icon;
|
||||
|
||||
static String s_last_title_button_icons_path;
|
||||
|
||||
WindowFrame::WindowFrame(Window& window)
|
||||
: m_window(window)
|
||||
{
|
||||
auto button = make<Button>(*this, [this](auto&) {
|
||||
m_window.request_close();
|
||||
});
|
||||
m_close_button = button.ptr();
|
||||
m_buttons.append(move(button));
|
||||
|
||||
if (window.is_resizable()) {
|
||||
auto button = make<Button>(*this, [this](auto&) {
|
||||
WindowManager::the().maximize_windows(m_window, !m_window.is_maximized());
|
||||
});
|
||||
m_maximize_button = button.ptr();
|
||||
m_buttons.append(move(button));
|
||||
}
|
||||
|
||||
if (window.is_minimizable()) {
|
||||
auto button = make<Button>(*this, [this](auto&) {
|
||||
WindowManager::the().minimize_windows(m_window, true);
|
||||
});
|
||||
m_minimize_button = button.ptr();
|
||||
m_buttons.append(move(button));
|
||||
}
|
||||
|
||||
set_button_icons();
|
||||
}
|
||||
|
||||
WindowFrame::~WindowFrame()
|
||||
{
|
||||
}
|
||||
|
||||
void WindowFrame::set_button_icons()
|
||||
{
|
||||
if (m_window.is_frameless())
|
||||
return;
|
||||
|
||||
String icons_path = WindowManager::the().palette().title_button_icons_path();
|
||||
|
||||
StringBuilder full_path;
|
||||
if (!s_minimize_icon || s_last_title_button_icons_path != icons_path) {
|
||||
full_path.append(icons_path);
|
||||
full_path.append("window-minimize.png");
|
||||
if (!(s_minimize_icon = Gfx::Bitmap::load_from_file(full_path.to_string()).leak_ref()))
|
||||
s_minimize_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/downward-triangle.png").leak_ref();
|
||||
full_path.clear();
|
||||
}
|
||||
if (!s_maximize_icon || s_last_title_button_icons_path != icons_path) {
|
||||
full_path.append(icons_path);
|
||||
full_path.append("window-maximize.png");
|
||||
if (!(s_maximize_icon = Gfx::Bitmap::load_from_file(full_path.to_string()).leak_ref()))
|
||||
s_maximize_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/upward-triangle.png").leak_ref();
|
||||
full_path.clear();
|
||||
}
|
||||
if (!s_restore_icon || s_last_title_button_icons_path != icons_path) {
|
||||
full_path.append(icons_path);
|
||||
full_path.append("window-restore.png");
|
||||
if (!(s_restore_icon = Gfx::Bitmap::load_from_file(full_path.to_string()).leak_ref()))
|
||||
s_restore_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-restore.png").leak_ref();
|
||||
full_path.clear();
|
||||
}
|
||||
if (!s_close_icon || s_last_title_button_icons_path != icons_path) {
|
||||
full_path.append(icons_path);
|
||||
full_path.append("window-close.png");
|
||||
if (!(s_close_icon = Gfx::Bitmap::load_from_file(full_path.to_string()).leak_ref()))
|
||||
s_close_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-close.png").leak_ref();
|
||||
full_path.clear();
|
||||
}
|
||||
|
||||
m_close_button->set_icon(*s_close_icon);
|
||||
if (m_window.is_minimizable())
|
||||
m_minimize_button->set_icon(*s_minimize_icon);
|
||||
if (m_window.is_resizable())
|
||||
m_maximize_button->set_icon(m_window.is_maximized() ? *s_restore_icon : *s_maximize_icon);
|
||||
|
||||
s_last_title_button_icons_path = icons_path;
|
||||
}
|
||||
|
||||
void WindowFrame::did_set_maximized(Badge<Window>, bool maximized)
|
||||
{
|
||||
ASSERT(m_maximize_button);
|
||||
m_maximize_button->set_icon(maximized ? *s_restore_icon : *s_maximize_icon);
|
||||
}
|
||||
|
||||
Gfx::IntRect WindowFrame::title_bar_rect() const
|
||||
{
|
||||
return Gfx::WindowTheme::current().title_bar_rect(to_theme_window_type(m_window.type()), m_window.rect(), WindowManager::the().palette());
|
||||
}
|
||||
|
||||
Gfx::IntRect WindowFrame::title_bar_icon_rect() const
|
||||
{
|
||||
return Gfx::WindowTheme::current().title_bar_icon_rect(to_theme_window_type(m_window.type()), m_window.rect(), WindowManager::the().palette());
|
||||
}
|
||||
|
||||
Gfx::IntRect WindowFrame::title_bar_text_rect() const
|
||||
{
|
||||
return Gfx::WindowTheme::current().title_bar_text_rect(to_theme_window_type(m_window.type()), m_window.rect(), WindowManager::the().palette());
|
||||
}
|
||||
|
||||
Gfx::WindowTheme::WindowState WindowFrame::window_state_for_theme() const
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
|
||||
if (m_flash_timer && m_flash_timer->is_active())
|
||||
return m_flash_counter & 1 ? Gfx::WindowTheme::WindowState::Active : Gfx::WindowTheme::WindowState::Inactive;
|
||||
|
||||
if (&m_window == wm.m_highlight_window)
|
||||
return Gfx::WindowTheme::WindowState::Highlighted;
|
||||
if (&m_window == wm.m_move_window)
|
||||
return Gfx::WindowTheme::WindowState::Moving;
|
||||
if (wm.is_active_window_or_accessory(m_window))
|
||||
return Gfx::WindowTheme::WindowState::Active;
|
||||
return Gfx::WindowTheme::WindowState::Inactive;
|
||||
}
|
||||
|
||||
void WindowFrame::paint_notification_frame(Gfx::Painter& painter)
|
||||
{
|
||||
auto palette = WindowManager::the().palette();
|
||||
Gfx::WindowTheme::current().paint_notification_frame(painter, m_window.rect(), palette, m_buttons.last().relative_rect());
|
||||
}
|
||||
|
||||
void WindowFrame::paint_normal_frame(Gfx::Painter& painter)
|
||||
{
|
||||
auto palette = WindowManager::the().palette();
|
||||
auto& window = m_window;
|
||||
String title_text;
|
||||
if (window.client() && window.client()->is_unresponsive()) {
|
||||
StringBuilder builder;
|
||||
builder.append(window.title());
|
||||
builder.append(" (Not responding)");
|
||||
title_text = builder.to_string();
|
||||
} else {
|
||||
title_text = window.title();
|
||||
}
|
||||
|
||||
auto leftmost_button_rect = m_buttons.is_empty() ? Gfx::IntRect() : m_buttons.last().relative_rect();
|
||||
Gfx::WindowTheme::current().paint_normal_frame(painter, window_state_for_theme(), m_window.rect(), title_text, m_window.icon(), palette, leftmost_button_rect);
|
||||
}
|
||||
|
||||
void WindowFrame::paint(Gfx::Painter& painter)
|
||||
{
|
||||
if (m_window.is_frameless())
|
||||
return;
|
||||
|
||||
Gfx::PainterStateSaver saver(painter);
|
||||
painter.translate(rect().location());
|
||||
|
||||
if (m_window.type() == WindowType::Notification)
|
||||
paint_notification_frame(painter);
|
||||
else if (m_window.type() == WindowType::Normal)
|
||||
paint_normal_frame(painter);
|
||||
else
|
||||
return;
|
||||
|
||||
for (auto& button : m_buttons) {
|
||||
button.paint(painter);
|
||||
}
|
||||
}
|
||||
|
||||
static Gfx::IntRect frame_rect_for_window(Window& window, const Gfx::IntRect& rect)
|
||||
{
|
||||
if (window.is_frameless())
|
||||
return rect;
|
||||
return Gfx::WindowTheme::current().frame_rect_for_window(to_theme_window_type(window.type()), rect, WindowManager::the().palette());
|
||||
}
|
||||
|
||||
static Gfx::IntRect frame_rect_for_window(Window& window)
|
||||
{
|
||||
return frame_rect_for_window(window, window.rect());
|
||||
}
|
||||
|
||||
Gfx::IntRect WindowFrame::rect() const
|
||||
{
|
||||
return frame_rect_for_window(m_window);
|
||||
}
|
||||
|
||||
void WindowFrame::invalidate_title_bar()
|
||||
{
|
||||
invalidate(title_bar_rect());
|
||||
}
|
||||
|
||||
void WindowFrame::invalidate(Gfx::IntRect relative_rect)
|
||||
{
|
||||
auto frame_rect = rect();
|
||||
auto window_rect = m_window.rect();
|
||||
relative_rect.move_by(frame_rect.x() - window_rect.x(), frame_rect.y() - window_rect.y());
|
||||
m_window.invalidate(relative_rect, true);
|
||||
}
|
||||
|
||||
void WindowFrame::notify_window_rect_changed(const Gfx::IntRect& old_rect, const Gfx::IntRect& new_rect)
|
||||
{
|
||||
layout_buttons();
|
||||
|
||||
auto old_frame_rect = frame_rect_for_window(m_window, old_rect);
|
||||
auto& compositor = Compositor::the();
|
||||
for (auto& dirty : old_frame_rect.shatter(rect()))
|
||||
compositor.invalidate_screen(dirty);
|
||||
if (!m_window.is_opaque())
|
||||
compositor.invalidate_screen(rect());
|
||||
|
||||
compositor.invalidate_occlusions();
|
||||
|
||||
WindowManager::the().notify_rect_changed(m_window, old_rect, new_rect);
|
||||
}
|
||||
|
||||
void WindowFrame::layout_buttons()
|
||||
{
|
||||
auto button_rects = Gfx::WindowTheme::current().layout_buttons(to_theme_window_type(m_window.type()), m_window.rect(), WindowManager::the().palette(), m_buttons.size());
|
||||
for (size_t i = 0; i < m_buttons.size(); i++)
|
||||
m_buttons[i].set_relative_rect(button_rects[i]);
|
||||
}
|
||||
|
||||
void WindowFrame::on_mouse_event(const MouseEvent& event)
|
||||
{
|
||||
ASSERT(!m_window.is_fullscreen());
|
||||
|
||||
auto& wm = WindowManager::the();
|
||||
if (m_window.type() != WindowType::Normal && m_window.type() != WindowType::Notification)
|
||||
return;
|
||||
|
||||
if (m_window.type() == WindowType::Normal) {
|
||||
if (event.type() == Event::MouseDown)
|
||||
wm.move_to_front_and_make_active(m_window);
|
||||
|
||||
if (m_window.blocking_modal_window())
|
||||
return;
|
||||
|
||||
if (title_bar_icon_rect().contains(event.position())) {
|
||||
if (event.type() == Event::MouseDown && (event.button() == MouseButton::Left || event.button() == MouseButton::Right)) {
|
||||
// Manually start a potential double click. Since we're opening
|
||||
// a menu, we will only receive the MouseDown event, so we
|
||||
// need to record that fact. If the user subsequently clicks
|
||||
// on the same area, the menu will get closed, and we will
|
||||
// receive a MouseUp event, but because windows have changed
|
||||
// we don't get a MouseDoubleClick event. We can however record
|
||||
// this click, and when we receive the MouseUp event check if
|
||||
// it would have been considered a double click, if it weren't
|
||||
// for the fact that we opened and closed a window in the meanwhile
|
||||
auto& wm = WindowManager::the();
|
||||
wm.start_menu_doubleclick(m_window, event);
|
||||
|
||||
m_window.popup_window_menu(title_bar_rect().bottom_left().translated(rect().location()), WindowMenuDefaultAction::Close);
|
||||
return;
|
||||
} else if (event.type() == Event::MouseUp && event.button() == MouseButton::Left) {
|
||||
// Since the MouseDown event opened a menu, another MouseUp
|
||||
// from the second click outside the menu wouldn't be considered
|
||||
// a double click, so let's manually check if it would otherwise
|
||||
// have been be considered to be one
|
||||
auto& wm = WindowManager::the();
|
||||
if (wm.is_menu_doubleclick(m_window, event)) {
|
||||
// It is a double click, so perform activate the default item
|
||||
m_window.window_menu_activate_default();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is slightly hackish, but expand the title bar rect by two pixels downwards,
|
||||
// so that mouse events between the title bar and window contents don't act like
|
||||
// mouse events on the border.
|
||||
auto adjusted_title_bar_rect = title_bar_rect();
|
||||
adjusted_title_bar_rect.set_height(adjusted_title_bar_rect.height() + 2);
|
||||
|
||||
if (adjusted_title_bar_rect.contains(event.position())) {
|
||||
wm.clear_resize_candidate();
|
||||
|
||||
if (event.type() == Event::MouseDown)
|
||||
wm.move_to_front_and_make_active(m_window);
|
||||
|
||||
for (auto& button : m_buttons) {
|
||||
if (button.relative_rect().contains(event.position()))
|
||||
return button.on_mouse_event(event.translated(-button.relative_rect().location()));
|
||||
}
|
||||
if (event.type() == Event::MouseDown) {
|
||||
if (m_window.type() == WindowType::Normal && event.button() == MouseButton::Right) {
|
||||
auto default_action = m_window.is_maximized() ? WindowMenuDefaultAction::Restore : WindowMenuDefaultAction::Maximize;
|
||||
m_window.popup_window_menu(event.position().translated(rect().location()), default_action);
|
||||
return;
|
||||
}
|
||||
if (m_window.is_movable() && event.button() == MouseButton::Left)
|
||||
wm.start_window_move(m_window, event.translated(rect().location()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_window.is_resizable() && event.type() == Event::MouseMove && event.buttons() == 0) {
|
||||
constexpr ResizeDirection direction_for_hot_area[3][3] = {
|
||||
{ ResizeDirection::UpLeft, ResizeDirection::Up, ResizeDirection::UpRight },
|
||||
{ ResizeDirection::Left, ResizeDirection::None, ResizeDirection::Right },
|
||||
{ ResizeDirection::DownLeft, ResizeDirection::Down, ResizeDirection::DownRight },
|
||||
};
|
||||
Gfx::IntRect outer_rect = { {}, rect().size() };
|
||||
ASSERT(outer_rect.contains(event.position()));
|
||||
int window_relative_x = event.x() - outer_rect.x();
|
||||
int window_relative_y = event.y() - outer_rect.y();
|
||||
int hot_area_row = min(2, window_relative_y / (outer_rect.height() / 3));
|
||||
int hot_area_column = min(2, window_relative_x / (outer_rect.width() / 3));
|
||||
wm.set_resize_candidate(m_window, direction_for_hot_area[hot_area_row][hot_area_column]);
|
||||
Compositor::the().invalidate_cursor();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_window.is_resizable() && event.type() == Event::MouseDown && event.button() == MouseButton::Left)
|
||||
wm.start_window_resize(m_window, event.translated(rect().location()));
|
||||
}
|
||||
|
||||
void WindowFrame::start_flash_animation()
|
||||
{
|
||||
if (!m_flash_timer) {
|
||||
m_flash_timer = Core::Timer::construct(100, [this] {
|
||||
ASSERT(m_flash_counter);
|
||||
invalidate_title_bar();
|
||||
if (!--m_flash_counter)
|
||||
m_flash_timer->stop();
|
||||
});
|
||||
}
|
||||
m_flash_counter = 8;
|
||||
m_flash_timer->start();
|
||||
}
|
||||
|
||||
}
|
81
Userland/Services/WindowServer/WindowFrame.h
Normal file
81
Userland/Services/WindowServer/WindowFrame.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/NonnullOwnPtrVector.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <LibCore/Forward.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibGfx/WindowTheme.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
class Button;
|
||||
class MouseEvent;
|
||||
class Window;
|
||||
|
||||
class WindowFrame {
|
||||
public:
|
||||
WindowFrame(Window&);
|
||||
~WindowFrame();
|
||||
|
||||
Gfx::IntRect rect() const;
|
||||
void paint(Gfx::Painter&);
|
||||
void on_mouse_event(const MouseEvent&);
|
||||
void notify_window_rect_changed(const Gfx::IntRect& old_rect, const Gfx::IntRect& new_rect);
|
||||
void invalidate_title_bar();
|
||||
void invalidate(Gfx::IntRect relative_rect);
|
||||
|
||||
Gfx::IntRect title_bar_rect() const;
|
||||
Gfx::IntRect title_bar_icon_rect() const;
|
||||
Gfx::IntRect title_bar_text_rect() const;
|
||||
|
||||
void did_set_maximized(Badge<Window>, bool);
|
||||
|
||||
void layout_buttons();
|
||||
void set_button_icons();
|
||||
|
||||
void start_flash_animation();
|
||||
|
||||
private:
|
||||
void paint_notification_frame(Gfx::Painter&);
|
||||
void paint_normal_frame(Gfx::Painter&);
|
||||
|
||||
Gfx::WindowTheme::WindowState window_state_for_theme() const;
|
||||
|
||||
Window& m_window;
|
||||
NonnullOwnPtrVector<Button> m_buttons;
|
||||
Button* m_close_button { nullptr };
|
||||
Button* m_maximize_button { nullptr };
|
||||
Button* m_minimize_button { nullptr };
|
||||
|
||||
RefPtr<Core::Timer> m_flash_timer;
|
||||
size_t m_flash_counter { 0 };
|
||||
};
|
||||
|
||||
}
|
1509
Userland/Services/WindowServer/WindowManager.cpp
Normal file
1509
Userland/Services/WindowServer/WindowManager.cpp
Normal file
File diff suppressed because it is too large
Load diff
479
Userland/Services/WindowServer/WindowManager.h
Normal file
479
Userland/Services/WindowServer/WindowManager.h
Normal file
|
@ -0,0 +1,479 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/InlineLinkedList.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibCore/ConfigFile.h>
|
||||
#include <LibCore/ElapsedTimer.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/DisjointRectSet.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <LibGfx/Palette.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <WindowServer/Cursor.h>
|
||||
#include <WindowServer/Event.h>
|
||||
#include <WindowServer/MenuBar.h>
|
||||
#include <WindowServer/MenuManager.h>
|
||||
#include <WindowServer/Window.h>
|
||||
#include <WindowServer/WindowSwitcher.h>
|
||||
#include <WindowServer/WindowType.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
class Screen;
|
||||
class MouseEvent;
|
||||
class Window;
|
||||
class ClientConnection;
|
||||
class WindowSwitcher;
|
||||
class Button;
|
||||
|
||||
enum class ResizeDirection {
|
||||
None,
|
||||
Left,
|
||||
UpLeft,
|
||||
Up,
|
||||
UpRight,
|
||||
Right,
|
||||
DownRight,
|
||||
Down,
|
||||
DownLeft
|
||||
};
|
||||
|
||||
class WindowManager : public Core::Object {
|
||||
C_OBJECT(WindowManager)
|
||||
|
||||
friend class Compositor;
|
||||
friend class WindowFrame;
|
||||
friend class WindowSwitcher;
|
||||
|
||||
public:
|
||||
static WindowManager& the();
|
||||
|
||||
explicit WindowManager(const Gfx::PaletteImpl&);
|
||||
virtual ~WindowManager() override;
|
||||
|
||||
Palette palette() const { return Palette(*m_palette); }
|
||||
|
||||
RefPtr<Core::ConfigFile> config() const { return m_config; }
|
||||
void reload_config(bool);
|
||||
|
||||
void add_window(Window&);
|
||||
void remove_window(Window&);
|
||||
|
||||
void notify_title_changed(Window&);
|
||||
void notify_modal_unparented(Window&);
|
||||
void notify_rect_changed(Window&, const Gfx::IntRect& oldRect, const Gfx::IntRect& newRect);
|
||||
void notify_minimization_state_changed(Window&);
|
||||
void notify_opacity_changed(Window&);
|
||||
void notify_occlusion_state_changed(Window&);
|
||||
void notify_progress_changed(Window&);
|
||||
void notify_client_changed_app_menubar(ClientConnection&);
|
||||
|
||||
Gfx::IntRect maximized_window_rect(const Window&) const;
|
||||
|
||||
const ClientConnection* dnd_client() const { return m_dnd_client.ptr(); }
|
||||
const String& dnd_text() const { return m_dnd_text; }
|
||||
const Core::MimeData& dnd_mime_data() const { return *m_dnd_mime_data; }
|
||||
const Gfx::Bitmap* dnd_bitmap() const { return m_dnd_bitmap; }
|
||||
Gfx::IntRect dnd_rect() const;
|
||||
|
||||
void start_dnd_drag(ClientConnection&, const String& text, Gfx::Bitmap*, const Core::MimeData&);
|
||||
void end_dnd_drag();
|
||||
|
||||
Window* active_window() { return m_active_window.ptr(); }
|
||||
const Window* active_window() const { return m_active_window.ptr(); }
|
||||
Window* active_input_window() { return m_active_input_window.ptr(); }
|
||||
const Window* active_input_window() const { return m_active_input_window.ptr(); }
|
||||
const ClientConnection* active_client() const;
|
||||
|
||||
const Window* highlight_window() const { return m_highlight_window.ptr(); }
|
||||
void set_highlight_window(Window*);
|
||||
|
||||
void move_to_front_and_make_active(Window&);
|
||||
|
||||
Gfx::IntRect menubar_rect() const;
|
||||
Gfx::IntRect desktop_rect() const;
|
||||
|
||||
const Cursor& active_cursor() const;
|
||||
const Cursor& hidden_cursor() const { return *m_hidden_cursor; }
|
||||
const Cursor& arrow_cursor() const { return *m_arrow_cursor; }
|
||||
const Cursor& crosshair_cursor() const { return *m_crosshair_cursor; }
|
||||
const Cursor& hand_cursor() const { return *m_hand_cursor; }
|
||||
const Cursor& help_cursor() const { return *m_help_cursor; }
|
||||
const Cursor& resize_horizontally_cursor() const { return *m_resize_horizontally_cursor; }
|
||||
const Cursor& resize_vertically_cursor() const { return *m_resize_vertically_cursor; }
|
||||
const Cursor& resize_diagonally_tlbr_cursor() const { return *m_resize_diagonally_tlbr_cursor; }
|
||||
const Cursor& resize_diagonally_bltr_cursor() const { return *m_resize_diagonally_bltr_cursor; }
|
||||
const Cursor& resize_column_cursor() const { return *m_resize_column_cursor; }
|
||||
const Cursor& resize_row_cursor() const { return *m_resize_row_cursor; }
|
||||
const Cursor& i_beam_cursor() const { return *m_i_beam_cursor; }
|
||||
const Cursor& disallowed_cursor() const { return *m_disallowed_cursor; }
|
||||
const Cursor& move_cursor() const { return *m_move_cursor; }
|
||||
const Cursor& drag_cursor() const { return *m_drag_cursor; }
|
||||
const Cursor& wait_cursor() const { return *m_wait_cursor; }
|
||||
|
||||
const Gfx::Font& font() const;
|
||||
const Gfx::Font& window_title_font() const;
|
||||
|
||||
bool set_resolution(int width, int height);
|
||||
Gfx::IntSize resolution() const;
|
||||
|
||||
void set_acceleration_factor(double);
|
||||
void set_scroll_step_size(unsigned);
|
||||
|
||||
Window* set_active_input_window(Window*);
|
||||
void restore_active_input_window(Window*);
|
||||
void set_active_window(Window*, bool make_input = true);
|
||||
void set_hovered_button(Button*);
|
||||
|
||||
const Button* cursor_tracking_button() const { return m_cursor_tracking_button.ptr(); }
|
||||
void set_cursor_tracking_button(Button*);
|
||||
|
||||
void set_resize_candidate(Window&, ResizeDirection);
|
||||
void clear_resize_candidate();
|
||||
ResizeDirection resize_direction_of_window(const Window&);
|
||||
|
||||
void tell_wm_listeners_window_state_changed(Window&);
|
||||
void tell_wm_listeners_window_icon_changed(Window&);
|
||||
void tell_wm_listeners_window_rect_changed(Window&);
|
||||
|
||||
bool is_active_window_or_accessory(Window&) const;
|
||||
|
||||
void start_window_resize(Window&, const Gfx::IntPoint&, MouseButton);
|
||||
void start_window_resize(Window&, const MouseEvent&);
|
||||
|
||||
const Window* active_fullscreen_window() const
|
||||
{
|
||||
if (m_active_window && m_active_window->is_fullscreen()) {
|
||||
return m_active_window;
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
Window* active_fullscreen_window()
|
||||
{
|
||||
if (m_active_window && m_active_window->is_fullscreen()) {
|
||||
return m_active_window;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool update_theme(String theme_path, String theme_name);
|
||||
|
||||
void set_hovered_window(Window*);
|
||||
void deliver_mouse_event(Window& window, MouseEvent& event);
|
||||
|
||||
void did_popup_a_menu(Badge<Menu>);
|
||||
|
||||
void start_menu_doubleclick(Window& window, const MouseEvent& event);
|
||||
bool is_menu_doubleclick(Window& window, const MouseEvent& event) const;
|
||||
|
||||
void minimize_windows(Window&, bool);
|
||||
void maximize_windows(Window&, bool);
|
||||
|
||||
template<typename Function>
|
||||
IterationDecision for_each_window_in_modal_stack(Window& window, Function f)
|
||||
{
|
||||
auto* blocking_modal_window = window.blocking_modal_window();
|
||||
if (blocking_modal_window || window.is_modal()) {
|
||||
Vector<Window*> modal_stack;
|
||||
auto* modal_stack_top = blocking_modal_window ? blocking_modal_window : &window;
|
||||
for (auto* parent = modal_stack_top->parent_window(); parent; parent = parent->parent_window()) {
|
||||
auto* blocked_by = parent->blocking_modal_window();
|
||||
if (!blocked_by || (blocked_by != modal_stack_top && !modal_stack_top->is_descendant_of(*blocked_by)))
|
||||
break;
|
||||
modal_stack.append(parent);
|
||||
if (!parent->is_modal())
|
||||
break;
|
||||
}
|
||||
if (!modal_stack.is_empty()) {
|
||||
for (size_t i = modal_stack.size(); i > 0; i--) {
|
||||
IterationDecision decision = f(*modal_stack[i - 1], false);
|
||||
if (decision != IterationDecision::Continue)
|
||||
return decision;
|
||||
}
|
||||
}
|
||||
return f(*modal_stack_top, true);
|
||||
} else {
|
||||
// Not a modal window stack, just "iterate" over this window
|
||||
return f(window, true);
|
||||
}
|
||||
}
|
||||
|
||||
Gfx::IntPoint get_recommended_window_position(const Gfx::IntPoint& desired);
|
||||
|
||||
private:
|
||||
NonnullRefPtr<Cursor> get_cursor(const String& name);
|
||||
|
||||
void process_mouse_event(MouseEvent&, Window*& hovered_window);
|
||||
void process_event_for_doubleclick(Window& window, MouseEvent& event);
|
||||
bool process_ongoing_window_resize(const MouseEvent&, Window*& hovered_window);
|
||||
bool process_ongoing_window_move(MouseEvent&, Window*& hovered_window);
|
||||
bool process_ongoing_drag(MouseEvent&, Window*& hovered_window);
|
||||
void start_window_move(Window&, const MouseEvent&);
|
||||
template<typename Callback>
|
||||
IterationDecision for_each_visible_window_of_type_from_back_to_front(WindowType, Callback, bool ignore_highlight = false);
|
||||
template<typename Callback>
|
||||
IterationDecision for_each_visible_window_of_type_from_front_to_back(WindowType, Callback, bool ignore_highlight = false);
|
||||
template<typename Callback>
|
||||
IterationDecision for_each_visible_window_from_front_to_back(Callback);
|
||||
template<typename Callback>
|
||||
IterationDecision for_each_visible_window_from_back_to_front(Callback);
|
||||
template<typename Callback>
|
||||
void for_each_window_listening_to_wm_events(Callback);
|
||||
template<typename Callback>
|
||||
void for_each_window(Callback);
|
||||
template<typename Callback>
|
||||
IterationDecision for_each_window_of_type_from_front_to_back(WindowType, Callback, bool ignore_highlight = false);
|
||||
|
||||
virtual void event(Core::Event&) override;
|
||||
void paint_window_frame(const Window&);
|
||||
void tell_wm_listener_about_window(Window& listener, Window&);
|
||||
void tell_wm_listener_about_window_icon(Window& listener, Window&);
|
||||
void tell_wm_listener_about_window_rect(Window& listener, Window&);
|
||||
bool pick_new_active_window(Window*);
|
||||
|
||||
void do_move_to_front(Window&, bool, bool);
|
||||
|
||||
RefPtr<Cursor> m_hidden_cursor;
|
||||
RefPtr<Cursor> m_arrow_cursor;
|
||||
RefPtr<Cursor> m_hand_cursor;
|
||||
RefPtr<Cursor> m_help_cursor;
|
||||
RefPtr<Cursor> m_resize_horizontally_cursor;
|
||||
RefPtr<Cursor> m_resize_vertically_cursor;
|
||||
RefPtr<Cursor> m_resize_diagonally_tlbr_cursor;
|
||||
RefPtr<Cursor> m_resize_diagonally_bltr_cursor;
|
||||
RefPtr<Cursor> m_resize_column_cursor;
|
||||
RefPtr<Cursor> m_resize_row_cursor;
|
||||
RefPtr<Cursor> m_i_beam_cursor;
|
||||
RefPtr<Cursor> m_disallowed_cursor;
|
||||
RefPtr<Cursor> m_move_cursor;
|
||||
RefPtr<Cursor> m_drag_cursor;
|
||||
RefPtr<Cursor> m_wait_cursor;
|
||||
RefPtr<Cursor> m_crosshair_cursor;
|
||||
|
||||
InlineLinkedList<Window> m_windows_in_order;
|
||||
|
||||
struct DoubleClickInfo {
|
||||
struct ClickMetadata {
|
||||
Core::ElapsedTimer clock;
|
||||
Gfx::IntPoint last_position;
|
||||
};
|
||||
|
||||
const ClickMetadata& metadata_for_button(MouseButton) const;
|
||||
ClickMetadata& metadata_for_button(MouseButton);
|
||||
|
||||
void reset()
|
||||
{
|
||||
m_left = {};
|
||||
m_right = {};
|
||||
m_middle = {};
|
||||
m_back = {};
|
||||
m_forward = {};
|
||||
}
|
||||
|
||||
WeakPtr<Window> m_clicked_window;
|
||||
|
||||
private:
|
||||
ClickMetadata m_left;
|
||||
ClickMetadata m_right;
|
||||
ClickMetadata m_middle;
|
||||
ClickMetadata m_back;
|
||||
ClickMetadata m_forward;
|
||||
};
|
||||
|
||||
bool is_considered_doubleclick(const MouseEvent& event, const DoubleClickInfo::ClickMetadata& metadata) const;
|
||||
|
||||
DoubleClickInfo m_double_click_info;
|
||||
int m_double_click_speed { 0 };
|
||||
int m_max_distance_for_double_click { 4 };
|
||||
|
||||
WeakPtr<Window> m_active_window;
|
||||
WeakPtr<Window> m_hovered_window;
|
||||
WeakPtr<Window> m_highlight_window;
|
||||
WeakPtr<Window> m_active_input_window;
|
||||
WeakPtr<Window> m_active_input_tracking_window;
|
||||
|
||||
WeakPtr<Window> m_move_window;
|
||||
Gfx::IntPoint m_move_origin;
|
||||
Gfx::IntPoint m_move_window_origin;
|
||||
|
||||
WeakPtr<Window> m_resize_window;
|
||||
WeakPtr<Window> m_resize_candidate;
|
||||
MouseButton m_resizing_mouse_button { MouseButton::None };
|
||||
Gfx::IntRect m_resize_window_original_rect;
|
||||
Gfx::IntPoint m_resize_origin;
|
||||
ResizeDirection m_resize_direction { ResizeDirection::None };
|
||||
|
||||
u8 m_keyboard_modifiers { 0 };
|
||||
|
||||
WindowSwitcher m_switcher;
|
||||
|
||||
WeakPtr<Button> m_cursor_tracking_button;
|
||||
WeakPtr<Button> m_hovered_button;
|
||||
|
||||
NonnullRefPtr<Gfx::PaletteImpl> m_palette;
|
||||
|
||||
RefPtr<Core::ConfigFile> m_config;
|
||||
|
||||
WeakPtr<ClientConnection> m_dnd_client;
|
||||
String m_dnd_text;
|
||||
RefPtr<Core::MimeData> m_dnd_mime_data;
|
||||
RefPtr<Gfx::Bitmap> m_dnd_bitmap;
|
||||
};
|
||||
|
||||
template<typename Callback>
|
||||
IterationDecision WindowManager::for_each_visible_window_of_type_from_back_to_front(WindowType type, Callback callback, bool ignore_highlight)
|
||||
{
|
||||
bool do_highlight_window_at_end = false;
|
||||
for (auto& window : m_windows_in_order) {
|
||||
if (!window.is_visible())
|
||||
continue;
|
||||
if (window.is_minimized())
|
||||
continue;
|
||||
if (window.type() != type)
|
||||
continue;
|
||||
if (!ignore_highlight && m_highlight_window == &window) {
|
||||
do_highlight_window_at_end = true;
|
||||
continue;
|
||||
}
|
||||
if (callback(window) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
if (do_highlight_window_at_end) {
|
||||
if (callback(*m_highlight_window) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
IterationDecision WindowManager::for_each_visible_window_from_back_to_front(Callback callback)
|
||||
{
|
||||
if (for_each_visible_window_of_type_from_back_to_front(WindowType::Desktop, callback) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
if (for_each_visible_window_of_type_from_back_to_front(WindowType::Normal, callback) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
if (for_each_visible_window_of_type_from_back_to_front(WindowType::Taskbar, callback) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
if (for_each_visible_window_of_type_from_back_to_front(WindowType::Notification, callback) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
if (for_each_visible_window_of_type_from_back_to_front(WindowType::Tooltip, callback) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
if (for_each_visible_window_of_type_from_back_to_front(WindowType::Menubar, callback) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
if (for_each_visible_window_of_type_from_back_to_front(WindowType::Menu, callback) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
return for_each_visible_window_of_type_from_back_to_front(WindowType::WindowSwitcher, callback);
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
IterationDecision WindowManager::for_each_visible_window_of_type_from_front_to_back(WindowType type, Callback callback, bool ignore_highlight)
|
||||
{
|
||||
if (!ignore_highlight && m_highlight_window && m_highlight_window->type() == type && m_highlight_window->is_visible()) {
|
||||
if (callback(*m_highlight_window) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
|
||||
for (auto* window = m_windows_in_order.tail(); window; window = window->prev()) {
|
||||
if (!window->is_visible())
|
||||
continue;
|
||||
if (window->is_minimized())
|
||||
continue;
|
||||
if (window->type() != type)
|
||||
continue;
|
||||
if (!ignore_highlight && window == m_highlight_window)
|
||||
continue;
|
||||
if (callback(*window) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
IterationDecision WindowManager::for_each_visible_window_from_front_to_back(Callback callback)
|
||||
{
|
||||
if (for_each_visible_window_of_type_from_front_to_back(WindowType::WindowSwitcher, callback) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
if (for_each_visible_window_of_type_from_front_to_back(WindowType::Menu, callback) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
if (for_each_visible_window_of_type_from_front_to_back(WindowType::Menubar, callback) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
if (for_each_visible_window_of_type_from_front_to_back(WindowType::Tooltip, callback) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
if (for_each_visible_window_of_type_from_front_to_back(WindowType::Notification, callback) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
if (for_each_visible_window_of_type_from_front_to_back(WindowType::Taskbar, callback) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
if (for_each_visible_window_of_type_from_front_to_back(WindowType::Normal, callback) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
return for_each_visible_window_of_type_from_front_to_back(WindowType::Desktop, callback);
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
void WindowManager::for_each_window_listening_to_wm_events(Callback callback)
|
||||
{
|
||||
for (auto* window = m_windows_in_order.tail(); window; window = window->prev()) {
|
||||
if (!window->listens_to_wm_events())
|
||||
continue;
|
||||
if (callback(*window) == IterationDecision::Break)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
void WindowManager::for_each_window(Callback callback)
|
||||
{
|
||||
for (auto* window = m_windows_in_order.tail(); window; window = window->prev()) {
|
||||
if (callback(*window) == IterationDecision::Break)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
IterationDecision WindowManager::for_each_window_of_type_from_front_to_back(WindowType type, Callback callback, bool ignore_highlight)
|
||||
{
|
||||
if (!ignore_highlight && m_highlight_window && m_highlight_window->type() == type && m_highlight_window->is_visible()) {
|
||||
if (callback(*m_highlight_window) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
|
||||
for (auto* window = m_windows_in_order.tail(); window; window = window->prev()) {
|
||||
if (window->type() != type)
|
||||
continue;
|
||||
if (!ignore_highlight && window == m_highlight_window)
|
||||
continue;
|
||||
if (callback(*window) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
}
|
||||
|
||||
}
|
117
Userland/Services/WindowServer/WindowServer.ipc
Normal file
117
Userland/Services/WindowServer/WindowServer.ipc
Normal file
|
@ -0,0 +1,117 @@
|
|||
endpoint WindowServer = 2
|
||||
{
|
||||
Greet() => (i32 client_id, Gfx::IntRect screen_rect, i32 system_theme_buffer_id)
|
||||
|
||||
CreateMenubar() => (i32 menubar_id)
|
||||
DestroyMenubar(i32 menubar_id) => ()
|
||||
|
||||
CreateMenu([UTF8] String menu_title) => (i32 menu_id)
|
||||
DestroyMenu(i32 menu_id) => ()
|
||||
|
||||
AddMenuToMenubar(i32 menubar_id, i32 menu_id) => ()
|
||||
SetApplicationMenubar(i32 menubar_id) => ()
|
||||
|
||||
SetSystemMenu(i32 menu_id) => ()
|
||||
|
||||
AddMenuItem(
|
||||
i32 menu_id,
|
||||
i32 identifier,
|
||||
i32 submenu_id,
|
||||
[UTF8] String text,
|
||||
bool enabled,
|
||||
bool checkable,
|
||||
bool checked,
|
||||
bool is_default,
|
||||
[UTF8] String shortcut,
|
||||
i32 icon_buffer_id,
|
||||
bool exclusive) => ()
|
||||
|
||||
AddMenuSeparator(i32 menu_id) => ()
|
||||
|
||||
UpdateMenuItem(i32 menu_id, i32 identifier, i32 submenu_id, [UTF8] String text, bool enabled, bool checkable, bool checked, bool is_default, [UTF8] String shortcut) => ()
|
||||
|
||||
CreateWindow(
|
||||
Gfx::IntRect rect,
|
||||
bool auto_position,
|
||||
bool has_alpha_channel,
|
||||
bool modal,
|
||||
bool minimizable,
|
||||
bool resizable,
|
||||
bool fullscreen,
|
||||
bool frameless,
|
||||
bool accessory,
|
||||
float opacity,
|
||||
Gfx::IntSize base_size,
|
||||
Gfx::IntSize size_increment,
|
||||
Optional<Gfx::IntSize> resize_aspect_ratio,
|
||||
i32 type,
|
||||
[UTF8] String title,
|
||||
i32 parent_window_id) => (i32 window_id)
|
||||
|
||||
DestroyWindow(i32 window_id) => (Vector<i32> destroyed_window_ids)
|
||||
|
||||
SetWindowTitle(i32 window_id, [UTF8] String title) => ()
|
||||
GetWindowTitle(i32 window_id) => ([UTF8] String title)
|
||||
|
||||
SetWindowProgress(i32 window_id, i32 progress) =|
|
||||
|
||||
SetWindowRect(i32 window_id, Gfx::IntRect rect) => (Gfx::IntRect rect)
|
||||
GetWindowRect(i32 window_id) => (Gfx::IntRect rect)
|
||||
|
||||
GetWindowRectInMenubar(i32 window_id) => (Gfx::IntRect rect)
|
||||
|
||||
IsMaximized(i32 window_id) => (bool maximized)
|
||||
|
||||
InvalidateRect(i32 window_id, Vector<Gfx::IntRect> rects, bool ignore_occlusion) =|
|
||||
DidFinishPainting(i32 window_id, Vector<Gfx::IntRect> rects) =|
|
||||
|
||||
SetGlobalCursorTracking(i32 window_id, bool enabled) => ()
|
||||
SetWindowOpacity(i32 window_id, float opacity) => ()
|
||||
|
||||
SetWindowBackingStore(i32 window_id, i32 bpp, i32 pitch, i32 shbuf_id, bool has_alpha_channel, Gfx::IntSize size, bool flush_immediately) => ()
|
||||
|
||||
WM_SetActiveWindow(i32 client_id, i32 window_id) =|
|
||||
WM_SetWindowMinimized(i32 client_id, i32 window_id, bool minimized) =|
|
||||
WM_StartWindowResize(i32 client_id, i32 window_id) =|
|
||||
WM_PopupWindowMenu(i32 client_id, i32 window_id, Gfx::IntPoint screen_position) =|
|
||||
WM_SetWindowTaskbarRect(i32 client_id, i32 window_id, Gfx::IntRect rect) =|
|
||||
|
||||
SetWindowHasAlphaChannel(i32 window_id, bool has_alpha_channel) => ()
|
||||
MoveWindowToFront(i32 window_id) => ()
|
||||
SetFullscreen(i32 window_id, bool fullscreen) => ()
|
||||
PopupMenu(i32 menu_id, Gfx::IntPoint screen_position) => ()
|
||||
DismissMenu(i32 menu_id) => ()
|
||||
|
||||
AsyncSetWallpaper(String path) =|
|
||||
|
||||
SetBackgroundColor(String background_color) => ()
|
||||
SetWallpaperMode(String mode) => ()
|
||||
|
||||
SetResolution(Gfx::IntSize resolution) => (bool success, Gfx::IntSize resolution)
|
||||
SetWindowIconBitmap(i32 window_id, Gfx::ShareableBitmap icon) => ()
|
||||
|
||||
GetWallpaper() => (String path)
|
||||
SetWindowCursor(i32 window_id, i32 cursor_type) => ()
|
||||
SetWindowCustomCursor(i32 window_id, Gfx::ShareableBitmap cursor) => ()
|
||||
|
||||
StartDrag([UTF8] String text, HashMap<String,ByteBuffer> mime_data, i32 bitmap_id, Gfx::IntSize bitmap_size) => (bool started)
|
||||
|
||||
SetSystemTheme(String theme_path, [UTF8] String theme_name) => (bool success)
|
||||
GetSystemTheme() => ([UTF8] String theme_name)
|
||||
|
||||
SetWindowBaseSizeAndSizeIncrement(i32 window_id, Gfx::IntSize base_size, Gfx::IntSize size_increment) => ()
|
||||
SetWindowResizeAspectRatio(i32 window_id, Optional<Gfx::IntSize> resize_aspect_ratio) => ()
|
||||
|
||||
EnableDisplayLink() =|
|
||||
DisableDisplayLink() =|
|
||||
|
||||
GetGlobalCursorPosition() => (Gfx::IntPoint position)
|
||||
|
||||
SetMouseAcceleration(float factor) => ()
|
||||
GetMouseAcceleration() => (float factor)
|
||||
|
||||
SetScrollStepSize(u32 step_size) => ()
|
||||
GetScrollStepSize() => (u32 step_size)
|
||||
|
||||
Pong() =|
|
||||
}
|
257
Userland/Services/WindowServer/WindowSwitcher.cpp
Normal file
257
Userland/Services/WindowServer/WindowSwitcher.cpp
Normal file
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/Font.h>
|
||||
#include <LibGfx/StylePainter.h>
|
||||
#include <WindowServer/Compositor.h>
|
||||
#include <WindowServer/Event.h>
|
||||
#include <WindowServer/Screen.h>
|
||||
#include <WindowServer/WindowManager.h>
|
||||
#include <WindowServer/WindowSwitcher.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
static WindowSwitcher* s_the;
|
||||
|
||||
WindowSwitcher& WindowSwitcher::the()
|
||||
{
|
||||
ASSERT(s_the);
|
||||
return *s_the;
|
||||
}
|
||||
|
||||
WindowSwitcher::WindowSwitcher()
|
||||
{
|
||||
s_the = this;
|
||||
}
|
||||
|
||||
WindowSwitcher::~WindowSwitcher()
|
||||
{
|
||||
}
|
||||
|
||||
void WindowSwitcher::set_visible(bool visible)
|
||||
{
|
||||
if (m_visible == visible)
|
||||
return;
|
||||
m_visible = visible;
|
||||
Compositor::the().invalidate_occlusions();
|
||||
if (m_switcher_window)
|
||||
m_switcher_window->set_visible(visible);
|
||||
if (!m_visible)
|
||||
return;
|
||||
refresh();
|
||||
}
|
||||
|
||||
Window* WindowSwitcher::selected_window()
|
||||
{
|
||||
if (m_selected_index < 0 || m_selected_index >= static_cast<int>(m_windows.size()))
|
||||
return nullptr;
|
||||
return m_windows[m_selected_index].ptr();
|
||||
}
|
||||
|
||||
void WindowSwitcher::event(Core::Event& event)
|
||||
{
|
||||
if (!static_cast<Event&>(event).is_mouse_event())
|
||||
return;
|
||||
|
||||
auto& mouse_event = static_cast<MouseEvent&>(event);
|
||||
int new_hovered_index = -1;
|
||||
for (size_t i = 0; i < m_windows.size(); ++i) {
|
||||
auto item_rect = this->item_rect(i);
|
||||
if (item_rect.contains(mouse_event.position())) {
|
||||
new_hovered_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mouse_event.type() == Event::MouseMove) {
|
||||
if (m_hovered_index != new_hovered_index) {
|
||||
m_hovered_index = new_hovered_index;
|
||||
redraw();
|
||||
}
|
||||
}
|
||||
|
||||
if (new_hovered_index == -1)
|
||||
return;
|
||||
|
||||
if (mouse_event.type() == Event::MouseDown)
|
||||
select_window_at_index(new_hovered_index);
|
||||
|
||||
event.accept();
|
||||
}
|
||||
|
||||
void WindowSwitcher::on_key_event(const KeyEvent& event)
|
||||
{
|
||||
if (event.type() == Event::KeyUp) {
|
||||
if (event.key() == Key_Logo) {
|
||||
if (auto* window = selected_window()) {
|
||||
window->set_minimized(false);
|
||||
WindowManager::the().move_to_front_and_make_active(*window);
|
||||
}
|
||||
WindowManager::the().set_highlight_window(nullptr);
|
||||
hide();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key() == Key_LeftShift || event.key() == Key_RightShift)
|
||||
return;
|
||||
if (event.key() != Key_Tab) {
|
||||
WindowManager::the().set_highlight_window(nullptr);
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
ASSERT(!m_windows.is_empty());
|
||||
|
||||
int new_selected_index;
|
||||
|
||||
if (!event.shift()) {
|
||||
new_selected_index = (m_selected_index + 1) % static_cast<int>(m_windows.size());
|
||||
} else {
|
||||
new_selected_index = (m_selected_index - 1) % static_cast<int>(m_windows.size());
|
||||
if (new_selected_index < 0)
|
||||
new_selected_index = static_cast<int>(m_windows.size()) - 1;
|
||||
}
|
||||
ASSERT(new_selected_index < static_cast<int>(m_windows.size()));
|
||||
|
||||
select_window_at_index(new_selected_index);
|
||||
}
|
||||
|
||||
void WindowSwitcher::select_window(Window& window)
|
||||
{
|
||||
for (size_t i = 0; i < m_windows.size(); ++i) {
|
||||
if (m_windows.at(i) == &window) {
|
||||
select_window_at_index(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WindowSwitcher::select_window_at_index(int index)
|
||||
{
|
||||
m_selected_index = index;
|
||||
auto* highlight_window = m_windows.at(index).ptr();
|
||||
ASSERT(highlight_window);
|
||||
WindowManager::the().set_highlight_window(highlight_window);
|
||||
redraw();
|
||||
}
|
||||
|
||||
void WindowSwitcher::redraw()
|
||||
{
|
||||
draw();
|
||||
Compositor::the().invalidate_screen(m_rect);
|
||||
}
|
||||
|
||||
Gfx::IntRect WindowSwitcher::item_rect(int index) const
|
||||
{
|
||||
return {
|
||||
padding(),
|
||||
padding() + index * item_height(),
|
||||
m_rect.width() - padding() * 2,
|
||||
item_height()
|
||||
};
|
||||
}
|
||||
|
||||
void WindowSwitcher::draw()
|
||||
{
|
||||
auto palette = WindowManager::the().palette();
|
||||
Gfx::Painter painter(*m_switcher_window->backing_store());
|
||||
painter.fill_rect({ {}, m_rect.size() }, palette.window());
|
||||
painter.draw_rect({ {}, m_rect.size() }, palette.threed_shadow2());
|
||||
for (size_t index = 0; index < m_windows.size(); ++index) {
|
||||
auto& window = *m_windows.at(index);
|
||||
auto item_rect = this->item_rect(index);
|
||||
Color text_color;
|
||||
Color rect_text_color;
|
||||
if (static_cast<int>(index) == m_selected_index) {
|
||||
painter.fill_rect(item_rect, palette.selection());
|
||||
text_color = palette.selection_text();
|
||||
rect_text_color = palette.threed_shadow1();
|
||||
} else {
|
||||
if (static_cast<int>(index) == m_hovered_index)
|
||||
Gfx::StylePainter::paint_button(painter, item_rect, palette, Gfx::ButtonStyle::CoolBar, false, true);
|
||||
text_color = palette.window_text();
|
||||
rect_text_color = palette.threed_shadow2();
|
||||
}
|
||||
item_rect.shrink(item_padding(), 0);
|
||||
Gfx::IntRect thumbnail_rect = { item_rect.location().translated(0, 5), { thumbnail_width(), thumbnail_height() } };
|
||||
if (window.backing_store()) {
|
||||
painter.draw_scaled_bitmap(thumbnail_rect, *window.backing_store(), window.backing_store()->rect());
|
||||
Gfx::StylePainter::paint_frame(painter, thumbnail_rect.inflated(4, 4), palette, Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2);
|
||||
}
|
||||
Gfx::IntRect icon_rect = { thumbnail_rect.bottom_right().translated(-window.icon().width(), -window.icon().height()), { window.icon().width(), window.icon().height() } };
|
||||
painter.fill_rect(icon_rect, palette.window());
|
||||
painter.blit(icon_rect.location(), window.icon(), window.icon().rect());
|
||||
painter.draw_text(item_rect.translated(thumbnail_width() + 12, 0), window.title(), WindowManager::the().window_title_font(), Gfx::TextAlignment::CenterLeft, text_color);
|
||||
painter.draw_text(item_rect, window.rect().to_string(), Gfx::TextAlignment::CenterRight, rect_text_color);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowSwitcher::refresh()
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
const Window* selected_window = nullptr;
|
||||
if (m_selected_index > 0 && m_windows[m_selected_index])
|
||||
selected_window = m_windows[m_selected_index].ptr();
|
||||
if (!selected_window)
|
||||
selected_window = wm.highlight_window() ? wm.highlight_window() : wm.active_window();
|
||||
m_windows.clear();
|
||||
m_selected_index = 0;
|
||||
int window_count = 0;
|
||||
int longest_title_width = 0;
|
||||
wm.for_each_window_of_type_from_front_to_back(
|
||||
WindowType::Normal, [&](Window& window) {
|
||||
if (window.is_frameless())
|
||||
return IterationDecision::Continue;
|
||||
++window_count;
|
||||
longest_title_width = max(longest_title_width, wm.font().width(window.title()));
|
||||
if (selected_window == &window)
|
||||
m_selected_index = m_windows.size();
|
||||
m_windows.append(window);
|
||||
return IterationDecision::Continue;
|
||||
},
|
||||
true);
|
||||
if (m_windows.is_empty()) {
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
int space_for_window_rect = 180;
|
||||
m_rect.set_width(thumbnail_width() + longest_title_width + space_for_window_rect + padding() * 2 + item_padding() * 2);
|
||||
m_rect.set_height(window_count * item_height() + padding() * 2);
|
||||
m_rect.center_within(Screen::the().rect());
|
||||
if (!m_switcher_window)
|
||||
m_switcher_window = Window::construct(*this, WindowType::WindowSwitcher);
|
||||
m_switcher_window->set_rect(m_rect);
|
||||
redraw();
|
||||
}
|
||||
|
||||
void WindowSwitcher::refresh_if_needed()
|
||||
{
|
||||
if (m_visible)
|
||||
refresh();
|
||||
}
|
||||
|
||||
}
|
84
Userland/Services/WindowServer/WindowSwitcher.h
Normal file
84
Userland/Services/WindowServer/WindowSwitcher.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Vector.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibCore/Object.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
|
||||
namespace WindowServer {
|
||||
|
||||
class KeyEvent;
|
||||
class Window;
|
||||
|
||||
class WindowSwitcher final : public Core::Object {
|
||||
C_OBJECT(WindowSwitcher)
|
||||
public:
|
||||
static WindowSwitcher& the();
|
||||
|
||||
WindowSwitcher();
|
||||
virtual ~WindowSwitcher() override;
|
||||
|
||||
bool is_visible() const { return m_visible; }
|
||||
void set_visible(bool);
|
||||
|
||||
void show() { set_visible(true); }
|
||||
void hide() { set_visible(false); }
|
||||
|
||||
void on_key_event(const KeyEvent&);
|
||||
|
||||
void refresh();
|
||||
void refresh_if_needed();
|
||||
|
||||
void select_window(Window&);
|
||||
|
||||
private:
|
||||
int thumbnail_width() const { return 40; }
|
||||
int thumbnail_height() const { return 40; }
|
||||
int item_height() const { return 10 + thumbnail_height(); }
|
||||
int padding() const { return 8; }
|
||||
int item_padding() const { return 8; }
|
||||
|
||||
void draw();
|
||||
void redraw();
|
||||
void select_window_at_index(int index);
|
||||
Gfx::IntRect item_rect(int index) const;
|
||||
Window* selected_window();
|
||||
|
||||
virtual void event(Core::Event&) override;
|
||||
|
||||
RefPtr<Window> m_switcher_window;
|
||||
Gfx::IntRect m_rect;
|
||||
bool m_visible { false };
|
||||
Vector<WeakPtr<Window>> m_windows;
|
||||
int m_selected_index { 0 };
|
||||
int m_hovered_index { -1 };
|
||||
};
|
||||
|
||||
}
|
41
Userland/Services/WindowServer/WindowType.h
Normal file
41
Userland/Services/WindowServer/WindowType.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Keep this in sync with GUI::WindowType.
|
||||
enum class WindowType {
|
||||
Invalid = 0,
|
||||
Normal,
|
||||
Menu,
|
||||
WindowSwitcher,
|
||||
Taskbar,
|
||||
Tooltip,
|
||||
Menubar,
|
||||
MenuApplet,
|
||||
Notification,
|
||||
Desktop,
|
||||
};
|
119
Userland/Services/WindowServer/main.cpp
Normal file
119
Userland/Services/WindowServer/main.cpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "AppletManager.h"
|
||||
#include "Compositor.h"
|
||||
#include "EventLoop.h"
|
||||
#include "Screen.h"
|
||||
#include "WindowManager.h"
|
||||
#include <AK/SharedBuffer.h>
|
||||
#include <LibCore/ConfigFile.h>
|
||||
#include <LibGfx/Palette.h>
|
||||
#include <LibGfx/SystemTheme.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
if (pledge("stdio video thread shared_buffer accept rpath wpath cpath unix proc fattr sigaction", nullptr) < 0) {
|
||||
perror("pledge");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil("/res", "r") < 0) {
|
||||
perror("unveil /res");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil("/tmp", "cw") < 0) {
|
||||
perror("unveil /tmp cw");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil("/etc/WindowServer/WindowServer.ini", "rwc") < 0) {
|
||||
perror("unveil /etc/WindowServer/WindowServer.ini");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil("/dev", "rw") < 0) {
|
||||
perror("unveil /dev rw");
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct sigaction act;
|
||||
memset(&act, 0, sizeof(act));
|
||||
act.sa_flags = SA_NOCLDWAIT;
|
||||
act.sa_handler = SIG_IGN;
|
||||
int rc = sigaction(SIGCHLD, &act, nullptr);
|
||||
if (rc < 0) {
|
||||
perror("sigaction");
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto wm_config = Core::ConfigFile::open("/etc/WindowServer/WindowServer.ini");
|
||||
auto theme_name = wm_config->read_entry("Theme", "Name", "Default");
|
||||
|
||||
auto theme = Gfx::load_system_theme(String::format("/res/themes/%s.ini", theme_name.characters()));
|
||||
ASSERT(theme);
|
||||
Gfx::set_system_theme(*theme);
|
||||
auto palette = Gfx::PaletteImpl::create_with_shared_buffer(*theme);
|
||||
|
||||
WindowServer::EventLoop loop;
|
||||
|
||||
if (pledge("stdio video thread shared_buffer accept rpath wpath cpath proc", nullptr) < 0) {
|
||||
perror("pledge");
|
||||
return 1;
|
||||
}
|
||||
|
||||
WindowServer::Screen screen(wm_config->read_num_entry("Screen", "Width", 1024),
|
||||
wm_config->read_num_entry("Screen", "Height", 768));
|
||||
screen.set_acceleration_factor(atof(wm_config->read_entry("Mouse", "AccelerationFactor", "1.0").characters()));
|
||||
screen.set_scroll_step_size(wm_config->read_num_entry("Mouse", "ScrollStepSize", 4));
|
||||
WindowServer::Compositor::the();
|
||||
auto wm = WindowServer::WindowManager::construct(*palette);
|
||||
auto am = WindowServer::AppletManager::construct();
|
||||
auto mm = WindowServer::MenuManager::construct();
|
||||
|
||||
if (unveil("/tmp", "") < 0) {
|
||||
perror("unveil /tmp");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil("/dev", "") < 0) {
|
||||
perror("unveil /dev");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil(nullptr, nullptr) < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
|
||||
dbgln("Entering WindowServer main loop");
|
||||
loop.exec();
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue