diff options
Diffstat (limited to 'Servers/WindowServer')
28 files changed, 4458 insertions, 0 deletions
diff --git a/Servers/WindowServer/.gitignore b/Servers/WindowServer/.gitignore new file mode 100644 index 0000000000..7d04c54152 --- /dev/null +++ b/Servers/WindowServer/.gitignore @@ -0,0 +1,3 @@ +*.o +*.d +WindowServer diff --git a/Servers/WindowServer/Makefile b/Servers/WindowServer/Makefile new file mode 100644 index 0000000000..b09160983b --- /dev/null +++ b/Servers/WindowServer/Makefile @@ -0,0 +1,53 @@ +SHAREDGRAPHICS_OBJS = \ + ../../SharedGraphics/Painter.o \ + ../../SharedGraphics/Font.o \ + ../../SharedGraphics/Rect.o \ + ../../SharedGraphics/GraphicsBitmap.o \ + ../../SharedGraphics/CharacterBitmap.o \ + ../../SharedGraphics/DisjointRectSet.o \ + ../../SharedGraphics/Color.o + +WINDOWSERVER_OBJS = \ + WSMessageReceiver.o \ + WSMessageLoop.o \ + WSWindow.o \ + WSWindowManager.o \ + WSScreen.o \ + WSMenuBar.o \ + WSMenu.o \ + WSMenuItem.o \ + WSClientConnection.o \ + WSWindowSwitcher.o \ + WSClipboard.o \ + main.o + +APP = WindowServer +OBJS = $(SHAREDGRAPHICS_OBJS) $(WINDOWSERVER_OBJS) + +STANDARD_FLAGS = -std=c++17 +WARNING_FLAGS = -Wextra -Wall -Wundef -Wcast-qual -Wwrite-strings -Wimplicit-fallthrough +FLAVOR_FLAGS = -fno-exceptions -fno-rtti +OPTIMIZATION_FLAGS = -Os +INCLUDE_FLAGS = -I.. -I../.. -I. -I../../LibC +LDFLAGS = -L../../LibC + +DEFINES = -DSERENITY -DSANITIZE_PTRS -DUSERLAND + +CXXFLAGS = -MMD -MP $(WARNING_FLAGS) $(OPTIMIZATION_FLAGS) $(FLAVOR_FLAGS) $(STANDARD_FLAGS) $(INCLUDE_FLAGS) $(DEFINES) +CXX = i686-pc-serenity-g++ +LD = i686-pc-serenity-ld +AR = i686-pc-serenity-ar + +all: $(APP) + +$(APP): $(OBJS) + $(LD) -o $(APP) $(LDFLAGS) $(OBJS) -lc + +.cpp.o: + @echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $< + +-include $(OBJS:%.o=%.d) + +clean: + @echo "CLEAN"; rm -f $(APPS) $(OBJS) *.d + diff --git a/Servers/WindowServer/WSAPITypes.h b/Servers/WindowServer/WSAPITypes.h new file mode 100644 index 0000000000..69f33c9612 --- /dev/null +++ b/Servers/WindowServer/WSAPITypes.h @@ -0,0 +1,209 @@ +#pragma once + +#include <SharedGraphics/Color.h> +#include <SharedGraphics/Rect.h> + +typedef unsigned WSAPI_Color; + +struct WSAPI_Point { + int x; + int y; +}; + +struct WSAPI_Size { + int width; + int height; +}; + +struct WSAPI_Rect { + WSAPI_Point location; + WSAPI_Size size; +}; + +struct WSAPI_WindowParameters { + WSAPI_Rect rect; + Color background_color; + unsigned flags { 0 }; + char title[128]; +}; + +struct WSAPI_WindowBackingStoreInfo { + WSAPI_Size size; + size_t bpp; + size_t pitch; + RGBA32* pixels; +}; + +enum class WSAPI_MouseButton : unsigned char { + NoButton = 0, + Left = 1, + Right = 2, + Middle = 4, +}; + +struct WSAPI_KeyModifiers { enum { + Shift = 1 << 0, + Alt = 1 << 1, + Ctrl = 1 << 2, +}; }; + + +struct WSAPI_ServerMessage { + enum Type : unsigned { + Invalid, + Error, + Paint, + MouseMove, + MouseDown, + MouseUp, + WindowEntered, + WindowLeft, + KeyDown, + KeyUp, + WindowActivated, + WindowDeactivated, + WindowResized, + WindowCloseRequest, + MenuItemActivated, + DidCreateMenubar, + DidDestroyMenubar, + DidCreateMenu, + DidDestroyMenu, + DidAddMenuToMenubar, + DidSetApplicationMenubar, + DidAddMenuItem, + DidAddMenuSeparator, + DidCreateWindow, + DidDestroyWindow, + DidGetWindowTitle, + DidGetWindowRect, + DidGetWindowBackingStore, + Greeting, + DidGetClipboardContents, + DidSetClipboardContents, + DidSetWindowBackingStore, + }; + Type type { Invalid }; + int window_id { -1 }; + int text_length { 0 }; + char text[256]; + + union { + struct { + int server_pid; + } greeting; + struct { + WSAPI_Rect rect; + WSAPI_Rect old_rect; + } window; + struct { + WSAPI_Rect rect; + WSAPI_Size window_size; + } paint; + struct { + WSAPI_Point position; + WSAPI_MouseButton button; + unsigned buttons; + byte modifiers; + } mouse; + struct { + char character; + byte key; + byte modifiers; + bool ctrl : 1; + bool alt : 1; + bool shift : 1; + } key; + struct { + int menubar_id; + int menu_id; + unsigned identifier; + } menu; + struct { + WSAPI_Size size; + size_t bpp; + size_t pitch; + int shared_buffer_id; + bool has_alpha_channel; + } backing; + struct { + int shared_buffer_id; + int contents_size; + } clipboard; + }; +}; + +struct WSAPI_ClientMessage { + enum Type : unsigned { + Invalid, + CreateMenubar, + DestroyMenubar, + CreateMenu, + DestroyMenu, + AddMenuToMenubar, + SetApplicationMenubar, + AddMenuItem, + AddMenuSeparator, + CreateWindow, + DestroyWindow, + SetWindowTitle, + GetWindowTitle, + SetWindowRect, + GetWindowRect, + InvalidateRect, + DidFinishPainting, + GetWindowBackingStore, + SetGlobalCursorTracking, + SetWindowOpacity, + SetWindowBackingStore, + GetClipboardContents, + SetClipboardContents, + Greeting, + }; + Type type { Invalid }; + int window_id { -1 }; + int text_length { 0 }; + char text[256]; + int value { 0 }; + + union { + struct { + int client_pid; + } greeting; + struct { + int menubar_id; + int menu_id; + unsigned identifier; + char shortcut_text[32]; + int shortcut_text_length; + } menu; + struct { + WSAPI_Rect rect; + bool has_alpha_channel; + bool modal; + bool resizable; + float opacity; + WSAPI_Size base_size; + WSAPI_Size size_increment; + } window; + struct { + WSAPI_Size size; + size_t bpp; + size_t pitch; + int shared_buffer_id; + bool has_alpha_channel; + bool flush_immediately; + } backing; + struct { + int shared_buffer_id; + int contents_size; + } clipboard; + }; +}; + +inline Rect::Rect(const WSAPI_Rect& r) : Rect(r.location, r.size) { } +inline Point::Point(const WSAPI_Point& p) : Point(p.x, p.y) { } +inline Size::Size(const WSAPI_Size& s) : Size(s.width, s.height) { } +inline Rect::operator WSAPI_Rect() const { return { m_location, m_size }; } +inline Point::operator WSAPI_Point() const { return { m_x, m_y }; } +inline Size::operator WSAPI_Size() const { return { m_width, m_height }; } diff --git a/Servers/WindowServer/WSClientConnection.cpp b/Servers/WindowServer/WSClientConnection.cpp new file mode 100644 index 0000000000..b3b9c9ac51 --- /dev/null +++ b/Servers/WindowServer/WSClientConnection.cpp @@ -0,0 +1,544 @@ +#include <WindowServer/WSClientConnection.h> +#include <WindowServer/WSMessageLoop.h> +#include <WindowServer/WSMenuBar.h> +#include <WindowServer/WSMenu.h> +#include <WindowServer/WSMenuItem.h> +#include <WindowServer/WSWindow.h> +#include <WindowServer/WSWindowManager.h> +#include <WindowServer/WSAPITypes.h> +#include <WindowServer/WSClipboard.h> +#include <SharedBuffer.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <errno.h> +#include <stdio.h> + +HashMap<int, WSClientConnection*>* s_connections; + +void WSClientConnection::for_each_client(Function<void(WSClientConnection&)> callback) +{ + if (!s_connections) + return; + for (auto& it : *s_connections) { + callback(*it.value); + } +} + +WSClientConnection* WSClientConnection::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; +} + +WSClientConnection::WSClientConnection(int fd) + : m_fd(fd) +{ + static int s_next_client_id = 0; + m_client_id = ++s_next_client_id; + + int rc = ioctl(m_fd, 413, (int)&m_pid); + ASSERT(rc == 0); + + if (!s_connections) + s_connections = new HashMap<int, WSClientConnection*>; + s_connections->set(m_client_id, this); + + WSAPI_ServerMessage message; + message.type = WSAPI_ServerMessage::Type::Greeting; + message.greeting.server_pid = getpid(); + post_message(message); +} + +WSClientConnection::~WSClientConnection() +{ + s_connections->remove(m_client_id); + int rc = close(m_fd); + ASSERT(rc == 0); +} + +void WSClientConnection::post_error(const String& error_message) +{ + dbgprintf("WSClientConnection::post_error: client_id=%d: %s\n", m_client_id, error_message.characters()); + WSAPI_ServerMessage message; + message.type = WSAPI_ServerMessage::Type::Error; + ASSERT(error_message.length() < (ssize_t)sizeof(message.text)); + strcpy(message.text, error_message.characters()); + message.text_length = error_message.length(); + post_message(message); +} + +void WSClientConnection::post_message(const WSAPI_ServerMessage& message) +{ + int nwritten = write(m_fd, &message, sizeof(message)); + if (nwritten < 0) { + if (errno == EPIPE) { + dbgprintf("WSClientConnection::post_message: Disconnected from peer.\n"); + return; + } + perror("WSClientConnection::post_message write"); + ASSERT_NOT_REACHED(); + } + + ASSERT(nwritten == sizeof(message)); +} + +RetainPtr<GraphicsBitmap> WSClientConnection::create_shared_bitmap(GraphicsBitmap::Format format, const Size& size) +{ + auto shared_buffer = SharedBuffer::create(m_pid, size.area() * sizeof(RGBA32)); + ASSERT(shared_buffer); + return GraphicsBitmap::create_with_shared_buffer(format, *shared_buffer, size); +} + +void WSClientConnection::on_message(WSMessage& message) +{ + if (message.is_client_request()) { + on_request(static_cast<WSAPIClientRequest&>(message)); + return; + } + + if (message.type() == WSMessage::WM_ClientDisconnected) { + int client_id = static_cast<WSClientDisconnectedNotification&>(message).client_id(); + dbgprintf("WSClientConnection: Client disconnected: %d\n", client_id); + delete this; + return; + } +} + +void WSClientConnection::handle_request(WSAPICreateMenubarRequest&) +{ + int menubar_id = m_next_menubar_id++; + auto menubar = make<WSMenuBar>(*this, menubar_id); + m_menubars.set(menubar_id, move(menubar)); + WSAPI_ServerMessage response; + response.type = WSAPI_ServerMessage::Type::DidCreateMenubar; + response.menu.menubar_id = menubar_id; + post_message(response); +} + +void WSClientConnection::handle_request(WSAPIDestroyMenubarRequest& request) +{ + int menubar_id = request.menubar_id(); + auto it = m_menubars.find(menubar_id); + if (it == m_menubars.end()) { + post_error("Bad menubar ID"); + return; + } + auto& menubar = *(*it).value; + WSWindowManager::the().close_menubar(menubar); + m_menubars.remove(it); + WSAPI_ServerMessage response; + response.type = WSAPI_ServerMessage::Type::DidDestroyMenubar; + response.menu.menubar_id = menubar_id; + post_message(response); +} + +void WSClientConnection::handle_request(WSAPICreateMenuRequest& request) +{ + int menu_id = m_next_menu_id++; + auto menu = make<WSMenu>(this, menu_id, request.text()); + m_menus.set(menu_id, move(menu)); + WSAPI_ServerMessage response; + response.type = WSAPI_ServerMessage::Type::DidCreateMenu; + response.menu.menu_id = menu_id; + post_message(response); +} + +void WSClientConnection::handle_request(WSAPIDestroyMenuRequest& request) +{ + int menu_id = static_cast<WSAPIDestroyMenuRequest&>(request).menu_id(); + auto it = m_menus.find(menu_id); + if (it == m_menus.end()) { + post_error("Bad menu ID"); + return; + } + auto& menu = *(*it).value; + WSWindowManager::the().close_menu(menu); + m_menus.remove(it); + WSAPI_ServerMessage response; + response.type = WSAPI_ServerMessage::Type::DidDestroyMenu; + response.menu.menu_id = menu_id; + post_message(response); +} + +void WSClientConnection::handle_request(WSAPISetApplicationMenubarRequest& request) +{ + int menubar_id = request.menubar_id(); + auto it = m_menubars.find(menubar_id); + if (it == m_menubars.end()) { + post_error("Bad menubar ID"); + return; + } + auto& menubar = *(*it).value; + m_app_menubar = menubar.make_weak_ptr(); + WSWindowManager::the().notify_client_changed_app_menubar(*this); + WSAPI_ServerMessage response; + response.type = WSAPI_ServerMessage::Type::DidSetApplicationMenubar; + response.menu.menubar_id = menubar_id; + post_message(response); +} + +void WSClientConnection::handle_request(WSAPIAddMenuToMenubarRequest& request) +{ + int menubar_id = request.menubar_id(); + int menu_id = request.menu_id(); + auto it = m_menubars.find(menubar_id); + auto jt = m_menus.find(menu_id); + if (it == m_menubars.end()) { + post_error("Bad menubar ID"); + return; + } + if (jt == m_menus.end()) { + post_error("Bad menu ID"); + return; + } + auto& menubar = *(*it).value; + auto& menu = *(*jt).value; + menubar.add_menu(&menu); + WSAPI_ServerMessage response; + response.type = WSAPI_ServerMessage::Type::DidAddMenuToMenubar; + response.menu.menubar_id = menubar_id; + response.menu.menu_id = menu_id; + post_message(response); +} + +void WSClientConnection::handle_request(WSAPIAddMenuItemRequest& request) +{ + int menu_id = request.menu_id(); + unsigned identifier = request.identifier(); + auto it = m_menus.find(menu_id); + if (it == m_menus.end()) { + post_error("Bad menu ID"); + return; + } + auto& menu = *(*it).value; + menu.add_item(make<WSMenuItem>(identifier, request.text(), request.shortcut_text())); + WSAPI_ServerMessage response; + response.type = WSAPI_ServerMessage::Type::DidAddMenuItem; + response.menu.menu_id = menu_id; + response.menu.identifier = identifier; + post_message(response); +} + +void WSClientConnection::handle_request(WSAPIAddMenuSeparatorRequest& request) +{ + int menu_id = request.menu_id(); + auto it = m_menus.find(menu_id); + if (it == m_menus.end()) { + post_error("Bad menu ID"); + return; + } + auto& menu = *(*it).value; + menu.add_item(make<WSMenuItem>(WSMenuItem::Separator)); + WSAPI_ServerMessage response; + response.type = WSAPI_ServerMessage::Type::DidAddMenuSeparator; + response.menu.menu_id = menu_id; + post_message(response); +} + +void WSClientConnection::handle_request(WSAPISetWindowOpacityRequest& request) +{ + int window_id = request.window_id(); + auto it = m_windows.find(window_id); + if (it == m_windows.end()) { + post_error("Bad window ID"); + return; + } + auto& window = *(*it).value; + window.set_opacity(request.opacity()); +} + +void WSClientConnection::handle_request(WSAPISetWindowTitleRequest& request) +{ + int window_id = request.window_id(); + auto it = m_windows.find(window_id); + if (it == m_windows.end()) { + post_error("Bad window ID"); + return; + } + auto& window = *(*it).value; + window.set_title(request.title()); +} + +void WSClientConnection::handle_request(WSAPIGetWindowTitleRequest& request) +{ + int window_id = request.window_id(); + auto it = m_windows.find(window_id); + if (it == m_windows.end()) { + post_error("Bad window ID"); + return; + } + auto& window = *(*it).value; + WSAPI_ServerMessage response; + response.type = WSAPI_ServerMessage::Type::DidGetWindowTitle; + response.window_id = window.window_id(); + ASSERT(window.title().length() < (ssize_t)sizeof(response.text)); + strcpy(response.text, window.title().characters()); + response.text_length = window.title().length(); + post_message(response); +} + +void WSClientConnection::handle_request(WSAPISetWindowRectRequest& request) +{ + int window_id = request.window_id(); + auto it = m_windows.find(window_id); + if (it == m_windows.end()) { + post_error("Bad window ID"); + return; + } + auto& window = *(*it).value; + window.set_rect(request.rect()); +} + +void WSClientConnection::handle_request(WSAPIGetWindowRectRequest& request) +{ + int window_id = request.window_id(); + auto it = m_windows.find(window_id); + if (it == m_windows.end()) { + post_error("Bad window ID"); + return; + } + auto& window = *(*it).value; + WSAPI_ServerMessage response; + response.type = WSAPI_ServerMessage::Type::DidGetWindowRect; + response.window_id = window.window_id(); + response.window.rect = window.rect(); + post_message(response); +} + +void WSClientConnection::handle_request(WSAPISetClipboardContentsRequest& request) +{ + auto shared_buffer = SharedBuffer::create_from_shared_buffer_id(request.shared_buffer_id()); + if (!shared_buffer) { + post_error("Bad shared buffer ID"); + return; + } + WSClipboard::the().set_data(*shared_buffer, request.size()); + WSAPI_ServerMessage response; + response.type = WSAPI_ServerMessage::Type::DidSetClipboardContents; + response.clipboard.shared_buffer_id = shared_buffer->shared_buffer_id(); + post_message(response); +} + +void WSClientConnection::handle_request(WSAPIGetClipboardContentsRequest&) +{ + WSAPI_ServerMessage response; + response.type = WSAPI_ServerMessage::Type::DidGetClipboardContents; + response.clipboard.shared_buffer_id = -1; + response.clipboard.contents_size = 0; + if (WSClipboard::the().size()) { + // FIXME: Optimize case where an app is copy/pasting within itself. + // We can just reuse the SharedBuffer then, since it will have the same peer PID. + // It would be even nicer if a SharedBuffer could have an arbitrary number of clients.. + RetainPtr<SharedBuffer> shared_buffer = SharedBuffer::create(m_pid, WSClipboard::the().size()); + ASSERT(shared_buffer); + memcpy(shared_buffer->data(), WSClipboard::the().data(), WSClipboard::the().size()); + shared_buffer->seal(); + response.clipboard.shared_buffer_id = shared_buffer->shared_buffer_id(); + response.clipboard.contents_size = WSClipboard::the().size(); + + // FIXME: This is a workaround for the fact that SharedBuffers will go away if neither side is retaining them. + // After we respond to GetClipboardContents, we have to wait for the client to retain the buffer on his side. + m_last_sent_clipboard_content = move(shared_buffer); + } + post_message(response); +} + +void WSClientConnection::handle_request(WSAPICreateWindowRequest& request) +{ + int window_id = m_next_window_id++; + auto window = make<WSWindow>(*this, window_id, request.is_modal()); + window->set_has_alpha_channel(request.has_alpha_channel()); + window->set_resizable(request.is_resizable()); + window->set_title(request.title()); + window->set_rect(request.rect()); + window->set_opacity(request.opacity()); + window->set_size_increment(request.size_increment()); + window->set_base_size(request.base_size()); + window->invalidate(); + m_windows.set(window_id, move(window)); + WSAPI_ServerMessage response; + response.type = WSAPI_ServerMessage::Type::DidCreateWindow; + response.window_id = window_id; + post_message(response); +} + +void WSClientConnection::handle_request(WSAPIDestroyWindowRequest& request) +{ + int window_id = request.window_id(); + auto it = m_windows.find(window_id); + if (it == m_windows.end()) { + post_error("Bad window ID"); + return; + } + auto& window = *(*it).value; + WSWindowManager::the().invalidate(window); + m_windows.remove(it); +} + +void WSClientConnection::handle_request(WSAPIInvalidateRectRequest& request) +{ + int window_id = request.window_id(); + auto it = m_windows.find(window_id); + if (it == m_windows.end()) { + post_error("Bad window ID"); + return; + } + auto& window = *(*it).value; + WSAPI_ServerMessage response; + response.type = WSAPI_ServerMessage::Type::Paint; + response.window_id = window_id; + response.paint.rect = request.rect(); + response.paint.window_size = window.size(); + post_message(response); +} + +void WSClientConnection::handle_request(WSAPIDidFinishPaintingNotification& request) +{ + int window_id = request.window_id(); + auto it = m_windows.find(window_id); + if (it == m_windows.end()) { + post_error("Bad window ID"); + return; + } + auto& window = *(*it).value; + + if (!window.has_painted_since_last_resize()) { + if (window.last_lazy_resize_rect().size() == request.rect().size()) { + window.set_has_painted_since_last_resize(true); + WSMessageLoop::the().post_message(window, make<WSResizeEvent>(window.last_lazy_resize_rect(), window.rect())); + } + } + WSWindowManager::the().invalidate(window, request.rect()); +} + +void WSClientConnection::handle_request(WSAPIGetWindowBackingStoreRequest& request) +{ + int window_id = request.window_id(); + auto it = m_windows.find(window_id); + if (it == m_windows.end()) { + post_error("Bad window ID"); + return; + } + auto& window = *(*it).value; + auto* backing_store = window.backing_store(); + + WSAPI_ServerMessage response; + response.type = WSAPI_ServerMessage::Type::DidGetWindowBackingStore; + response.window_id = window_id; + response.backing.bpp = sizeof(RGBA32); + response.backing.pitch = backing_store->pitch(); + response.backing.size = backing_store->size(); + response.backing.has_alpha_channel = backing_store->has_alpha_channel(); + response.backing.shared_buffer_id = backing_store->shared_buffer_id(); + post_message(response); +} + +void WSClientConnection::handle_request(WSAPISetWindowBackingStoreRequest& request) +{ + int window_id = request.window_id(); + auto it = m_windows.find(window_id); + if (it == m_windows.end()) { + post_error("Bad window ID"); + return; + } + auto& window = *(*it).value; + if (window.last_backing_store() && window.last_backing_store()->shared_buffer_id() == request.shared_buffer_id()) { + window.swap_backing_stores(); + } else { + auto shared_buffer = SharedBuffer::create_from_shared_buffer_id(request.shared_buffer_id()); + if (!shared_buffer) + return; + auto backing_store = GraphicsBitmap::create_with_shared_buffer( + request.has_alpha_channel() ? GraphicsBitmap::Format::RGBA32 : GraphicsBitmap::Format::RGB32, + *shared_buffer, + request.size()); + window.set_backing_store(move(backing_store)); + } + + if (request.flush_immediately()) + window.invalidate(); + + WSAPI_ServerMessage response; + response.type = WSAPI_ServerMessage::Type::DidSetWindowBackingStore; + response.window_id = window_id; + response.backing.shared_buffer_id = request.shared_buffer_id(); + post_message(response); +} + +void WSClientConnection::handle_request(WSAPISetGlobalCursorTrackingRequest& request) +{ + int window_id = request.window_id(); + auto it = m_windows.find(window_id); + if (it == m_windows.end()) { + post_error("Bad window ID"); + return; + } + auto& window = *(*it).value; + window.set_global_cursor_tracking_enabled(request.value()); +} + +void WSClientConnection::on_request(WSAPIClientRequest& request) +{ + switch (request.type()) { + case WSMessage::APICreateMenubarRequest: + return handle_request(static_cast<WSAPICreateMenubarRequest&>(request)); + case WSMessage::APIDestroyMenubarRequest: + return handle_request(static_cast<WSAPIDestroyMenubarRequest&>(request)); + case WSMessage::APICreateMenuRequest: + return handle_request(static_cast<WSAPICreateMenuRequest&>(request)); + case WSMessage::APIDestroyMenuRequest: + return handle_request(static_cast<WSAPIDestroyMenuRequest&>(request)); + case WSMessage::APISetApplicationMenubarRequest: + return handle_request(static_cast<WSAPISetApplicationMenubarRequest&>(request)); + case WSMessage::APIAddMenuToMenubarRequest: + return handle_request(static_cast<WSAPIAddMenuToMenubarRequest&>(request)); + case WSMessage::APIAddMenuItemRequest: + return handle_request(static_cast<WSAPIAddMenuItemRequest&>(request)); + case WSMessage::APIAddMenuSeparatorRequest: + return handle_request(static_cast<WSAPIAddMenuSeparatorRequest&>(request)); + case WSMessage::APISetWindowTitleRequest: + return handle_request(static_cast<WSAPISetWindowTitleRequest&>(request)); + case WSMessage::APIGetWindowTitleRequest: + return handle_request(static_cast<WSAPIGetWindowTitleRequest&>(request)); + case WSMessage::APISetWindowRectRequest: + return handle_request(static_cast<WSAPISetWindowRectRequest&>(request)); + case WSMessage::APIGetWindowRectRequest: + return handle_request(static_cast<WSAPIGetWindowRectRequest&>(request)); + case WSMessage::APISetClipboardContentsRequest: + return handle_request(static_cast<WSAPISetClipboardContentsRequest&>(request)); + case WSMessage::APIGetClipboardContentsRequest: + return handle_request(static_cast<WSAPIGetClipboardContentsRequest&>(request)); + case WSMessage::APICreateWindowRequest: + return handle_request(static_cast<WSAPICreateWindowRequest&>(request)); + case WSMessage::APIDestroyWindowRequest: + return handle_request(static_cast<WSAPIDestroyWindowRequest&>(request)); + case WSMessage::APIInvalidateRectRequest: + return handle_request(static_cast<WSAPIInvalidateRectRequest&>(request)); + case WSMessage::APIDidFinishPaintingNotification: + return handle_request(static_cast<WSAPIDidFinishPaintingNotification&>(request)); + case WSMessage::APIGetWindowBackingStoreRequest: + return handle_request(static_cast<WSAPIGetWindowBackingStoreRequest&>(request)); + case WSMessage::APISetGlobalCursorTrackingRequest: + return handle_request(static_cast<WSAPISetGlobalCursorTrackingRequest&>(request)); + case WSMessage::APISetWindowOpacityRequest: + return handle_request(static_cast<WSAPISetWindowOpacityRequest&>(request)); + case WSMessage::APISetWindowBackingStoreRequest: + return handle_request(static_cast<WSAPISetWindowBackingStoreRequest&>(request)); + default: + break; + } +} + +bool WSClientConnection::is_showing_modal_window() const +{ + for (auto& it : m_windows) { + auto& window = *it.value; + if (window.is_visible() && window.is_modal()) + return true; + } + return false; +} diff --git a/Servers/WindowServer/WSClientConnection.h b/Servers/WindowServer/WSClientConnection.h new file mode 100644 index 0000000000..06e643ed37 --- /dev/null +++ b/Servers/WindowServer/WSClientConnection.h @@ -0,0 +1,101 @@ +#pragma once + +#include <AK/HashMap.h> +#include <AK/OwnPtr.h> +#include <AK/WeakPtr.h> +#include <AK/Function.h> +#include <SharedGraphics/GraphicsBitmap.h> +#include <WindowServer/WSMessageReceiver.h> +#include <WindowServer/WSMessage.h> + +class WSWindow; +class WSMenu; +class WSMenuBar; +struct WSAPI_ServerMessage; + +class WSClientConnection final : public WSMessageReceiver { +public: + explicit WSClientConnection(int fd); + virtual ~WSClientConnection() override; + + static WSClientConnection* from_client_id(int client_id); + static void for_each_client(Function<void(WSClientConnection&)>); + + void post_message(const WSAPI_ServerMessage&); + RetainPtr<GraphicsBitmap> create_shared_bitmap(GraphicsBitmap::Format, const Size&); + + int client_id() const { return m_client_id; } + WSMenuBar* app_menubar() { return m_app_menubar.ptr(); } + + int fd() const { return m_fd; } + pid_t pid() const { return m_pid; } + + bool is_showing_modal_window() const; + + template<typename Matching, typename Callback> void for_each_window_matching(Matching, Callback); + template<typename Callback> void for_each_window(Callback); + +private: + virtual void on_message(WSMessage&) override; + + void on_request(WSAPIClientRequest&); + void handle_request(WSAPICreateMenubarRequest&); + void handle_request(WSAPIDestroyMenubarRequest&); + void handle_request(WSAPICreateMenuRequest&); + void handle_request(WSAPIDestroyMenuRequest&); + void handle_request(WSAPISetApplicationMenubarRequest&); + void handle_request(WSAPIAddMenuToMenubarRequest&); + void handle_request(WSAPIAddMenuItemRequest&); + void handle_request(WSAPIAddMenuSeparatorRequest&); + void handle_request(WSAPISetWindowTitleRequest&); + void handle_request(WSAPIGetWindowTitleRequest&); + void handle_request(WSAPISetWindowRectRequest&); + void handle_request(WSAPIGetWindowRectRequest&); + void handle_request(WSAPISetClipboardContentsRequest&); + void handle_request(WSAPIGetClipboardContentsRequest&); + void handle_request(WSAPICreateWindowRequest&); + void handle_request(WSAPIDestroyWindowRequest&); + void handle_request(WSAPIInvalidateRectRequest&); + void handle_request(WSAPIDidFinishPaintingNotification&); + void handle_request(WSAPIGetWindowBackingStoreRequest&); + void handle_request(WSAPISetWindowBackingStoreRequest&); + void handle_request(WSAPISetGlobalCursorTrackingRequest&); + void handle_request(WSAPISetWindowOpacityRequest&); + + void post_error(const String&); + + int m_client_id { 0 }; + int m_fd { -1 }; + pid_t m_pid { 0 }; + + HashMap<int, OwnPtr<WSWindow>> m_windows; + HashMap<int, OwnPtr<WSMenuBar>> m_menubars; + HashMap<int, OwnPtr<WSMenu>> m_menus; + WeakPtr<WSMenuBar> m_app_menubar; + + int m_next_menubar_id { 10000 }; + int m_next_menu_id { 20000 }; + int m_next_window_id { 1982 }; + + RetainPtr<SharedBuffer> m_last_sent_clipboard_content; +}; + +template<typename Matching, typename Callback> +void WSClientConnection::for_each_window_matching(Matching matching, Callback callback) +{ + for (auto& it : m_windows) { + if (matching(*it.value)) { + if (callback(*it.value) == IterationDecision::Abort) + return; + } + } +} + +template<typename Callback> +void WSClientConnection::for_each_window(Callback callback) +{ + for (auto& it : m_windows) { + if (callback(*it.value) == IterationDecision::Abort) + return; + } +} diff --git a/Servers/WindowServer/WSClipboard.cpp b/Servers/WindowServer/WSClipboard.cpp new file mode 100644 index 0000000000..1a7f8f2ea1 --- /dev/null +++ b/Servers/WindowServer/WSClipboard.cpp @@ -0,0 +1,48 @@ +#include <WindowServer/WSClipboard.h> + +WSClipboard& WSClipboard::the() +{ + static WSClipboard* s_the; + if (!s_the) + s_the = new WSClipboard; + return *s_the; +} + +WSClipboard::WSClipboard() +{ +} + +WSClipboard::~WSClipboard() +{ +} + +void WSClipboard::on_message(WSMessage&) +{ +} + +const byte* WSClipboard::data() const +{ + if (!m_shared_buffer) + return nullptr; + return (const byte*)m_shared_buffer->data(); +} + +int WSClipboard::size() const +{ + if (!m_shared_buffer) + return 0; + return m_contents_size; +} + +void WSClipboard::clear() +{ + m_shared_buffer = nullptr; + m_contents_size = 0; +} + +void WSClipboard::set_data(Retained<SharedBuffer>&& data, int contents_size) +{ + dbgprintf("WSClipboard::set_data <- %p (%u bytes)\n", data->data(), contents_size); + m_shared_buffer = move(data); + m_contents_size = contents_size; +} diff --git a/Servers/WindowServer/WSClipboard.h b/Servers/WindowServer/WSClipboard.h new file mode 100644 index 0000000000..6c29bb8cd8 --- /dev/null +++ b/Servers/WindowServer/WSClipboard.h @@ -0,0 +1,29 @@ +#pragma once + +#include <AK/AKString.h> +#include <WindowServer/WSMessageReceiver.h> +#include <SharedBuffer.h> + +class WSClipboard final : public WSMessageReceiver { +public: + static WSClipboard& the(); + virtual ~WSClipboard() override; + + bool has_data() const + { + return m_shared_buffer; + } + + const byte* data() const; + int size() const; + + void clear(); + void set_data(Retained<SharedBuffer>&&, int contents_size); + +private: + WSClipboard(); + virtual void on_message(WSMessage&) override; + + RetainPtr<SharedBuffer> m_shared_buffer; + int m_contents_size { 0 }; +}; diff --git a/Servers/WindowServer/WSMenu.cpp b/Servers/WindowServer/WSMenu.cpp new file mode 100644 index 0000000000..b8b457122f --- /dev/null +++ b/Servers/WindowServer/WSMenu.cpp @@ -0,0 +1,168 @@ +#include "WSMenu.h" +#include "WSMenuItem.h" +#include "WSWindow.h" +#include "WSMessage.h" +#include "WSMessageLoop.h" +#include "WSWindowManager.h" +#include <WindowServer/WSAPITypes.h> +#include <WindowServer/WSClientConnection.h> +#include <SharedGraphics/Painter.h> +#include <SharedGraphics/Font.h> + +WSMenu::WSMenu(WSClientConnection* client, int menu_id, String&& name) + : m_client(client) + , m_menu_id(menu_id) + , m_name(move(name)) +{ +} + +WSMenu::~WSMenu() +{ +} + +const Font& WSMenu::font() const +{ + return Font::default_font(); +} + +int WSMenu::width() const +{ + int longest = 0; + for (auto& item : m_items) { + if (item->type() == WSMenuItem::Text) { + int item_width = font().width(item->text()); + if (!item->shortcut_text().is_empty()) + item_width += padding_between_text_and_shortcut() + font().width(item->shortcut_text()); + + longest = max(longest, item_width); + } + } + + return max(longest, rect_in_menubar().width()) + horizontal_padding(); +} + +int WSMenu::height() const +{ + if (m_items.is_empty()) + return 0; + return (m_items.last()->rect().bottom() - 1) + vertical_padding(); +} + +void WSMenu::redraw() +{ + ASSERT(menu_window()); + draw(); + menu_window()->invalidate(); +} + +WSWindow& WSMenu::ensure_menu_window() +{ + if (!m_menu_window) { + Point next_item_location(1, vertical_padding() / 2); + for (auto& item : m_items) { + int height = 0; + if (item->type() == WSMenuItem::Text) + height = item_height(); + else if (item->type() == WSMenuItem::Separator) + height = 7; + item->set_rect({ next_item_location, { width() - 2, height } }); + next_item_location.move_by(0, height); + } + + auto window = make<WSWindow>(*this, WSWindowType::Menu); + window->set_opacity(0.95f); + window->set_rect(0, 0, width(), height()); + m_menu_window = move(window); + draw(); + } + return *m_menu_window; +} + +void WSMenu::draw() +{ + ASSERT(menu_window()); + ASSERT(menu_window()->backing_store()); + Painter painter(*menu_window()->backing_store()); + + Rect rect { { }, menu_window()->size() }; + painter.draw_rect(rect, Color::White); + painter.fill_rect(rect.shrunken(2, 2), Color::LightGray); + + for (auto& item : m_items) { + if (item->type() == WSMenuItem::Text) { + Color text_color = Color::Black; + if (item.ptr() == m_hovered_item) { + painter.fill_rect(item->rect(), WSWindowManager::the().menu_selection_color()); + text_color = Color::White; + } + painter.draw_text(item->rect().translated(left_padding(), 0), item->text(), TextAlignment::CenterLeft, text_color); + if (!item->shortcut_text().is_empty()) { + painter.draw_text(item->rect().translated(-right_padding(), 0), item->shortcut_text(), TextAlignment::CenterRight, text_color); + } + } else if (item->type() == WSMenuItem::Separator) { + Point p1(1, item->rect().center().y()); + Point p2(width() - 2, item->rect().center().y()); + painter.draw_line(p1, p2, Color::MidGray); + } + } +} + +void WSMenu::on_message(WSMessage& message) +{ + ASSERT(menu_window()); + if (message.type() == WSMessage::MouseMove) { + auto* item = item_at(static_cast<WSMouseEvent&>(message).position()); + if (!item || m_hovered_item == item) + return; + m_hovered_item = item; + redraw(); + return; + } + + if (message.type() == WSMessage::MouseUp) { + if (!m_hovered_item) + return; + did_activate(*m_hovered_item); + clear_hovered_item(); + return; + } +} + +void WSMenu::clear_hovered_item() +{ + if (!m_hovered_item) + return; + m_hovered_item = nullptr; + redraw(); +} + +void WSMenu::did_activate(WSMenuItem& item) +{ + if (on_item_activation) + on_item_activation(item); + + close(); + + WSAPI_ServerMessage message; + message.type = WSAPI_ServerMessage::Type::MenuItemActivated; + message.menu.menu_id = m_menu_id; + message.menu.identifier = item.identifier(); + + if (m_client) + m_client->post_message(message); +} + +WSMenuItem* WSMenu::item_at(const Point& position) +{ + for (auto& item : m_items) { + if (!item->rect().contains(position)) + continue; + return item.ptr(); + } + return nullptr; +} + +void WSMenu::close() +{ + WSWindowManager::the().close_menu(*this); +}; diff --git a/Servers/WindowServer/WSMenu.h b/Servers/WindowServer/WSMenu.h new file mode 100644 index 0000000000..e7e6c34ef0 --- /dev/null +++ b/Servers/WindowServer/WSMenu.h @@ -0,0 +1,90 @@ +#pragma once + +#include <AK/AKString.h> +#include <AK/Vector.h> +#include <AK/WeakPtr.h> +#include <SharedGraphics/Rect.h> +#include <WindowServer/WSMenuItem.h> +#include <WindowServer/WSMessageReceiver.h> + +class WSClientConnection; +class WSMenuBar; +class WSMessage; +class WSWindow; +class Font; + +class WSMenu final : public WSMessageReceiver { +public: + WSMenu(WSClientConnection*, int menu_id, String&& name); + virtual ~WSMenu() override; + + WSClientConnection* client() { return m_client; } + const WSClientConnection* client() const { return m_client; } + int menu_id() const { return m_menu_id; } + + WSMenuBar* menu_bar() { return m_menubar; } + const WSMenuBar* menu_bar() const { return m_menubar; } + + bool is_empty() const { return m_items.is_empty(); } + int item_count() const { return m_items.size(); } + WSMenuItem* item(int i) { return m_items[i].ptr(); } + const WSMenuItem* item(int i) const { return m_items[i].ptr(); } + + void add_item(OwnPtr<WSMenuItem>&& 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); + } + + Rect text_rect_in_menubar() const { return m_text_rect_in_menubar; } + void set_text_rect_in_menubar(const Rect& rect) { m_text_rect_in_menubar = rect; } + + Rect rect_in_menubar() const { return m_rect_in_menubar; } + void set_rect_in_menubar(const Rect& rect) { m_rect_in_menubar = rect; } + + WSWindow* menu_window() { return m_menu_window.ptr(); } + WSWindow& ensure_menu_window(); + + int width() const; + int height() const; + + int item_height() const { return 18; } + int vertical_padding() const { return 4; } + int horizontal_padding() const { return left_padding() + right_padding(); } + int left_padding() const { return 14; } + int right_padding() const { return 14; } + + void draw(); + const Font& font() const; + + WSMenuItem* item_at(const Point&); + void redraw(); + + const WSMenuItem* hovered_item() const { return m_hovered_item; } + void clear_hovered_item(); + + Function<void(WSMenuItem&)> on_item_activation; + + void close(); + +private: + virtual void on_message(WSMessage&) override; + + int padding_between_text_and_shortcut() const { return 50; } + void did_activate(WSMenuItem&); + WSClientConnection* m_client { nullptr }; + int m_menu_id { 0 }; + String m_name; + Rect m_rect_in_menubar; + Rect m_text_rect_in_menubar; + WSMenuBar* m_menubar { nullptr }; + WSMenuItem* m_hovered_item { nullptr }; + Vector<OwnPtr<WSMenuItem>> m_items; + OwnPtr<WSWindow> m_menu_window; +}; + diff --git a/Servers/WindowServer/WSMenuBar.cpp b/Servers/WindowServer/WSMenuBar.cpp new file mode 100644 index 0000000000..6d642d160d --- /dev/null +++ b/Servers/WindowServer/WSMenuBar.cpp @@ -0,0 +1,14 @@ +#include "WSMenuBar.h" +#include "WSMenu.h" +#include "WSMenuItem.h" + +WSMenuBar::WSMenuBar(WSClientConnection& client, int menubar_id) + : m_client(client) + , m_menubar_id(menubar_id) +{ +} + +WSMenuBar::~WSMenuBar() +{ +} + diff --git a/Servers/WindowServer/WSMenuBar.h b/Servers/WindowServer/WSMenuBar.h new file mode 100644 index 0000000000..7854795ed5 --- /dev/null +++ b/Servers/WindowServer/WSMenuBar.h @@ -0,0 +1,31 @@ +#pragma once + +#include "WSMenu.h" +#include <AK/Vector.h> +#include <AK/Weakable.h> +#include <AK/WeakPtr.h> + +class WSMenuBar : public Weakable<WSMenuBar> { +public: + WSMenuBar(WSClientConnection& client, int menubar_id); + ~WSMenuBar(); + + WSClientConnection& client() { return m_client; } + const WSClientConnection& client() const { return m_client; } + int menubar_id() const { return m_menubar_id; } + void add_menu(WSMenu* menu) { m_menus.append(menu); } + + template<typename Callback> + void for_each_menu(Callback callback) + { + for (auto& menu : m_menus) { + if (!callback(*menu)) + return; + } + } + +private: + WSClientConnection& m_client; + int m_menubar_id { 0 }; + Vector<WSMenu*> m_menus; +}; diff --git a/Servers/WindowServer/WSMenuItem.cpp b/Servers/WindowServer/WSMenuItem.cpp new file mode 100644 index 0000000000..49041e0034 --- /dev/null +++ b/Servers/WindowServer/WSMenuItem.cpp @@ -0,0 +1,18 @@ +#include "WSMenuItem.h" + +WSMenuItem::WSMenuItem(unsigned identifier, const String& text, const String& shortcut_text) + : m_type(Text) + , m_identifier(identifier) + , m_text(text) + , m_shortcut_text(shortcut_text) +{ +} + +WSMenuItem::WSMenuItem(Type type) + : m_type(type) +{ +} + +WSMenuItem::~WSMenuItem() +{ +} diff --git a/Servers/WindowServer/WSMenuItem.h b/Servers/WindowServer/WSMenuItem.h new file mode 100644 index 0000000000..e3dce736f8 --- /dev/null +++ b/Servers/WindowServer/WSMenuItem.h @@ -0,0 +1,38 @@ +#pragma once + +#include <AK/AKString.h> +#include <AK/Function.h> +#include <SharedGraphics/Rect.h> + +class WSMenuItem { +public: + enum Type { + None, + Text, + Separator, + }; + + explicit WSMenuItem(unsigned identifier, const String& text, const String& shortcut_text = { }); + explicit WSMenuItem(Type); + ~WSMenuItem(); + + Type type() const { return m_type; } + bool enabled() const { return m_enabled; } + + String text() const { return m_text; } + String shortcut_text() const { return m_shortcut_text; } + + void set_rect(const Rect& rect) { m_rect = rect; } + Rect rect() const { return m_rect; } + + unsigned identifier() const { return m_identifier; } + +private: + Type m_type { None }; + bool m_enabled { true }; + unsigned m_identifier { 0 }; + String m_text; + String m_shortcut_text; + Rect m_rect; +}; + diff --git a/Servers/WindowServer/WSMessage.h b/Servers/WindowServer/WSMessage.h new file mode 100644 index 0000000000..1b2ec1018c --- /dev/null +++ b/Servers/WindowServer/WSMessage.h @@ -0,0 +1,563 @@ +#pragma once + +#include <SharedGraphics/Point.h> +#include <SharedGraphics/Rect.h> +#include <AK/AKString.h> +#include <AK/Types.h> +#include <Kernel/KeyCode.h> + +class WSMessage { +public: + enum Type { + Invalid = 0, + WM_DeferredCompose, + WM_ClientDisconnected, + MouseMove, + MouseDown, + MouseUp, + WindowEntered, + WindowLeft, + KeyDown, + KeyUp, + WindowActivated, + WindowDeactivated, + WindowCloseRequest, + WindowResized, + + __Begin_API_Client_Requests, + APICreateMenubarRequest, + APIDestroyMenubarRequest, + APIAddMenuToMenubarRequest, + APISetApplicationMenubarRequest, + APICreateMenuRequest, + APIDestroyMenuRequest, + APIAddMenuItemRequest, + APIAddMenuSeparatorRequest, + APICreateWindowRequest, + APIDestroyWindowRequest, + APISetWindowTitleRequest, + APIGetWindowTitleRequest, + APISetWindowRectRequest, + APIGetWindowRectRequest, + APIInvalidateRectRequest, + APIDidFinishPaintingNotification, + APIGetWindowBackingStoreRequest, + APISetGlobalCursorTrackingRequest, + APISetWindowOpacityRequest, + APISetWindowBackingStoreRequest, + APISetClipboardContentsRequest, + APIGetClipboardContentsRequest, + __End_API_Client_Requests, + }; + + WSMessage() { } + explicit WSMessage(Type type) : m_type(type) { } + virtual ~WSMessage() { } + + Type type() const { return m_type; } + + bool is_client_request() const { return m_type > __Begin_API_Client_Requests && m_type < __End_API_Client_Requests; } + bool is_mouse_event() const { return m_type == MouseMove || m_type == MouseDown || m_type == MouseUp; } + bool is_key_event() const { return m_type == KeyUp || m_type == KeyDown; } + +private: + Type m_type { Invalid }; +}; + +class WSClientDisconnectedNotification : public WSMessage { +public: + explicit WSClientDisconnectedNotification(int client_id) + : WSMessage(WM_ClientDisconnected) + , m_client_id(client_id) + { + } + + int client_id() const { return m_client_id; } + +private: + int m_client_id { 0 }; +}; + +class WSAPIClientRequest : public WSMessage { +public: + WSAPIClientRequest(Type type, int client_id) + : WSMessage(type) + , m_client_id(client_id) + { + } + + int client_id() const { return m_client_id; } + +private: + int m_client_id { 0 }; +}; + +class WSAPISetGlobalCursorTrackingRequest : public WSAPIClientRequest { +public: + WSAPISetGlobalCursorTrackingRequest(int client_id, int window_id, bool value) + : WSAPIClientRequest(WSMessage::APISetGlobalCursorTrackingRequest, client_id) + , m_window_id(window_id) + , m_value(value) + { + } + + int window_id() const { return m_window_id; } + bool value() const { return m_value; } + +private: + int m_window_id { 0 }; + bool m_value { false }; +}; + +class WSAPICreateMenubarRequest : public WSAPIClientRequest { +public: + WSAPICreateMenubarRequest(int client_id) + : WSAPIClientRequest(WSMessage::APICreateMenubarRequest, client_id) + { + } +}; + +class WSAPIDestroyMenubarRequest : public WSAPIClientRequest { +public: + WSAPIDestroyMenubarRequest(int client_id, int menubar_id) + : WSAPIClientRequest(WSMessage::APIDestroyMenubarRequest, client_id) + , m_menubar_id(menubar_id) + { + } + + int menubar_id() const { return m_menubar_id; } + +private: + int m_menubar_id { 0 }; +}; + +class WSAPISetApplicationMenubarRequest : public WSAPIClientRequest { +public: + WSAPISetApplicationMenubarRequest(int client_id, int menubar_id) + : WSAPIClientRequest(WSMessage::APISetApplicationMenubarRequest, client_id) + , m_menubar_id(menubar_id) + { + } + + int menubar_id() const { return m_menubar_id; } + +private: + int m_menubar_id { 0 }; +}; + +class WSAPIAddMenuToMenubarRequest : public WSAPIClientRequest { +public: + WSAPIAddMenuToMenubarRequest(int client_id, int menubar_id, int menu_id) + : WSAPIClientRequest(WSMessage::APIAddMenuToMenubarRequest, client_id) + , m_menubar_id(menubar_id) + , m_menu_id(menu_id) + { + } + + int menubar_id() const { return m_menubar_id; } + int menu_id() const { return m_menu_id; } + +private: + int m_menubar_id { 0 }; + int m_menu_id { 0 }; +}; + +class WSAPICreateMenuRequest : public WSAPIClientRequest { +public: + WSAPICreateMenuRequest(int client_id, const String& text) + : WSAPIClientRequest(WSMessage::APICreateMenuRequest, client_id) + , m_text(text) + { + } + + String text() const { return m_text; } + +private: + String m_text; +}; + +class WSAPIDestroyMenuRequest : public WSAPIClientRequest { +public: + WSAPIDestroyMenuRequest(int client_id, int menu_id) + : WSAPIClientRequest(WSMessage::APIDestroyMenuRequest, client_id) + , m_menu_id(menu_id) + { + } + + int menu_id() const { return m_menu_id; } + +private: + int m_menu_id { 0 }; +}; + +class WSAPIAddMenuItemRequest : public WSAPIClientRequest { +public: + WSAPIAddMenuItemRequest(int client_id, int menu_id, unsigned identifier, const String& text, const String& shortcut_text) + : WSAPIClientRequest(WSMessage::APIAddMenuItemRequest, client_id) + , m_menu_id(menu_id) + , m_identifier(identifier) + , m_text(text) + , m_shortcut_text(shortcut_text) + { + } + + int menu_id() const { return m_menu_id; } + unsigned identifier() const { return m_identifier; } + String text() const { return m_text; } + String shortcut_text() const { return m_shortcut_text; } + +private: + int m_menu_id { 0 }; + unsigned m_identifier { 0 }; + String m_text; + String m_shortcut_text; +}; + +class WSAPIAddMenuSeparatorRequest : public WSAPIClientRequest { +public: + WSAPIAddMenuSeparatorRequest(int client_id, int menu_id) + : WSAPIClientRequest(WSMessage::APIAddMenuSeparatorRequest, client_id) + , m_menu_id(menu_id) + { + } + + int menu_id() const { return m_menu_id; } + +private: + int m_menu_id { 0 }; +}; + +class WSAPISetWindowTitleRequest final : public WSAPIClientRequest { +public: + explicit WSAPISetWindowTitleRequest(int client_id, int window_id, String&& title) + : WSAPIClientRequest(WSMessage::APISetWindowTitleRequest, client_id) + , m_client_id(client_id) + , m_window_id(window_id) + , m_title(move(title)) + { + } + + int client_id() const { return m_client_id; } + int window_id() const { return m_window_id; } + String title() const { return m_title; } + +private: + int m_client_id { 0 }; + int m_window_id { 0 }; + String m_title; +}; + +class WSAPIGetWindowTitleRequest final : public WSAPIClientRequest { +public: + explicit WSAPIGetWindowTitleRequest(int client_id, int window_id) + : WSAPIClientRequest(WSMessage::APIGetWindowTitleRequest, client_id) + , m_client_id(client_id) + , m_window_id(window_id) + { + } + + int client_id() const { return m_client_id; } + int window_id() const { return m_window_id; } + +private: + int m_client_id { 0 }; + int m_window_id { 0 }; +}; + +class WSAPISetClipboardContentsRequest final : public WSAPIClientRequest { +public: + explicit WSAPISetClipboardContentsRequest(int client_id, int shared_buffer_id, int size) + : WSAPIClientRequest(WSMessage::APISetClipboardContentsRequest, client_id) + , m_client_id(client_id) + , m_shared_buffer_id(shared_buffer_id) + , m_size(size) + { + } + + int client_id() const { return m_client_id; } + int shared_buffer_id() const { return m_shared_buffer_id; } + int size() const { return m_size; } + +private: + int m_client_id { 0 }; + int m_shared_buffer_id { 0 }; + int m_size { 0 }; +}; + +class WSAPIGetClipboardContentsRequest final : public WSAPIClientRequest { +public: + explicit WSAPIGetClipboardContentsRequest(int client_id) + : WSAPIClientRequest(WSMessage::APIGetClipboardContentsRequest, client_id) + , m_client_id(client_id) + { + } + + int client_id() const { return m_client_id; } + +private: + int m_client_id { 0 }; +}; + +class WSAPISetWindowOpacityRequest final : public WSAPIClientRequest { +public: + explicit WSAPISetWindowOpacityRequest(int client_id, int window_id, float opacity) + : WSAPIClientRequest(WSMessage::APISetWindowOpacityRequest, client_id) + , m_client_id(client_id) + , m_window_id(window_id) + , m_opacity(opacity) + { + } + + int client_id() const { return m_client_id; } + int window_id() const { return m_window_id; } + float opacity() const { return m_opacity; } + +private: + int m_client_id { 0 }; + int m_window_id { 0 }; + float m_opacity { 0 }; +}; + +class WSAPISetWindowBackingStoreRequest final : public WSAPIClientRequest { +public: + explicit WSAPISetWindowBackingStoreRequest(int client_id, int window_id, int shared_buffer_id, const Size& size, size_t bpp, size_t pitch, bool has_alpha_channel, bool flush_immediately) + : WSAPIClientRequest(WSMessage::APISetWindowBackingStoreRequest, client_id) + , m_client_id(client_id) + , m_window_id(window_id) + , m_shared_buffer_id(shared_buffer_id) + , m_size(size) + , m_bpp(bpp) + , m_pitch(pitch) + , m_has_alpha_channel(has_alpha_channel) + , m_flush_immediately(flush_immediately) + { + } + + int client_id() const { return m_client_id; } + int window_id() const { return m_window_id; } + int shared_buffer_id() const { return m_shared_buffer_id; } + Size size() const { return m_size; } + size_t bpp() const { return m_bpp; } + size_t pitch() const { return m_pitch; } + bool has_alpha_channel() const { return m_has_alpha_channel; } + bool flush_immediately() const { return m_flush_immediately; } + +private: + int m_client_id { 0 }; + int m_window_id { 0 }; + int m_shared_buffer_id { 0 }; + Size m_size; + size_t m_bpp; + size_t m_pitch; + bool m_has_alpha_channel; + bool m_flush_immediately; +}; + +class WSAPISetWindowRectRequest final : public WSAPIClientRequest { +public: + explicit WSAPISetWindowRectRequest(int client_id, int window_id, const Rect& rect) + : WSAPIClientRequest(WSMessage::APISetWindowRectRequest, client_id) + , m_client_id(client_id) + , m_window_id(window_id) + , m_rect(rect) + { + } + + int client_id() const { return m_client_id; } + int window_id() const { return m_window_id; } + Rect rect() const { return m_rect; } + +private: + int m_client_id { 0 }; + int m_window_id { 0 }; + Rect m_rect; +}; + +class WSAPIGetWindowRectRequest final : public WSAPIClientRequest { +public: + explicit WSAPIGetWindowRectRequest(int client_id, int window_id) + : WSAPIClientRequest(WSMessage::APIGetWindowRectRequest, client_id) + , m_client_id(client_id) + , m_window_id(window_id) + { + } + + int client_id() const { return m_client_id; } + int window_id() const { return m_window_id; } + +private: + int m_client_id { 0 }; + int m_window_id { 0 }; +}; + +class WSAPICreateWindowRequest : public WSAPIClientRequest { +public: + WSAPICreateWindowRequest(int client_id, const Rect& rect, const String& title, bool has_alpha_channel, bool modal, bool resizable, float opacity, const Size& base_size, const Size& size_increment) + : WSAPIClientRequest(WSMessage::APICreateWindowRequest, client_id) + , m_rect(rect) + , m_title(title) + , m_opacity(opacity) + , m_has_alpha_channel(has_alpha_channel) + , m_modal(modal) + , m_resizable(resizable) + , m_size_increment(size_increment) + , m_base_size(base_size) + { + } + + Rect rect() const { return m_rect; } + String title() const { return m_title; } + bool has_alpha_channel() const { return m_has_alpha_channel; } + bool is_modal() const { return m_modal; } + bool is_resizable() const { return m_resizable; } + float opacity() const { return m_opacity; } + Size size_increment() const { return m_size_increment; } + Size base_size() const { return m_base_size; } + +private: + Rect m_rect; + String m_title; + float m_opacity { 0 }; + bool m_has_alpha_channel { false }; + bool m_modal { false }; + bool m_resizable { false }; + Size m_size_increment; + Size m_base_size; +}; + +class WSAPIDestroyWindowRequest : public WSAPIClientRequest { +public: + WSAPIDestroyWindowRequest(int client_id, int window_id) + : WSAPIClientRequest(WSMessage::APIDestroyWindowRequest, client_id) + , m_window_id(window_id) + { + } + + int window_id() const { return m_window_id; } + +private: + int m_window_id { 0 }; +}; + +class WSAPIInvalidateRectRequest final : public WSAPIClientRequest { +public: + explicit WSAPIInvalidateRectRequest(int client_id, int window_id, const Rect& rect) + : WSAPIClientRequest(WSMessage::APIInvalidateRectRequest, client_id) + , m_window_id(window_id) + , m_rect(rect) + { + } + + int window_id() const { return m_window_id; } + Rect rect() const { return m_rect; } + +private: + int m_window_id { 0 }; + Rect m_rect; +}; + +class WSAPIGetWindowBackingStoreRequest final : public WSAPIClientRequest { +public: + explicit WSAPIGetWindowBackingStoreRequest(int client_id, int window_id) + : WSAPIClientRequest(WSMessage::APIGetWindowBackingStoreRequest, client_id) + , m_window_id(window_id) + { + } + + int window_id() const { return m_window_id; } + +private: + int m_window_id { 0 }; +}; + +class WSAPIDidFinishPaintingNotification final : public WSAPIClientRequest { +public: + explicit WSAPIDidFinishPaintingNotification(int client_id, int window_id, const Rect& rect) + : WSAPIClientRequest(WSMessage::APIDidFinishPaintingNotification, client_id) + , m_window_id(window_id) + , m_rect(rect) + { + } + + int window_id() const { return m_window_id; } + Rect rect() const { return m_rect; } + +private: + int m_window_id { 0 }; + Rect m_rect; +}; + +enum class MouseButton : byte { + None = 0, + Left = 1, + Right = 2, + Middle = 4, +}; + +class WSKeyEvent final : public WSMessage { +public: + WSKeyEvent(Type type, int key, char character, byte modifiers) + : WSMessage(type) + , m_key(key) + , m_character(character) + , m_modifiers(modifiers) + { + } + + 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; } + byte modifiers() const { return m_modifiers; } + char character() const { return m_character; } + +private: + friend class WSMessageLoop; + friend class WSScreen; + int m_key { 0 }; + char m_character { 0 }; + byte m_modifiers { 0 }; +}; + +class WSMouseEvent final : public WSMessage { +public: + WSMouseEvent(Type type, const Point& position, unsigned buttons, MouseButton button, unsigned modifiers) + : WSMessage(type) + , m_position(position) + , m_buttons(buttons) + , m_button(button) + , m_modifiers(modifiers) + { + } + + Point 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; } + +private: + Point m_position; + unsigned m_buttons { 0 }; + MouseButton m_button { MouseButton::None }; + unsigned m_modifiers { 0 }; +}; + +class WSResizeEvent final : public WSMessage { +public: + WSResizeEvent(const Rect& old_rect, const Rect& rect) + : WSMessage(WSMessage::WindowResized) + , m_old_rect(old_rect) + , m_rect(rect) + { + } + + Rect old_rect() const { return m_old_rect; } + Rect rect() const { return m_rect; } + +private: + Rect m_old_rect; + Rect m_rect; +}; diff --git a/Servers/WindowServer/WSMessageLoop.cpp b/Servers/WindowServer/WSMessageLoop.cpp new file mode 100644 index 0000000000..a774317ab7 --- /dev/null +++ b/Servers/WindowServer/WSMessageLoop.cpp @@ -0,0 +1,332 @@ +#include <WindowServer/WSMessageLoop.h> +#include <WindowServer/WSMessage.h> +#include <WindowServer/WSMessageReceiver.h> +#include <WindowServer/WSWindowManager.h> +#include <WindowServer/WSScreen.h> +#include <WindowServer/WSClientConnection.h> +#include <WindowServer/WSAPITypes.h> +#include <Kernel/KeyCode.h> +#include <Kernel/MousePacket.h> +#include <LibC/sys/socket.h> +#include <LibC/sys/select.h> +#include <LibC/unistd.h> +#include <LibC/time.h> +#include <LibC/fcntl.h> +#include <LibC/stdio.h> +#include <LibC/errno.h> + +//#define WSMESSAGELOOP_DEBUG + +static WSMessageLoop* s_the; + +WSMessageLoop::WSMessageLoop() +{ + if (!s_the) + s_the = this; +} + +WSMessageLoop::~WSMessageLoop() +{ +} + +WSMessageLoop& WSMessageLoop::the() +{ + ASSERT(s_the); + return *s_the; +} + +int WSMessageLoop::exec() +{ + m_keyboard_fd = open("/dev/keyboard", O_RDONLY | O_NONBLOCK | O_CLOEXEC); + m_mouse_fd = open("/dev/psaux", O_RDONLY | O_NONBLOCK | O_CLOEXEC); + + unlink("/tmp/wsportal"); + + m_server_fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + ASSERT(m_server_fd >= 0); + sockaddr_un address; + address.sun_family = AF_LOCAL; + strcpy(address.sun_path, "/tmp/wsportal"); + int rc = bind(m_server_fd, (const sockaddr*)&address, sizeof(address)); + ASSERT(rc == 0); + rc = listen(m_server_fd, 5); + ASSERT(rc == 0); + + ASSERT(m_keyboard_fd >= 0); + ASSERT(m_mouse_fd >= 0); + + m_running = true; + for (;;) { + wait_for_message(); + Vector<QueuedMessage> messages = move(m_queued_messages); + + for (auto& queued_message : messages) { + auto* receiver = queued_message.receiver.ptr(); + auto& message = *queued_message.message; +#ifdef WSMESSAGELOOP_DEBUG + dbgprintf("WSMessageLoop: receiver{%p} message %u\n", receiver, (unsigned)message.type()); +#endif + if (receiver) + receiver->on_message(message); + } + } +} + +void WSMessageLoop::post_message(WSMessageReceiver& receiver, OwnPtr<WSMessage>&& message) +{ +#ifdef WSMESSAGELOOP_DEBUG + dbgprintf("WSMessageLoop::post_message: {%u} << receiver=%p, message=%p (type=%u)\n", m_queued_messages.size(), &receiver, message.ptr(), message->type()); +#endif + m_queued_messages.append({ receiver.make_weak_ptr(), move(message) }); +} + +void WSMessageLoop::Timer::reload() +{ + struct timeval now; + gettimeofday(&now, nullptr); + next_fire_time = { + now.tv_sec + (interval / 1000), + now.tv_usec + (interval % 1000) * 1000 + }; +} + +int WSMessageLoop::start_timer(int interval, Function<void()>&& callback) +{ + auto timer = make<Timer>(); + int timer_id = m_next_timer_id++; + timer->timer_id = timer_id; + timer->callback = move(callback); + timer->interval = interval; + timer->reload(); + m_timers.set(timer_id, move(timer)); + return timer_id; +} + +int WSMessageLoop::stop_timer(int timer_id) +{ + auto it = m_timers.find(timer_id); + if (it == m_timers.end()) + return -1; + m_timers.remove(it); + return 0; +} + +void WSMessageLoop::wait_for_message() +{ + fd_set rfds; + FD_ZERO(&rfds); + int max_fd = 0; + auto add_fd_to_set = [&max_fd] (int fd, auto& set) { + FD_SET(fd, &set); + if (fd > max_fd) + max_fd = fd; + }; + + add_fd_to_set(m_keyboard_fd, rfds); + add_fd_to_set(m_mouse_fd, rfds); + add_fd_to_set(m_server_fd, rfds); + + WSClientConnection::for_each_client([&] (WSClientConnection& client) { + add_fd_to_set(client.fd(), rfds); + }); + + struct timeval timeout = { 0, 0 }; + + if (m_queued_messages.is_empty()) { + bool had_any_timer = false; + for (auto& it : m_timers) { + auto& timer = *it.value; + if (!had_any_timer) { + timeout = timer.next_fire_time; + had_any_timer = true; + continue; + } + if (timer.next_fire_time.tv_sec > timeout.tv_sec || (timer.next_fire_time.tv_sec == timeout.tv_sec && timer.next_fire_time.tv_usec > timeout.tv_usec)) + timeout = timer.next_fire_time; + } + } + + int rc = select(max_fd + 1, &rfds, nullptr, nullptr, m_queued_messages.is_empty() && m_timers.is_empty() ? nullptr : &timeout); + if (rc < 0) { + ASSERT_NOT_REACHED(); + } + + struct timeval now; + gettimeofday(&now, nullptr); + for (auto& it : m_timers) { + auto& timer = *it.value; + if (now.tv_sec > timer.next_fire_time.tv_sec || (now.tv_sec == timer.next_fire_time.tv_sec && now.tv_usec > timer.next_fire_time.tv_usec)) { + timer.callback(); + timer.reload(); + } + } + + if (FD_ISSET(m_keyboard_fd, &rfds)) + drain_keyboard(); + if (FD_ISSET(m_mouse_fd, &rfds)) + drain_mouse(); + if (FD_ISSET(m_server_fd, &rfds)) { + sockaddr_un address; + socklen_t address_size = sizeof(address); + int client_fd = accept(m_server_fd, (sockaddr*)&address, &address_size); + if (client_fd < 0) { + dbgprintf("WindowServer: accept() failed: %s\n", strerror(errno)); + } else { + new WSClientConnection(client_fd); + } + } + WSClientConnection::for_each_client([&] (WSClientConnection& client) { + if (!FD_ISSET(client.fd(), &rfds)) + return; + unsigned messages_received = 0; + for (;;) { + WSAPI_ClientMessage message; + // FIXME: Don't go one message at a time, that's so much context switching, oof. + ssize_t nread = read(client.fd(), &message, sizeof(WSAPI_ClientMessage)); + if (nread == 0) { + if (!messages_received) + notify_client_disconnected(client.client_id()); + break; + } + if (nread < 0) { + perror("read"); + ASSERT_NOT_REACHED(); + } + on_receive_from_client(client.client_id(), message); + ++messages_received; + } + }); +} + +void WSMessageLoop::drain_mouse() +{ + auto& screen = WSScreen::the(); + unsigned prev_buttons = screen.mouse_button_state(); + int dx = 0; + int dy = 0; + unsigned buttons = prev_buttons; + for (;;) { + MousePacket packet; + ssize_t nread = read(m_mouse_fd, &packet, sizeof(MousePacket)); + if (nread == 0) + break; + ASSERT(nread == sizeof(packet)); + buttons = packet.buttons; + + dx += packet.dx; + dy += -packet.dy; + if (buttons != prev_buttons) { + screen.on_receive_mouse_data(dx, dy, buttons); + dx = 0; + dy = 0; + prev_buttons = buttons; + } + } + if (dx || dy) + screen.on_receive_mouse_data(dx, dy, buttons); +} + +void WSMessageLoop::drain_keyboard() +{ + auto& screen = WSScreen::the(); + for (;;) { + KeyEvent event; + ssize_t nread = read(m_keyboard_fd, (byte*)&event, sizeof(KeyEvent)); + if (nread == 0) + break; + ASSERT(nread == sizeof(KeyEvent)); + screen.on_receive_keyboard_data(event); + } +} + +void WSMessageLoop::notify_client_disconnected(int client_id) +{ + auto* client = WSClientConnection::from_client_id(client_id); + if (!client) + return; + post_message(*client, make<WSClientDisconnectedNotification>(client_id)); +} + +void WSMessageLoop::on_receive_from_client(int client_id, const WSAPI_ClientMessage& message) +{ +#if 0 + // FIXME: This should not be necessary.. why is this necessary? + while (!running()) + sched_yield(); +#endif + + WSClientConnection& client = *WSClientConnection::from_client_id(client_id); + switch (message.type) { + case WSAPI_ClientMessage::Type::CreateMenubar: + post_message(client, make<WSAPICreateMenubarRequest>(client_id)); + break; + case WSAPI_ClientMessage::Type::DestroyMenubar: + post_message(client, make<WSAPIDestroyMenubarRequest>(client_id, message.menu.menubar_id)); + break; + case WSAPI_ClientMessage::Type::SetApplicationMenubar: + post_message(client, make<WSAPISetApplicationMenubarRequest>(client_id, message.menu.menubar_id)); + break; + case WSAPI_ClientMessage::Type::AddMenuToMenubar: + post_message(client, make<WSAPIAddMenuToMenubarRequest>(client_id, message.menu.menubar_id, message.menu.menu_id)); + break; + case WSAPI_ClientMessage::Type::CreateMenu: + ASSERT(message.text_length < (ssize_t)sizeof(message.text)); + post_message(client, make<WSAPICreateMenuRequest>(client_id, String(message.text, message.text_length))); + break; + case WSAPI_ClientMessage::Type::DestroyMenu: + post_message(client, make<WSAPIDestroyMenuRequest>(client_id, message.menu.menu_id)); + break; + case WSAPI_ClientMessage::Type::AddMenuItem: + ASSERT(message.text_length < (ssize_t)sizeof(message.text)); + ASSERT(message.menu.shortcut_text_length < (ssize_t)sizeof(message.menu.shortcut_text)); + post_message(client, make<WSAPIAddMenuItemRequest>(client_id, message.menu.menu_id, message.menu.identifier, String(message.text, message.text_length), String(message.menu.shortcut_text, message.menu.shortcut_text_length))); + break; + case WSAPI_ClientMessage::Type::AddMenuSeparator: + post_message(client, make<WSAPIAddMenuSeparatorRequest>(client_id, message.menu.menu_id)); + break; + case WSAPI_ClientMessage::Type::CreateWindow: + ASSERT(message.text_length < (ssize_t)sizeof(message.text)); + post_message(client, make<WSAPICreateWindowRequest>(client_id, message.window.rect, String(message.text, message.text_length), message.window.has_alpha_channel, message.window.modal, message.window.resizable, message.window.opacity, message.window.base_size, message.window.size_increment)); + break; + case WSAPI_ClientMessage::Type::DestroyWindow: + post_message(client, make<WSAPIDestroyWindowRequest>(client_id, message.window_id)); + break; + case WSAPI_ClientMessage::Type::SetWindowTitle: + ASSERT(message.text_length < (ssize_t)sizeof(message.text)); + post_message(client, make<WSAPISetWindowTitleRequest>(client_id, message.window_id, String(message.text, message.text_length))); + break; + case WSAPI_ClientMessage::Type::GetWindowTitle: + ASSERT(message.text_length < (ssize_t)sizeof(message.text)); + post_message(client, make<WSAPIGetWindowTitleRequest>(client_id, message.window_id)); + break; + case WSAPI_ClientMessage::Type::SetWindowRect: + post_message(client, make<WSAPISetWindowRectRequest>(client_id, message.window_id, message.window.rect)); + break; + case WSAPI_ClientMessage::Type::GetWindowRect: + post_message(client, make<WSAPIGetWindowRectRequest>(client_id, message.window_id)); + break; + case WSAPI_ClientMessage::Type::SetClipboardContents: + post_message(client, make<WSAPISetClipboardContentsRequest>(client_id, message.clipboard.shared_buffer_id, message.clipboard.contents_size)); + break; + case WSAPI_ClientMessage::Type::GetClipboardContents: + post_message(client, make<WSAPIGetClipboardContentsRequest>(client_id)); + break; + case WSAPI_ClientMessage::Type::InvalidateRect: + post_message(client, make<WSAPIInvalidateRectRequest>(client_id, message.window_id, message.window.rect)); + break; + case WSAPI_ClientMessage::Type::DidFinishPainting: + post_message(client, make<WSAPIDidFinishPaintingNotification>(client_id, message.window_id, message.window.rect)); + break; + case WSAPI_ClientMessage::Type::GetWindowBackingStore: + post_message(client, make<WSAPIGetWindowBackingStoreRequest>(client_id, message.window_id)); + break; + case WSAPI_ClientMessage::Type::SetWindowBackingStore: + post_message(client, make<WSAPISetWindowBackingStoreRequest>(client_id, message.window_id, message.backing.shared_buffer_id, message.backing.size, message.backing.bpp, message.backing.pitch, message.backing.has_alpha_channel, message.backing.flush_immediately)); + break; + case WSAPI_ClientMessage::Type::SetGlobalCursorTracking: + post_message(client, make<WSAPISetGlobalCursorTrackingRequest>(client_id, message.window_id, message.value)); + break; + default: + break; + } +} diff --git a/Servers/WindowServer/WSMessageLoop.h b/Servers/WindowServer/WSMessageLoop.h new file mode 100644 index 0000000000..752cd0d60c --- /dev/null +++ b/Servers/WindowServer/WSMessageLoop.h @@ -0,0 +1,62 @@ +#pragma once + +#include "WSMessage.h" +#include <AK/HashMap.h> +#include <AK/OwnPtr.h> +#include <AK/Vector.h> +#include <AK/Function.h> +#include <AK/WeakPtr.h> + +class WSMessageReceiver; +struct WSAPI_ClientMessage; +struct WSAPI_ServerMessage; + +class WSMessageLoop { +public: + WSMessageLoop(); + ~WSMessageLoop(); + + int exec(); + + void post_message(WSMessageReceiver& receiver, OwnPtr<WSMessage>&&); + + static WSMessageLoop& the(); + + bool running() const { return m_running; } + + int start_timer(int ms, Function<void()>&&); + int stop_timer(int timer_id); + + void on_receive_from_client(int client_id, const WSAPI_ClientMessage&); + + void notify_client_disconnected(int client_id); + +private: + void wait_for_message(); + void drain_mouse(); + void drain_keyboard(); + + struct QueuedMessage { + WeakPtr<WSMessageReceiver> receiver; + OwnPtr<WSMessage> message; + }; + Vector<QueuedMessage> m_queued_messages; + + bool m_running { false }; + + int m_keyboard_fd { -1 }; + int m_mouse_fd { -1 }; + int m_server_fd { -1 }; + + struct Timer { + void reload(); + + int timer_id { 0 }; + int interval { 0 }; + struct timeval next_fire_time { 0, 0 }; + Function<void()> callback; + }; + + int m_next_timer_id { 1 }; + HashMap<int, OwnPtr<Timer>> m_timers; +}; diff --git a/Servers/WindowServer/WSMessageReceiver.cpp b/Servers/WindowServer/WSMessageReceiver.cpp new file mode 100644 index 0000000000..20508c3005 --- /dev/null +++ b/Servers/WindowServer/WSMessageReceiver.cpp @@ -0,0 +1,9 @@ +#include "WSMessageReceiver.h" + +WSMessageReceiver::WSMessageReceiver() +{ +} + +WSMessageReceiver::~WSMessageReceiver() +{ +} diff --git a/Servers/WindowServer/WSMessageReceiver.h b/Servers/WindowServer/WSMessageReceiver.h new file mode 100644 index 0000000000..97ee114c8c --- /dev/null +++ b/Servers/WindowServer/WSMessageReceiver.h @@ -0,0 +1,13 @@ +#pragma once + +#include <AK/Weakable.h> + +class WSMessage; + +class WSMessageReceiver : public Weakable<WSMessageReceiver> { +public: + WSMessageReceiver(); + virtual ~WSMessageReceiver(); + + virtual void on_message(WSMessage&) = 0; +}; diff --git a/Servers/WindowServer/WSScreen.cpp b/Servers/WindowServer/WSScreen.cpp new file mode 100644 index 0000000000..f491b014c6 --- /dev/null +++ b/Servers/WindowServer/WSScreen.cpp @@ -0,0 +1,98 @@ +#include "WSScreen.h" +#include "WSMessageLoop.h" +#include "WSMessage.h" +#include "WSWindowManager.h" +#include <unistd.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/mman.h> + +static WSScreen* s_the; + +WSScreen& WSScreen::the() +{ + ASSERT(s_the); + return *s_the; +} + +WSScreen::WSScreen(unsigned width, unsigned height) + : m_width(width) + , m_height(height) +{ + ASSERT(!s_the); + s_the = this; + m_cursor_location = rect().center(); + m_framebuffer_fd = open("/dev/bxvga", O_RDWR); + ASSERT(m_framebuffer_fd >= 0); + + set_resolution(width, height); +} + +WSScreen::~WSScreen() +{ +} + +void WSScreen::set_resolution(int width, int height) +{ + struct BXVGAResolution { + int width; + int height; + }; + BXVGAResolution resolution { (int)width, (int)height}; + int rc = ioctl(m_framebuffer_fd, 1985, (int)&resolution); + ASSERT(rc == 0); + + if (m_framebuffer) { + size_t previous_size_in_bytes = m_width * m_height * sizeof(RGBA32) * 2; + int rc = munmap(m_framebuffer, previous_size_in_bytes); + ASSERT(rc == 0); + } + + size_t framebuffer_size_in_bytes = width * height * sizeof(RGBA32) * 2; + m_framebuffer = (RGBA32*)mmap(nullptr, framebuffer_size_in_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, m_framebuffer_fd, 0); + ASSERT(m_framebuffer && m_framebuffer != (void*)-1); + + m_width = width; + m_height = height; + + m_cursor_location.constrain(rect()); +} + +void WSScreen::on_receive_mouse_data(int dx, int dy, unsigned buttons) +{ + auto prev_location = m_cursor_location; + m_cursor_location.move_by(dx, dy); + m_cursor_location.constrain(rect()); + 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<WSMouseEvent>(buttons & (unsigned)button ? WSMessage::MouseDown : WSMessage::MouseUp, m_cursor_location, buttons, button, m_modifiers); + WSMessageLoop::the().post_message(WSWindowManager::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); + if (m_cursor_location != prev_location) { + auto message = make<WSMouseEvent>(WSMessage::MouseMove, m_cursor_location, buttons, MouseButton::None, m_modifiers); + WSMessageLoop::the().post_message(WSWindowManager::the(), move(message)); + } + // NOTE: Invalidate the cursor if it moved, or if the left button changed state (for the cursor color inversion.) + if (m_cursor_location != prev_location || changed_buttons & (unsigned)MouseButton::Left) + WSWindowManager::the().invalidate_cursor(); +} + +void WSScreen::on_receive_keyboard_data(KeyEvent kernel_event) +{ + m_modifiers = kernel_event.modifiers(); + auto message = make<WSKeyEvent>(kernel_event.is_press() ? WSMessage::KeyDown : WSMessage::KeyUp, kernel_event.key, kernel_event.character, kernel_event.modifiers()); + WSMessageLoop::the().post_message(WSWindowManager::the(), move(message)); +} + +void WSScreen::set_y_offset(int offset) +{ + int rc = ioctl(m_framebuffer_fd, 1982, offset); + ASSERT(rc == 0); +} diff --git a/Servers/WindowServer/WSScreen.h b/Servers/WindowServer/WSScreen.h new file mode 100644 index 0000000000..0d71f816ac --- /dev/null +++ b/Servers/WindowServer/WSScreen.h @@ -0,0 +1,48 @@ +#pragma once + +#include <SharedGraphics/Rect.h> +#include <SharedGraphics/Size.h> +#include <SharedGraphics/Color.h> +#include <Kernel/KeyCode.h> + +class WSScreen { +public: + WSScreen(unsigned width, unsigned height); + ~WSScreen(); + + void set_resolution(int width, int height); + + int width() const { return m_width; } + int height() const { return m_height; } + RGBA32* scanline(int y); + + static WSScreen& the(); + + Size size() const { return { width(), height() }; } + Rect rect() const { return { 0, 0, width(), height() }; } + + void set_y_offset(int); + + Point cursor_location() const { return m_cursor_location; } + unsigned mouse_button_state() const { return m_mouse_button_state; } + + void on_receive_mouse_data(int dx, int dy, unsigned buttons); + void on_receive_keyboard_data(KeyEvent); + +private: + RGBA32* m_framebuffer { nullptr }; + + int m_width { 0 }; + int m_height { 0 }; + int m_framebuffer_fd { -1 }; + + Point m_cursor_location; + unsigned m_mouse_button_state { 0 }; + unsigned m_modifiers { 0 }; +}; + +inline RGBA32* WSScreen::scanline(int y) +{ + size_t pitch = sizeof(RGBA32) * width(); + return reinterpret_cast<RGBA32*>(((byte*)m_framebuffer) + (y * pitch)); +} diff --git a/Servers/WindowServer/WSWindow.cpp b/Servers/WindowServer/WSWindow.cpp new file mode 100644 index 0000000000..c2e1435efb --- /dev/null +++ b/Servers/WindowServer/WSWindow.cpp @@ -0,0 +1,180 @@ +#include "WSWindow.h" +#include "WSWindowManager.h" +#include "WSMessage.h" +#include "WSMessageLoop.h" +#include <WindowServer/WSAPITypes.h> +#include <WindowServer/WSClientConnection.h> + +static GraphicsBitmap& default_window_icon() +{ + static GraphicsBitmap* s_icon; + if (!s_icon) + s_icon = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/window16.rgb", { 16, 16 }).leak_ref(); + return *s_icon; +} + +WSWindow::WSWindow(WSMessageReceiver& internal_owner, WSWindowType type) + : m_internal_owner(&internal_owner) + , m_type(type) + , m_icon(default_window_icon()) +{ + WSWindowManager::the().add_window(*this); +} + +WSWindow::WSWindow(WSClientConnection& client, int window_id, bool modal) + : m_client(&client) + , m_type(WSWindowType::Normal) + , m_modal(modal) + , m_window_id(window_id) + , m_icon(default_window_icon()) +{ + WSWindowManager::the().add_window(*this); +} + +WSWindow::~WSWindow() +{ + WSWindowManager::the().remove_window(*this); +} + +void WSWindow::set_title(String&& title) +{ + if (m_title == title) + return; + m_title = move(title); + WSWindowManager::the().notify_title_changed(*this); +} + +void WSWindow::set_rect(const Rect& rect) +{ + Rect old_rect; + if (m_rect == rect) + return; + old_rect = m_rect; + m_rect = rect; + if (!m_client && (!m_backing_store || old_rect.size() != rect.size())) { + m_backing_store = GraphicsBitmap::create(GraphicsBitmap::Format::RGB32, m_rect.size()); + } + WSWindowManager::the().notify_rect_changed(*this, old_rect, rect); +} + +// FIXME: Just use the same types. +static WSAPI_MouseButton to_api(MouseButton button) +{ + switch (button) { + case MouseButton::None: return WSAPI_MouseButton::NoButton; + case MouseButton::Left: return WSAPI_MouseButton::Left; + case MouseButton::Right: return WSAPI_MouseButton::Right; + case MouseButton::Middle: return WSAPI_MouseButton::Middle; + } + ASSERT_NOT_REACHED(); +} + +void WSWindow::on_message(WSMessage& message) +{ + if (m_internal_owner) + return m_internal_owner->on_message(message); + + if (is_blocked_by_modal_window()) + return; + + WSAPI_ServerMessage server_message; + server_message.window_id = window_id(); + + switch (message.type()) { + case WSMessage::MouseMove: + server_message.type = WSAPI_ServerMessage::Type::MouseMove; + server_message.mouse.position = static_cast<WSMouseEvent&>(message).position(); + server_message.mouse.button = WSAPI_MouseButton::NoButton; + server_message.mouse.buttons = static_cast<WSMouseEvent&>(message).buttons(); + server_message.mouse.modifiers = static_cast<WSMouseEvent&>(message).modifiers(); + break; + case WSMessage::MouseDown: + server_message.type = WSAPI_ServerMessage::Type::MouseDown; + server_message.mouse.position = static_cast<WSMouseEvent&>(message).position(); + server_message.mouse.button = to_api(static_cast<WSMouseEvent&>(message).button()); + server_message.mouse.buttons = static_cast<WSMouseEvent&>(message).buttons(); + server_message.mouse.modifiers = static_cast<WSMouseEvent&>(message).modifiers(); + break; + case WSMessage::MouseUp: + server_message.type = WSAPI_ServerMessage::Type::MouseUp; + server_message.mouse.position = static_cast<WSMouseEvent&>(message).position(); + server_message.mouse.button = to_api(static_cast<WSMouseEvent&>(message).button()); + server_message.mouse.buttons = static_cast<WSMouseEvent&>(message).buttons(); + server_message.mouse.modifiers = static_cast<WSMouseEvent&>(message).modifiers(); + break; + case WSMessage::WindowEntered: + server_message.type = WSAPI_ServerMessage::Type::WindowEntered; + break; + case WSMessage::WindowLeft: + server_message.type = WSAPI_ServerMessage::Type::WindowLeft; + break; + case WSMessage::KeyDown: + server_message.type = WSAPI_ServerMessage::Type::KeyDown; + server_message.key.character = static_cast<WSKeyEvent&>(message).character(); + server_message.key.key = static_cast<WSKeyEvent&>(message).key(); + server_message.key.modifiers = static_cast<WSKeyEvent&>(message).modifiers(); + break; + case WSMessage::KeyUp: + server_message.type = WSAPI_ServerMessage::Type::KeyUp; + server_message.key.character = static_cast<WSKeyEvent&>(message).character(); + server_message.key.key = static_cast<WSKeyEvent&>(message).key(); + server_message.key.modifiers = static_cast<WSKeyEvent&>(message).modifiers(); + break; + case WSMessage::WindowActivated: + server_message.type = WSAPI_ServerMessage::Type::WindowActivated; + break; + case WSMessage::WindowDeactivated: + server_message.type = WSAPI_ServerMessage::Type::WindowDeactivated; + break; + case WSMessage::WindowCloseRequest: + server_message.type = WSAPI_ServerMessage::Type::WindowCloseRequest; + break; + case WSMessage::WindowResized: + server_message.type = WSAPI_ServerMessage::Type::WindowResized; + server_message.window.old_rect = static_cast<WSResizeEvent&>(message).old_rect(); + server_message.window.rect = static_cast<WSResizeEvent&>(message).rect(); + break; + default: + break; + } + + if (server_message.type == WSAPI_ServerMessage::Type::Invalid) + return; + + m_client->post_message(server_message); +} + +void WSWindow::set_global_cursor_tracking_enabled(bool enabled) +{ + m_global_cursor_tracking_enabled = enabled; +} + +void WSWindow::set_visible(bool b) +{ + if (m_visible == b) + return; + m_visible = b; + invalidate(); +} + +void WSWindow::set_resizable(bool resizable) +{ + if (m_resizable == resizable) + return; + m_resizable = resizable; +} + +void WSWindow::invalidate() +{ + WSWindowManager::the().invalidate(*this); +} + +bool WSWindow::is_active() const +{ + return WSWindowManager::the().active_window() == this; +} + +bool WSWindow::is_blocked_by_modal_window() const +{ + return !is_modal() && client() && client()->is_showing_modal_window(); +} diff --git a/Servers/WindowServer/WSWindow.h b/Servers/WindowServer/WSWindow.h new file mode 100644 index 0000000000..ff6ef6b5c8 --- /dev/null +++ b/Servers/WindowServer/WSWindow.h @@ -0,0 +1,126 @@ +#pragma once + +#include <SharedGraphics/Rect.h> +#include <SharedGraphics/GraphicsBitmap.h> +#include <AK/AKString.h> +#include <AK/InlineLinkedList.h> +#include "WSMessageReceiver.h" +#include <WindowServer/WSWindowType.h> + +class WSClientConnection; +class WSMenu; + +class WSWindow final : public WSMessageReceiver, public InlineLinkedListNode<WSWindow> { +public: + WSWindow(WSClientConnection&, int window_id, bool modal); + WSWindow(WSMessageReceiver&, WSWindowType); + virtual ~WSWindow() override; + + bool is_blocked_by_modal_window() const; + + WSClientConnection* client() { return m_client; } + const WSClientConnection* client() const { return m_client; } + + WSWindowType type() const { return m_type; } + int window_id() const { return m_window_id; } + + String title() const { return m_title; } + void set_title(String&&); + + float opacity() const { return m_opacity; } + void set_opacity(float opacity) { m_opacity = opacity; } + + 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 { return m_modal; } + + bool is_resizable() const { return m_resizable; } + void set_resizable(bool); + + Rect rect() const { return m_rect; } + void set_rect(const Rect&); + void set_rect(int x, int y, int width, int height) { set_rect({ x, y, width, height }); } + void set_rect_without_repaint(const Rect& rect) { m_rect = rect; } + void set_rect_from_window_manager_resize(const Rect&); + + void move_to(const Point& position) { set_rect({ position, size() }); } + void move_to(int x, int y) { move_to({ x, y }); } + + Point position() const { return m_rect.location(); } + void set_position(const Point& position) { set_rect({ position.x(), position.y(), width(), height() }); } + void set_position_without_repaint(const Point& position) { set_rect_without_repaint({ position.x(), position.y(), width(), height() }); } + + Size size() const { return m_rect.size(); } + + void invalidate(); + + virtual void on_message(WSMessage&) override; + + GraphicsBitmap* backing_store() { return m_backing_store.ptr(); } + void set_backing_store(RetainPtr<GraphicsBitmap>&& 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); + } + + GraphicsBitmap* last_backing_store() { return m_last_backing_store.ptr(); } + + void set_global_cursor_tracking_enabled(bool); + bool global_cursor_tracking() const { return m_global_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; } + + void set_last_lazy_resize_rect(const Rect& rect) { m_last_lazy_resize_rect = rect; } + Rect last_lazy_resize_rect() const { return m_last_lazy_resize_rect; } + + bool has_painted_since_last_resize() const { return m_has_painted_since_last_resize; } + void set_has_painted_since_last_resize(bool b) { m_has_painted_since_last_resize = b; } + + Size size_increment() const { return m_size_increment; } + void set_size_increment(const Size& increment) { m_size_increment = increment; } + + Size base_size() const { return m_base_size; } + void set_base_size(const Size& size) { m_base_size = size; } + + const GraphicsBitmap& icon() const { return *m_icon; } + void set_icon(Retained<GraphicsBitmap>&& icon) { m_icon = move(icon); } + + // For InlineLinkedList. + // FIXME: Maybe make a ListHashSet and then WSWindowManager can just use that. + WSWindow* m_next { nullptr }; + WSWindow* m_prev { nullptr }; + +private: + WSClientConnection* m_client { nullptr }; + WSMessageReceiver* m_internal_owner { nullptr }; + String m_title; + Rect m_rect; + WSWindowType m_type { WSWindowType::Normal }; + bool m_global_cursor_tracking_enabled { false }; + bool m_visible { true }; + bool m_has_alpha_channel { false }; + bool m_has_painted_since_last_resize { false }; + bool m_modal { false }; + bool m_resizable { false }; + RetainPtr<GraphicsBitmap> m_backing_store; + RetainPtr<GraphicsBitmap> m_last_backing_store; + int m_window_id { -1 }; + float m_opacity { 1 }; + Rect m_last_lazy_resize_rect; + Size m_size_increment; + Size m_base_size; + Retained<GraphicsBitmap> m_icon; +}; diff --git a/Servers/WindowServer/WSWindowManager.cpp b/Servers/WindowServer/WSWindowManager.cpp new file mode 100644 index 0000000000..a0f1e8c186 --- /dev/null +++ b/Servers/WindowServer/WSWindowManager.cpp @@ -0,0 +1,1241 @@ +#include "WSWindowManager.h" +#include "WSWindow.h" +#include "WSScreen.h" +#include "WSMessageLoop.h" +#include <SharedGraphics/Font.h> +#include <SharedGraphics/Painter.h> +#include <SharedGraphics/CharacterBitmap.h> +#include <AK/StdLibExtras.h> +#include <LibC/errno_numbers.h> +#include "WSMenu.h" +#include "WSMenuBar.h" +#include "WSMenuItem.h" +#include <WindowServer/WSClientConnection.h> +#include <unistd.h> +#include <stdio.h> +#include <time.h> + +#ifdef KERNEL +#include <Kernel/ProcFS.h> +#endif + +//#define DEBUG_COUNTERS +//#define DEBUG_WID_IN_TITLE_BAR +//#define RESIZE_DEBUG +#define USE_WALLPAPER + +static const int window_titlebar_height = 18; + +static inline Rect menu_window_rect(const Rect& rect) +{ + return rect.inflated(2, 2); +} + +static inline Rect title_bar_rect(const Rect& window) +{ + return { + window.x() - 1, + window.y() - window_titlebar_height, + window.width() + 2, + window_titlebar_height + }; +} + +static inline Rect title_bar_icon_rect(const Rect& window) +{ + auto titlebar_rect = title_bar_rect(window); + return { + titlebar_rect.x() + 2, + titlebar_rect.y(), + 16, + titlebar_rect.height(), + }; +} + +static inline Rect title_bar_text_rect(const Rect& window) +{ + auto titlebar_rect = title_bar_rect(window); + auto titlebar_icon_rect = title_bar_icon_rect(window); + return { + titlebar_rect.x() + 2 + titlebar_icon_rect.width() + 2, + titlebar_rect.y(), + titlebar_rect.width() - 4 - titlebar_icon_rect.width() - 2, + titlebar_rect.height() + }; +} + +static inline Rect close_button_rect_for_window(const Rect& window_rect) +{ + auto titlebar_inner_rect = title_bar_text_rect(window_rect); + int close_button_margin = 1; + int close_button_size = titlebar_inner_rect.height() - close_button_margin * 2; + return Rect { + titlebar_inner_rect.right() - close_button_size + 1, + titlebar_inner_rect.top() + close_button_margin, + close_button_size, + close_button_size - 1 + }; +} + +static inline Rect border_window_rect(const Rect& window) +{ + auto titlebar_rect = title_bar_rect(window); + return { titlebar_rect.x() - 1, + titlebar_rect.y() - 1, + titlebar_rect.width() + 2, + window_titlebar_height + window.height() + 3 + }; +} + +static inline Rect outer_window_rect(const Rect& window) +{ + auto rect = border_window_rect(window); + rect.inflate(2, 2); + return rect; +} + +static inline Rect outer_window_rect(const WSWindow& window) +{ + if (window.type() == WSWindowType::Menu) + return menu_window_rect(window.rect()); + if (window.type() == WSWindowType::WindowSwitcher) + return window.rect(); + ASSERT(window.type() == WSWindowType::Normal); + return outer_window_rect(window.rect()); +} + +static WSWindowManager* s_the; + +WSWindowManager& WSWindowManager::the() +{ + ASSERT(s_the); + return *s_the; +} + +static const char* cursor_bitmap_inner_ascii = { + " # " + " ## " + " ### " + " #### " + " ##### " + " ###### " + " ####### " + " ######## " + " ######### " + " ########## " + " ###### " + " ## ## " + " # ## " + " ## " + " ## " + " ## " + " " +}; + +static const char* cursor_bitmap_outer_ascii = { + "## " + "# # " + "# # " + "# # " + "# # " + "# # " + "# # " + "# # " + "# # " + "# # " + "# #### " + "# ## # " + "# # # # " + "## # # " + " # # " + " # # " + " ## " +}; + +void WSWindowManager::flip_buffers() +{ + swap(m_front_bitmap, m_back_bitmap); + swap(m_front_painter, m_back_painter); + int new_y_offset = m_buffers_are_flipped ? 0 : m_screen_rect.height(); + WSScreen::the().set_y_offset(new_y_offset); + m_buffers_are_flipped = !m_buffers_are_flipped; +} + +WSWindowManager::WSWindowManager() + : m_screen(WSScreen::the()) + , m_screen_rect(m_screen.rect()) + , m_flash_flush(false) +{ + s_the = this; + +#ifndef DEBUG_COUNTERS + (void)m_compose_count; + (void)m_flush_count; +#endif + auto size = m_screen_rect.size(); + m_front_bitmap = GraphicsBitmap::create_wrapper(GraphicsBitmap::Format::RGB32, size, m_screen.scanline(0)); + m_back_bitmap = GraphicsBitmap::create_wrapper(GraphicsBitmap::Format::RGB32, size, m_screen.scanline(size.height())); + + m_front_painter = make<Painter>(*m_front_bitmap); + m_back_painter = make<Painter>(*m_back_bitmap); + + m_front_painter->set_font(font()); + m_back_painter->set_font(font()); + + m_background_color = Color(50, 50, 50); + m_active_window_border_color = Color(110, 34, 9); + m_active_window_border_color2 = Color(244, 202, 158); + m_active_window_title_color = Color::White; + m_inactive_window_border_color = Color(128, 128, 128); + m_inactive_window_border_color2 = Color(192, 192, 192); + m_inactive_window_title_color = Color(213, 208, 199); + m_dragging_window_border_color = Color(161, 50, 13); + m_dragging_window_border_color2 = Color(250, 220, 187); + m_dragging_window_title_color = Color::White; + m_highlight_window_border_color = Color::from_rgb(0xa10d0d); + m_highlight_window_border_color2 = Color::from_rgb(0xfabbbb); + m_highlight_window_title_color = Color::White; + + m_cursor_bitmap_inner = CharacterBitmap::create_from_ascii(cursor_bitmap_inner_ascii, 12, 17); + m_cursor_bitmap_outer = CharacterBitmap::create_from_ascii(cursor_bitmap_outer_ascii, 12, 17); + +#ifdef USE_WALLPAPER + m_wallpaper_path = "/res/wallpapers/retro.rgb"; + m_wallpaper = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, m_wallpaper_path, { 1024, 768 }); +#endif + +#ifdef KERNEL + ProcFS::the().add_sys_bool("wm_flash_flush", m_flash_flush); + ProcFS::the().add_sys_string("wm_wallpaper", m_wallpaper_path, [this] { + m_wallpaper = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, m_wallpaper_path, m_screen_rect.size()); + invalidate(m_screen_rect); + }); +#endif + + m_username = getlogin(); + + m_menu_selection_color = Color::from_rgb(0x84351a); + + { + byte system_menu_name[] = { 0xf8, 0 }; + m_system_menu = make<WSMenu>(nullptr, -1, String((const char*)system_menu_name)); + m_system_menu->add_item(make<WSMenuItem>(0, "Open Terminal...")); + m_system_menu->add_item(make<WSMenuItem>(1, "Open ProcessManager...")); + m_system_menu->add_item(make<WSMenuItem>(WSMenuItem::Separator)); + m_system_menu->add_item(make<WSMenuItem>(100, "640x480")); + m_system_menu->add_item(make<WSMenuItem>(101, "800x600")); + m_system_menu->add_item(make<WSMenuItem>(102, "1024x768")); + m_system_menu->add_item(make<WSMenuItem>(103, "1920x1080")); + m_system_menu->add_item(make<WSMenuItem>(WSMenuItem::Separator)); + m_system_menu->add_item(make<WSMenuItem>(200, "About...")); + m_system_menu->on_item_activation = [this] (WSMenuItem& item) { + if (item.identifier() == 0) { + if (fork() == 0) { + execl("/bin/Terminal", "/bin/Terminal", nullptr); + ASSERT_NOT_REACHED(); + } + return; + } + if (item.identifier() == 1) { + if (fork() == 0) { + execl("/bin/ProcessManager", "/bin/ProcessManager", nullptr); + ASSERT_NOT_REACHED(); + } + return; + } + switch (item.identifier()) { + case 100: set_resolution(640, 480); break; + case 101: set_resolution(800, 600); break; + case 102: set_resolution(1024, 768); break; + case 103: set_resolution(1920, 1080); break; + } + if (item.identifier() == 200) { + if (fork() == 0) { + execl("/bin/About", "/bin/About", nullptr); + ASSERT_NOT_REACHED(); + } + return; + } +#ifdef DEBUG_MENUS + dbgprintf("WSMenu 1 item activated: '%s'\n", item.text().characters()); +#endif + }; + } + + // NOTE: This ensures that the system menu has the correct dimensions. + set_current_menubar(nullptr); + + WSMessageLoop::the().start_timer(300, [this] { + static time_t last_update_time; + time_t now = time(nullptr); + if (now != last_update_time) { + tick_clock(); + last_update_time = now; + } + }); + + invalidate(); + compose(); +} + +WSWindowManager::~WSWindowManager() +{ +} + +const Font& WSWindowManager::font() const +{ + return Font::default_font(); +} + +const Font& WSWindowManager::window_title_font() const +{ + return Font::default_bold_font(); +} + +const Font& WSWindowManager::menu_font() const +{ + return Font::default_font(); +} + +const Font& WSWindowManager::app_menu_font() const +{ + return Font::default_bold_font(); +} + +static void get_cpu_usage(unsigned& busy, unsigned& idle) +{ + busy = 0; + idle = 0; + + FILE* fp = fopen("/proc/all", "r"); + if (!fp) { + perror("failed to open /proc/all"); + exit(1); + } + for (;;) { + char buf[BUFSIZ]; + char* ptr = fgets(buf, sizeof(buf), fp); + if (!ptr) + break; + auto parts = String(buf, Chomp).split(','); + if (parts.size() < 17) + break; + bool ok; + pid_t pid = parts[0].to_uint(ok); + ASSERT(ok); + unsigned nsched = parts[1].to_uint(ok); + ASSERT(ok); + + if (pid == 0) + idle += nsched; + else + busy += nsched; + } + int rc = fclose(fp); + ASSERT(rc == 0); +} + +void WSWindowManager::tick_clock() +{ + static unsigned last_busy; + static unsigned last_idle; + unsigned busy; + unsigned idle; + get_cpu_usage(busy, idle); + unsigned busy_diff = busy - last_busy; + unsigned idle_diff = idle - last_idle; + last_busy = busy; + last_idle = idle; + float cpu = (float)busy_diff / (float)(busy_diff + idle_diff); + m_cpu_history.enqueue(cpu); + invalidate(menubar_rect()); +} + +void WSWindowManager::set_resolution(int width, int height) +{ + if (m_screen_rect.width() == width && m_screen_rect.height() == height) + return; + m_wallpaper_path = { }; + m_wallpaper = nullptr; + m_screen.set_resolution(width, height); + m_screen_rect = m_screen.rect(); + m_front_bitmap = GraphicsBitmap::create_wrapper(GraphicsBitmap::Format::RGB32, { width, height }, m_screen.scanline(0)); + m_back_bitmap = GraphicsBitmap::create_wrapper(GraphicsBitmap::Format::RGB32, { width, height }, m_screen.scanline(height)); + m_front_painter = make<Painter>(*m_front_bitmap); + m_back_painter = make<Painter>(*m_back_bitmap); + m_buffers_are_flipped = false; + invalidate(); + compose(); +} + +template<typename Callback> +void WSWindowManager::for_each_active_menubar_menu(Callback callback) +{ + callback(*m_system_menu); + if (m_current_menubar) + m_current_menubar->for_each_menu(callback); +} + +int WSWindowManager::menubar_menu_margin() const +{ + return 16; +} + +void WSWindowManager::set_current_menubar(WSMenuBar* menubar) +{ + if (menubar) + m_current_menubar = menubar->make_weak_ptr(); + else + m_current_menubar = nullptr; +#ifdef DEBUG_MENUS + dbgprintf("[WM] Current menubar is now %p\n", menubar); +#endif + Point next_menu_location { menubar_menu_margin() / 2, 0 }; + int index = 0; + for_each_active_menubar_menu([&] (WSMenu& menu) { + int text_width = index == 1 ? Font::default_bold_font().width(menu.name()) : font().width(menu.name()); + menu.set_rect_in_menubar({ next_menu_location.x() - menubar_menu_margin() / 2, 0, text_width + menubar_menu_margin(), menubar_rect().height() - 1 }); + menu.set_text_rect_in_menubar({ next_menu_location, { text_width, menubar_rect().height() } }); + next_menu_location.move_by(menu.rect_in_menubar().width(), 0); + ++index; + return true; + }); + invalidate(menubar_rect()); +} + +static const char* s_close_button_bitmap_data = { + "## ##" + "### ###" + " ###### " + " #### " + " ## " + " #### " + " ###### " + "### ###" + "## ##" +}; + +static CharacterBitmap* s_close_button_bitmap; +static const int s_close_button_bitmap_width = 8; +static const int s_close_button_bitmap_height = 9; + +void WSWindowManager::paint_window_frame(WSWindow& window) +{ + //printf("[WM] paint_window_frame {%p}, rect: %d,%d %dx%d\n", &window, window.rect().x(), window.rect().y(), window.rect().width(), window.rect().height()); + + if (window.type() == WSWindowType::Menu) { + m_back_painter->draw_rect(menu_window_rect(window.rect()), Color::LightGray); + return; + } + + if (window.type() == WSWindowType::WindowSwitcher) + return; + + auto titlebar_rect = title_bar_rect(window.rect()); + auto titlebar_icon_rect = title_bar_icon_rect(window.rect()); + auto titlebar_inner_rect = title_bar_text_rect(window.rect()); + auto outer_rect = outer_window_rect(window); + auto border_rect = border_window_rect(window.rect()); + auto close_button_rect = close_button_rect_for_window(window.rect()); + + auto titlebar_title_rect = titlebar_inner_rect; + titlebar_title_rect.set_width(Font::default_bold_font().width(window.title())); + + Rect inner_border_rect { + window.x() - 1, + window.y() - 1, + window.width() + 2, + window.height() + 2 + }; + + Color title_color; + Color border_color; + Color border_color2; + Color middle_border_color; + + if (&window == m_highlight_window.ptr()) { + border_color = m_highlight_window_border_color; + border_color2 = m_highlight_window_border_color2; + title_color = m_highlight_window_title_color; + middle_border_color = Color::White; + } else if (&window == m_drag_window.ptr()) { + border_color = m_dragging_window_border_color; + border_color2 = m_dragging_window_border_color2; + title_color = m_dragging_window_title_color; + middle_border_color = Color::White; + } else if (&window == m_active_window.ptr()) { + border_color = m_active_window_border_color; + border_color2 = m_active_window_border_color2; + title_color = m_active_window_title_color; + middle_border_color = Color::MidGray; + } else { + border_color = m_inactive_window_border_color; + border_color2 = m_inactive_window_border_color2; + title_color = m_inactive_window_title_color; + middle_border_color = Color::MidGray; + } + + m_back_painter->fill_rect_with_gradient(titlebar_rect, border_color, border_color2); + for (int i = 2; i <= titlebar_inner_rect.height() - 4; i += 2) { + m_back_painter->draw_line({ titlebar_title_rect.right() + 4, titlebar_inner_rect.y() + i }, { close_button_rect.left() - 3, titlebar_inner_rect.y() + i }, border_color); + } + m_back_painter->draw_rect(border_rect, middle_border_color); + m_back_painter->draw_rect(outer_rect, border_color); + m_back_painter->draw_rect(inner_border_rect, border_color); + + m_back_painter->draw_text(titlebar_title_rect, window.title(), window_title_font(), TextAlignment::CenterLeft, title_color); + + if (!s_close_button_bitmap) + s_close_button_bitmap = &CharacterBitmap::create_from_ascii(s_close_button_bitmap_data, s_close_button_bitmap_width, s_close_button_bitmap_height).leak_ref(); + + m_back_painter->fill_rect_with_gradient(close_button_rect.shrunken(2, 2), Color::LightGray, Color::White); + + m_back_painter->blit(titlebar_icon_rect.location(), window.icon(), window.icon().rect()); + + m_back_painter->draw_rect(close_button_rect, Color::DarkGray); + auto x_location = close_button_rect.center(); + x_location.move_by(-(s_close_button_bitmap_width / 2), -(s_close_button_bitmap_height / 2)); + m_back_painter->draw_bitmap(x_location, *s_close_button_bitmap, Color::Black); + +#ifdef DEBUG_WID_IN_TITLE_BAR + Color metadata_color(96, 96, 96); + m_back_painter->draw_text( + titlebar_inner_rect, + String::format("%d:%d", window.pid(), window.window_id()), + TextAlignment::CenterRight, + metadata_color + ); +#endif +} + +void WSWindowManager::add_window(WSWindow& window) +{ + m_windows.set(&window); + m_windows_in_order.append(&window); + if (!active_window() || active_window()->client() == window.client()) + set_active_window(&window); + if (m_switcher.is_visible() && window.type() != WSWindowType::WindowSwitcher) + m_switcher.refresh(); +} + +void WSWindowManager::move_to_front(WSWindow& window) +{ + if (window.is_blocked_by_modal_window()) + return; + + if (m_windows_in_order.tail() != &window) + invalidate(window); + m_windows_in_order.remove(&window); + m_windows_in_order.append(&window); +} + +void WSWindowManager::remove_window(WSWindow& window) +{ + if (!m_windows.contains(&window)) + return; + + invalidate(window); + m_windows.remove(&window); + m_windows_in_order.remove(&window); + if (!active_window() && !m_windows.is_empty()) + set_active_window(*m_windows.begin()); + if (m_switcher.is_visible() && window.type() != WSWindowType::WindowSwitcher) + m_switcher.refresh(); +} + +void WSWindowManager::notify_title_changed(WSWindow& window) +{ + dbgprintf("[WM] WSWindow{%p} title set to '%s'\n", &window, window.title().characters()); + invalidate(outer_window_rect(window)); + if (m_switcher.is_visible()) + m_switcher.refresh(); +} + +void WSWindowManager::notify_rect_changed(WSWindow& window, const Rect& old_rect, const Rect& new_rect) +{ + dbgprintf("[WM] WSWindow %p rect changed (%d,%d %dx%d) -> (%d,%d %dx%d)\n", &window, old_rect.x(), old_rect.y(), old_rect.width(), old_rect.height(), new_rect.x(), new_rect.y(), new_rect.width(), new_rect.height()); + invalidate(outer_window_rect(old_rect)); + invalidate(outer_window_rect(new_rect)); + if (m_switcher.is_visible() && window.type() != WSWindowType::WindowSwitcher) + m_switcher.refresh(); +} + +void WSWindowManager::handle_menu_mouse_event(WSMenu& menu, WSMouseEvent& event) +{ + bool is_hover_with_any_menu_open = event.type() == WSMouseEvent::MouseMove && m_current_menu; + bool is_mousedown_with_left_button = event.type() == WSMouseEvent::MouseDown && event.button() == MouseButton::Left; + bool should_open_menu = &menu != current_menu() && (is_hover_with_any_menu_open || is_mousedown_with_left_button); + + if (should_open_menu) { + if (current_menu() == &menu) + return; + close_current_menu(); + if (!menu.is_empty()) { + auto& menu_window = menu.ensure_menu_window(); + menu_window.move_to({ menu.rect_in_menubar().x(), menu.rect_in_menubar().bottom() }); + menu_window.set_visible(true); + } + m_current_menu = menu.make_weak_ptr(); + return; + } + if (event.type() == WSMouseEvent::MouseDown && event.button() == MouseButton::Left) { + close_current_menu(); + return; + } +} + +void WSWindowManager::close_current_menu() +{ + if (m_current_menu && m_current_menu->menu_window()) + m_current_menu->menu_window()->set_visible(false); + m_current_menu = nullptr; +} + +void WSWindowManager::handle_menubar_mouse_event(WSMouseEvent& event) +{ + for_each_active_menubar_menu([&] (WSMenu& menu) { + if (menu.rect_in_menubar().contains(event.position())) { + handle_menu_mouse_event(menu, event); + return false; + } + return true; + }); +} + +void WSWindowManager::handle_close_button_mouse_event(WSWindow& window, WSMouseEvent& event) +{ + if (event.type() == WSMessage::MouseDown && event.button() == MouseButton::Left) { + WSMessage message(WSMessage::WindowCloseRequest); + window.on_message(message); + return; + } +} + +void WSWindowManager::start_window_drag(WSWindow& window, WSMouseEvent& event) +{ +#ifdef DRAG_DEBUG + printf("[WM] Begin dragging WSWindow{%p}\n", &window); +#endif + m_drag_window = window.make_weak_ptr();; + m_drag_origin = event.position(); + m_drag_window_origin = window.position(); + invalidate(window); +} + +void WSWindowManager::start_window_resize(WSWindow& window, WSMouseEvent& event) +{ + 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 }, + }; + Rect outer_rect = outer_window_rect(window); + 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)); + m_resize_direction = direction_for_hot_area[hot_area_row][hot_area_column]; + if (m_resize_direction == ResizeDirection::None) { + ASSERT(!m_resize_window); + return; + } + +#ifdef RESIZE_DEBUG + printf("[WM] Begin resizing WSWindow{%p}\n", &window); +#endif + m_resize_window = window.make_weak_ptr();; + m_resize_origin = event.position(); + m_resize_window_original_rect = window.rect(); + m_resize_window->set_has_painted_since_last_resize(true); + + invalidate(window); +} + +void WSWindowManager::process_mouse_event(WSMouseEvent& event, WSWindow*& event_window) +{ + event_window = nullptr; + + if (m_drag_window) { + if (event.type() == WSMessage::MouseUp && event.button() == MouseButton::Left) { +#ifdef DRAG_DEBUG + printf("[WM] Finish dragging WSWindow{%p}\n", m_drag_window.ptr()); +#endif + invalidate(*m_drag_window); + m_drag_window = nullptr; + return; + } + + if (event.type() == WSMessage::MouseMove) { + auto old_window_rect = m_drag_window->rect(); + Point pos = m_drag_window_origin; +#ifdef DRAG_DEBUG + dbgprintf("[WM] Dragging [origin: %d,%d] now: %d,%d\n", m_drag_origin.x(), m_drag_origin.y(), event.x(), event.y()); +#endif + pos.move_by(event.x() - m_drag_origin.x(), event.y() - m_drag_origin.y()); + m_drag_window->set_position_without_repaint(pos); + invalidate(outer_window_rect(old_window_rect)); + invalidate(outer_window_rect(m_drag_window->rect())); + return; + } + } + + if (m_resize_window) { + if (event.type() == WSMessage::MouseUp && event.button() == MouseButton::Right) { +#ifdef RESIZE_DEBUG + printf("[WM] Finish resizing WSWindow{%p}\n", m_resize_window.ptr()); +#endif + WSMessageLoop::the().post_message(*m_resize_window, make<WSResizeEvent>(m_resize_window->rect(), m_resize_window->rect())); + invalidate(*m_resize_window); + m_resize_window = nullptr; + return; + } + + if (event.type() == WSMessage::MouseMove) { + auto old_rect = m_resize_window->rect(); + + int diff_x = event.x() - m_resize_origin.x(); + int diff_y = event.y() - m_resize_origin.y(); + + int change_x = 0; + int change_y = 0; + int change_w = 0; + int change_h = 0; + + switch (m_resize_direction) { + case ResizeDirection::DownRight: + change_w = diff_x; + change_h = diff_y; + break; + case ResizeDirection::Right: + change_w = diff_x; + break; + case ResizeDirection::UpRight: + change_w = diff_x; + change_y = diff_y; + change_h = -diff_y; + break; + case ResizeDirection::Up: + change_y = diff_y; + change_h = -diff_y; + break; + case ResizeDirection::UpLeft: + change_x = diff_x; + change_w = -diff_x; + change_y = diff_y; + change_h = -diff_y; + break; + case ResizeDirection::Left: + change_x = diff_x; + change_w = -diff_x; + break; + case ResizeDirection::DownLeft: + change_x = diff_x; + change_w = -diff_x; + change_h = diff_y; + break; + case ResizeDirection::Down: + change_h = diff_y; + break; + default: + ASSERT_NOT_REACHED(); + } + + auto new_rect = m_resize_window_original_rect; + Size minimum_size { 50, 50 }; + + new_rect.set_x(new_rect.x() + change_x); + new_rect.set_y(new_rect.y() + change_y); + new_rect.set_width(max(minimum_size.width(), new_rect.width() + change_w)); + new_rect.set_height(max(minimum_size.height(), new_rect.height() + change_h)); + + if (!m_resize_window->size_increment().is_null()) { + int horizontal_incs = (new_rect.width() - m_resize_window->base_size().width()) / m_resize_window->size_increment().width(); + new_rect.set_width(m_resize_window->base_size().width() + horizontal_incs * m_resize_window->size_increment().width()); + int vertical_incs = (new_rect.height() - m_resize_window->base_size().height()) / m_resize_window->size_increment().height(); + new_rect.set_height(m_resize_window->base_size().height() + vertical_incs * m_resize_window->size_increment().height()); + } + + if (m_resize_window->rect() == new_rect) + return; +#ifdef RESIZE_DEBUG + dbgprintf("[WM] Resizing [original: %s] now: %s\n", + m_resize_window_original_rect.to_string().characters(), + new_rect.to_string().characters()); +#endif + m_resize_window->set_rect(new_rect); + if (m_resize_window->has_painted_since_last_resize()) { + m_resize_window->set_has_painted_since_last_resize(false); +#ifdef RESIZE_DEBUG + dbgprintf("[WM] I'm gonna wait for %s\n", new_rect.to_string().characters()); +#endif + m_resize_window->set_last_lazy_resize_rect(new_rect); + WSMessageLoop::the().post_message(*m_resize_window, make<WSResizeEvent>(old_rect, new_rect)); + } + return; + } + } + + for (auto* window = m_windows_in_order.tail(); window; window = window->prev()) { + if (!window->global_cursor_tracking()) + continue; + ASSERT(window->is_visible()); // Maybe this should be supported? Idk. Let's catch it and think about it later. + Point position { event.x() - window->rect().x(), event.y() - window->rect().y() }; + auto local_event = make<WSMouseEvent>(event.type(), position, event.buttons(), event.button(), event.modifiers()); + window->on_message(*local_event); + } + + if (menubar_rect().contains(event.position())) { + handle_menubar_mouse_event(event); + return; + } + + if (m_current_menu && m_current_menu->menu_window()) { + bool event_is_inside_current_menu = m_current_menu->menu_window()->rect().contains(event.position()); + if (!event_is_inside_current_menu) { + if (m_current_menu->hovered_item()) + m_current_menu->clear_hovered_item(); + if (event.type() == WSMessage::MouseDown || event.type() == WSMessage::MouseUp) + close_current_menu(); + } + } + + for_each_visible_window_from_front_to_back([&] (WSWindow& window) { + if (window.type() == WSWindowType::Normal && outer_window_rect(window).contains(event.position())) { + if (m_keyboard_modifiers == Mod_Logo && event.type() == WSMessage::MouseDown && event.button() == MouseButton::Left) { + move_to_front(window); + set_active_window(&window); + start_window_drag(window, event); + return IterationDecision::Abort; + } + if (m_keyboard_modifiers == Mod_Logo && event.type() == WSMessage::MouseDown && event.button() == MouseButton::Right && !window.is_blocked_by_modal_window()) { + move_to_front(window); + set_active_window(&window); + start_window_resize(window, event); + return IterationDecision::Abort; + } + } + if (window.type() == WSWindowType::Normal && title_bar_rect(window.rect()).contains(event.position())) { + if (event.type() == WSMessage::MouseDown) { + move_to_front(window); + set_active_window(&window); + } + if (close_button_rect_for_window(window.rect()).contains(event.position())) { + handle_close_button_mouse_event(window, event); + return IterationDecision::Abort; + } + if (event.type() == WSMessage::MouseDown && event.button() == MouseButton::Left) + start_window_drag(window, event); + return IterationDecision::Abort; + } + + if (window.rect().contains(event.position())) { + if (window.type() == WSWindowType::Normal && event.type() == WSMessage::MouseDown) { + move_to_front(window); + set_active_window(&window); + } + event_window = &window; + if (!window.global_cursor_tracking()) { + // FIXME: Should we just alter the coordinates of the existing MouseEvent and pass it through? + Point position { event.x() - window.rect().x(), event.y() - window.rect().y() }; + auto local_event = make<WSMouseEvent>(event.type(), position, event.buttons(), event.button(), event.modifiers()); + window.on_message(*local_event); + } + return IterationDecision::Abort; + } + return IterationDecision::Continue; + }); +} + +void WSWindowManager::compose() +{ + auto dirty_rects = move(m_dirty_rects); + auto cursor_location = m_screen.cursor_location(); + dirty_rects.add(m_last_cursor_rect); + dirty_rects.add({ cursor_location.x(), cursor_location.y(), (int)m_cursor_bitmap_inner->width(), (int)m_cursor_bitmap_inner->height() }); +#ifdef DEBUG_COUNTERS + dbgprintf("[WM] compose #%u (%u rects)\n", ++m_compose_count, dirty_rects.rects().size()); +#endif + + auto any_opaque_window_contains_rect = [this] (const Rect& r) { + for (auto* window = m_windows_in_order.head(); window; window = window->next()) { + if (!window->is_visible()) + continue; + if (window->opacity() < 1.0f) + continue; + if (window->has_alpha_channel()) { + // FIXME: Just because the window has an alpha channel doesn't mean it's not opaque. + // Maybe there's some way we could know this? + continue; + } + if (outer_window_rect(*window).contains(r)) + return true; + } + return false; + }; + + auto any_opaque_window_above_this_one_contains_rect = [this] (const WSWindow& a_window, const Rect& rect) -> bool { + bool found = false; + bool checking = false; + for_each_visible_window_from_back_to_front([&] (WSWindow& window) { + if (&window == &a_window) { + checking = true; + return IterationDecision::Continue; + } + if (!checking) + return IterationDecision::Continue; + if (!window.is_visible()) + return IterationDecision::Continue;; + if (window.opacity() < 1.0f) + return IterationDecision::Continue;; + if (window.has_alpha_channel()) + return IterationDecision::Continue;; + if (outer_window_rect(window).contains(rect)) { + found = true; + return IterationDecision::Abort; + } + return IterationDecision::Continue; + }); + return found; + }; + + auto any_dirty_rect_intersects_window = [&dirty_rects] (const WSWindow& window) { + auto window_rect = outer_window_rect(window); + for (auto& dirty_rect : dirty_rects.rects()) { + if (dirty_rect.intersects(window_rect)) + return true; + } + return false; + }; + + for (auto& dirty_rect : dirty_rects.rects()) { + if (any_opaque_window_contains_rect(dirty_rect)) + continue; + if (!m_wallpaper) + m_back_painter->fill_rect(dirty_rect, m_background_color); + else + m_back_painter->blit(dirty_rect.location(), *m_wallpaper, dirty_rect); + } + + for_each_visible_window_from_back_to_front([&] (WSWindow& window) { + RetainPtr<GraphicsBitmap> backing_store = window.backing_store(); + if (!any_dirty_rect_intersects_window(window)) + return IterationDecision::Continue; + PainterStateSaver saver(*m_back_painter); + m_back_painter->set_clip_rect(outer_window_rect(window)); + for (auto& dirty_rect : dirty_rects.rects()) { + if (any_opaque_window_above_this_one_contains_rect(window, dirty_rect)) + continue; + PainterStateSaver saver(*m_back_painter); + m_back_painter->set_clip_rect(dirty_rect); + paint_window_frame(window); + if (!backing_store) + continue; + Rect dirty_rect_in_window_coordinates = Rect::intersection(dirty_rect, window.rect()); + if (dirty_rect_in_window_coordinates.is_empty()) + continue; + dirty_rect_in_window_coordinates.move_by(-window.position()); + auto dst = window.position(); + dst.move_by(dirty_rect_in_window_coordinates.location()); + if (window.opacity() == 1.0f) + m_back_painter->blit(dst, *backing_store, dirty_rect_in_window_coordinates); + else + m_back_painter->blit_with_opacity(dst, *backing_store, dirty_rect_in_window_coordinates, window.opacity()); + } + return IterationDecision::Continue; + }); + + draw_menubar(); + draw_cursor(); + + if (m_flash_flush) { + for (auto& rect : dirty_rects.rects()) + m_front_painter->fill_rect(rect, Color::Yellow); + } + + flip_buffers(); + for (auto& r : dirty_rects.rects()) + flush(r); +} + +void WSWindowManager::invalidate_cursor() +{ + auto cursor_location = m_screen.cursor_location(); + Rect cursor_rect { cursor_location.x(), cursor_location.y(), (int)m_cursor_bitmap_inner->width(), (int)m_cursor_bitmap_inner->height() }; + invalidate(cursor_rect); +} + +Rect WSWindowManager::menubar_rect() const +{ + return { 0, 0, m_screen_rect.width(), 18 }; +} + +void WSWindowManager::draw_menubar() +{ + auto menubar_rect = this->menubar_rect(); + + m_back_painter->fill_rect(menubar_rect, Color::LightGray); + m_back_painter->draw_line({ 0, menubar_rect.bottom() }, { menubar_rect.right(), menubar_rect.bottom() }, Color::White); + int index = 0; + for_each_active_menubar_menu([&] (WSMenu& menu) { + Color text_color = Color::Black; + if (&menu == current_menu()) { + m_back_painter->fill_rect(menu.rect_in_menubar(), menu_selection_color()); + text_color = Color::White; + } + m_back_painter->draw_text( + menu.text_rect_in_menubar(), + menu.name(), + index == 1 ? app_menu_font() : menu_font(), + TextAlignment::CenterLeft, + text_color + ); + ++index; + return true; + }); + + int username_width = Font::default_bold_font().width(m_username); + Rect username_rect { + menubar_rect.right() - menubar_menu_margin() / 2 - Font::default_bold_font().width(m_username), + menubar_rect.y(), + username_width, + menubar_rect.height() + }; + m_back_painter->draw_text(username_rect, m_username, Font::default_bold_font(), TextAlignment::CenterRight, Color::Black); + + time_t now = time(nullptr); + auto* tm = localtime(&now); + auto time_text = String::format("%4u-%02u-%02u %02u:%02u:%02u", + tm->tm_year + 1900, + tm->tm_mon + 1, + tm->tm_mday, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); + int time_width = font().width(time_text); + Rect time_rect { + username_rect.left() - menubar_menu_margin() / 2 - time_width, + menubar_rect.y(), + time_width, + menubar_rect.height() + }; + + m_back_painter->draw_text(time_rect, time_text, font(), TextAlignment::CenterRight, Color::Black); + + Rect cpu_rect { time_rect.right() - font().width(time_text) - (int)m_cpu_history.capacity() - 10, time_rect.y() + 1, (int)m_cpu_history.capacity(), time_rect.height() - 2 }; + m_back_painter->fill_rect(cpu_rect, Color::Black); + int i = m_cpu_history.capacity() - m_cpu_history.size(); + for (auto cpu_usage : m_cpu_history) { + m_back_painter->draw_line( + { cpu_rect.x() + i, cpu_rect.bottom() }, + { cpu_rect.x() + i, (int)(cpu_rect.y() + (cpu_rect.height() - (cpu_usage * (float)cpu_rect.height()))) }, + Color::from_rgb(0xaa6d4b) + ); + ++i; + } +} + +void WSWindowManager::draw_window_switcher() +{ + if (m_switcher.is_visible()) + m_switcher.draw(); +} + +void WSWindowManager::draw_cursor() +{ + auto cursor_location = m_screen.cursor_location(); + Rect cursor_rect { cursor_location.x(), cursor_location.y(), (int)m_cursor_bitmap_inner->width(), (int)m_cursor_bitmap_inner->height() }; + Color inner_color = Color::White; + Color outer_color = Color::Black; + if (m_screen.mouse_button_state() & (unsigned)MouseButton::Left) + swap(inner_color, outer_color); + m_back_painter->draw_bitmap(cursor_location, *m_cursor_bitmap_inner, inner_color); + m_back_painter->draw_bitmap(cursor_location, *m_cursor_bitmap_outer, outer_color); + m_last_cursor_rect = cursor_rect; +} + +void WSWindowManager::on_message(WSMessage& message) +{ + if (message.is_mouse_event()) { + WSWindow* event_window = nullptr; + process_mouse_event(static_cast<WSMouseEvent&>(message), event_window); + set_hovered_window(event_window); + return; + } + + if (message.is_key_event()) { + auto& key_event = static_cast<WSKeyEvent&>(message); + m_keyboard_modifiers = key_event.modifiers(); + + if (key_event.type() == WSMessage::KeyDown && key_event.modifiers() == Mod_Logo && key_event.key() == Key_Tab) + m_switcher.show(); + if (m_switcher.is_visible()) { + m_switcher.on_key_event(key_event); + return; + } + if (m_active_window) + return m_active_window->on_message(message); + return; + } + + if (message.type() == WSMessage::WM_DeferredCompose) { + m_pending_compose_event = false; + compose(); + return; + } +} + +void WSWindowManager::set_highlight_window(WSWindow* window) +{ + if (window == m_highlight_window.ptr()) + return; + if (auto* previous_highlight_window = m_highlight_window.ptr()) + invalidate(*previous_highlight_window); + m_highlight_window = window ? window->make_weak_ptr() : nullptr; + if (m_highlight_window) + invalidate(*m_highlight_window); +} + +void WSWindowManager::set_active_window(WSWindow* window) +{ + if (window && window->is_blocked_by_modal_window()) + return; + + if (window->type() != WSWindowType::Normal) { + dbgprintf("WSWindowManager: Attempted to make a non-normal window active.\n"); + return; + } + + if (window == m_active_window.ptr()) + return; + + if (auto* previously_active_window = m_active_window.ptr()) { + WSMessageLoop::the().post_message(*previously_active_window, make<WSMessage>(WSMessage::WindowDeactivated)); + invalidate(*previously_active_window); + } + m_active_window = window->make_weak_ptr(); + if (m_active_window) { + WSMessageLoop::the().post_message(*m_active_window, make<WSMessage>(WSMessage::WindowActivated)); + invalidate(*m_active_window); + + auto* client = window->client(); + ASSERT(client); + set_current_menubar(client->app_menubar()); + } +} + +void WSWindowManager::set_hovered_window(WSWindow* window) +{ + if (m_hovered_window.ptr() == window) + return; + + if (m_hovered_window) + WSMessageLoop::the().post_message(*m_hovered_window, make<WSMessage>(WSMessage::WindowLeft)); + + m_hovered_window = window ? window->make_weak_ptr() : nullptr; + + if (m_hovered_window) + WSMessageLoop::the().post_message(*m_hovered_window, make<WSMessage>(WSMessage::WindowEntered)); +} + +void WSWindowManager::invalidate() +{ + m_dirty_rects.clear_with_capacity(); + invalidate(m_screen_rect); +} + +void WSWindowManager::recompose_immediately() +{ + m_dirty_rects.clear_with_capacity(); + invalidate(m_screen_rect, false); +} + +void WSWindowManager::invalidate(const Rect& a_rect, bool should_schedule_compose_event) +{ + auto rect = Rect::intersection(a_rect, m_screen_rect); + if (rect.is_empty()) + return; + + m_dirty_rects.add(rect); + + if (should_schedule_compose_event && !m_pending_compose_event) { + WSMessageLoop::the().post_message(*this, make<WSMessage>(WSMessage::WM_DeferredCompose)); + m_pending_compose_event = true; + } +} + +void WSWindowManager::invalidate(const WSWindow& window) +{ + if (window.type() == WSWindowType::Menu) { + invalidate(menu_window_rect(window.rect())); + return; + } + if (window.type() == WSWindowType::Normal) { + invalidate(outer_window_rect(window)); + return; + } + if (window.type() == WSWindowType::WindowSwitcher) { + invalidate(window.rect()); + return; + } + ASSERT_NOT_REACHED(); +} + +void WSWindowManager::invalidate(const WSWindow& window, const Rect& rect) +{ + if (rect.is_empty()) { + invalidate(window); + return; + } + auto outer_rect = outer_window_rect(window); + auto inner_rect = rect; + inner_rect.move_by(window.position()); + // FIXME: This seems slightly wrong; the inner rect shouldn't intersect the border part of the outer rect. + inner_rect.intersect(outer_rect); + invalidate(inner_rect); +} + +void WSWindowManager::flush(const Rect& a_rect) +{ + auto rect = Rect::intersection(a_rect, m_screen_rect); + +#ifdef DEBUG_COUNTERS + dbgprintf("[WM] flush #%u (%d,%d %dx%d)\n", ++m_flush_count, rect.x(), rect.y(), rect.width(), rect.height()); +#endif + + const RGBA32* front_ptr = m_front_bitmap->scanline(rect.y()) + rect.x(); + RGBA32* back_ptr = m_back_bitmap->scanline(rect.y()) + rect.x(); + size_t pitch = m_back_bitmap->pitch(); + + for (int y = 0; y < rect.height(); ++y) { + fast_dword_copy(back_ptr, front_ptr, rect.width()); + front_ptr = (const RGBA32*)((const byte*)front_ptr + pitch); + back_ptr = (RGBA32*)((byte*)back_ptr + pitch); + } +} + +void WSWindowManager::close_menu(WSMenu& menu) +{ + if (current_menu() == &menu) + close_current_menu(); +} + +void WSWindowManager::close_menubar(WSMenuBar& menubar) +{ + if (current_menubar() == &menubar) + set_current_menubar(nullptr); +} + +const WSClientConnection* WSWindowManager::active_client() const +{ + if (m_active_window) + return m_active_window->client(); + return nullptr; +} + +void WSWindowManager::notify_client_changed_app_menubar(WSClientConnection& client) +{ + if (active_client() == &client) + set_current_menubar(client.app_menubar()); + invalidate(menubar_rect()); +} diff --git a/Servers/WindowServer/WSWindowManager.h b/Servers/WindowServer/WSWindowManager.h new file mode 100644 index 0000000000..55fbbdad31 --- /dev/null +++ b/Servers/WindowServer/WSWindowManager.h @@ -0,0 +1,240 @@ +#pragma once + +#include <SharedGraphics/Rect.h> +#include <SharedGraphics/Color.h> +#include <SharedGraphics/Painter.h> +#include <SharedGraphics/DisjointRectSet.h> +#include <AK/HashTable.h> +#include <AK/InlineLinkedList.h> +#include <AK/WeakPtr.h> +#include <AK/HashMap.h> +#include "WSMessageReceiver.h" +#include "WSMenuBar.h" +#include <WindowServer/WSWindowSwitcher.h> +#include <WindowServer/WSWindowType.h> +#include <WindowServer/WSWindow.h> +#include <AK/CircularQueue.h> + +class WSAPIClientRequest; +class WSScreen; +class WSMenuBar; +class WSMouseEvent; +class WSClientWantsToPaintMessage; +class WSWindow; +class WSClientConnection; +class WSWindowSwitcher; +class CharacterBitmap; +class GraphicsBitmap; + +enum class ResizeDirection { None, Left, UpLeft, Up, UpRight, Right, DownRight, Down, DownLeft }; + +class WSWindowManager : public WSMessageReceiver { + friend class WSWindowSwitcher; +public: + static WSWindowManager& the(); + + WSWindowManager(); + virtual ~WSWindowManager() override; + + void add_window(WSWindow&); + void remove_window(WSWindow&); + + void notify_title_changed(WSWindow&); + void notify_rect_changed(WSWindow&, const Rect& oldRect, const Rect& newRect); + void notify_client_changed_app_menubar(WSClientConnection&); + + WSWindow* active_window() { return m_active_window.ptr(); } + const WSClientConnection* active_client() const; + + WSWindow* highlight_window() { return m_highlight_window.ptr(); } + void set_highlight_window(WSWindow*); + + void move_to_front(WSWindow&); + + void invalidate_cursor(); + void draw_cursor(); + void draw_menubar(); + void draw_window_switcher(); + + Rect menubar_rect() const; + WSMenuBar* current_menubar() { return m_current_menubar.ptr(); } + void set_current_menubar(WSMenuBar*); + WSMenu* current_menu() { return m_current_menu.ptr(); } + void set_current_menu(WSMenu*); + + void invalidate(const WSWindow&); + void invalidate(const WSWindow&, const Rect&); + void invalidate(const Rect&, bool should_schedule_compose_event = true); + void invalidate(); + void recompose_immediately(); + void flush(const Rect&); + + const Font& font() const; + const Font& window_title_font() const; + const Font& menu_font() const; + const Font& app_menu_font() const; + + void close_menu(WSMenu&); + void close_menubar(WSMenuBar&); + Color menu_selection_color() const { return m_menu_selection_color; } + int menubar_menu_margin() const; + + void set_resolution(int width, int height); + +private: + void process_mouse_event(WSMouseEvent&, WSWindow*& event_window); + void handle_menu_mouse_event(WSMenu&, WSMouseEvent&); + void handle_menubar_mouse_event(WSMouseEvent&); + void handle_close_button_mouse_event(WSWindow&, WSMouseEvent&); + void start_window_resize(WSWindow&, WSMouseEvent&); + void start_window_drag(WSWindow&, WSMouseEvent&); + void handle_client_request(WSAPIClientRequest&); + void set_active_window(WSWindow*); + void set_hovered_window(WSWindow*); + template<typename Callback> IterationDecision for_each_visible_window_of_type_from_back_to_front(WSWindowType, Callback); + template<typename Callback> IterationDecision for_each_visible_window_of_type_from_front_to_back(WSWindowType, Callback); + 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_active_menubar_menu(Callback); + void close_current_menu(); + virtual void on_message(WSMessage&) override; + void compose(); + void paint_window_frame(WSWindow&); + void flip_buffers(); + void tick_clock(); + + WSScreen& m_screen; + Rect m_screen_rect; + + Color m_background_color; + Color m_active_window_border_color; + Color m_active_window_border_color2; + Color m_active_window_title_color; + Color m_inactive_window_border_color; + Color m_inactive_window_border_color2; + Color m_inactive_window_title_color; + Color m_dragging_window_border_color; + Color m_dragging_window_border_color2; + Color m_dragging_window_title_color; + Color m_highlight_window_border_color; + Color m_highlight_window_border_color2; + Color m_highlight_window_title_color; + + HashMap<int, OwnPtr<WSWindow>> m_windows_by_id; + HashTable<WSWindow*> m_windows; + InlineLinkedList<WSWindow> m_windows_in_order; + + WeakPtr<WSWindow> m_active_window; + WeakPtr<WSWindow> m_hovered_window; + WeakPtr<WSWindow> m_highlight_window; + + WeakPtr<WSWindow> m_drag_window; + Point m_drag_origin; + Point m_drag_window_origin; + + WeakPtr<WSWindow> m_resize_window; + Rect m_resize_window_original_rect; + Point m_resize_origin; + ResizeDirection m_resize_direction { ResizeDirection::None }; + + Rect m_last_cursor_rect; + + unsigned m_compose_count { 0 }; + unsigned m_flush_count { 0 }; + + RetainPtr<GraphicsBitmap> m_front_bitmap; + RetainPtr<GraphicsBitmap> m_back_bitmap; + + DisjointRectSet m_dirty_rects; + + bool m_pending_compose_event { false }; + + RetainPtr<CharacterBitmap> m_cursor_bitmap_inner; + RetainPtr<CharacterBitmap> m_cursor_bitmap_outer; + + OwnPtr<Painter> m_back_painter; + OwnPtr<Painter> m_front_painter; + + String m_wallpaper_path; + RetainPtr<GraphicsBitmap> m_wallpaper; + + bool m_flash_flush { false }; + bool m_buffers_are_flipped { false }; + + byte m_keyboard_modifiers { 0 }; + + OwnPtr<WSMenu> m_system_menu; + Color m_menu_selection_color; + WeakPtr<WSMenuBar> m_current_menubar; + WeakPtr<WSMenu> m_current_menu; + + WSWindowSwitcher m_switcher; + + CircularQueue<float, 30> m_cpu_history; + + String m_username; +}; + +template<typename Callback> +IterationDecision WSWindowManager::for_each_visible_window_of_type_from_back_to_front(WSWindowType type, Callback callback) +{ + bool do_highlight_window_at_end = false; + for (auto* window = m_windows_in_order.head(); window; window = window->next()) { + if (!window->is_visible()) + continue; + if (window->type() != type) + continue; + if (m_highlight_window.ptr() == window) { + do_highlight_window_at_end = true; + continue; + } + if (callback(*window) == IterationDecision::Abort) + return IterationDecision::Abort; + } + if (do_highlight_window_at_end) { + if (callback(*m_highlight_window) == IterationDecision::Abort) + return IterationDecision::Abort; + } + return IterationDecision::Continue; +} + +template<typename Callback> +IterationDecision WSWindowManager::for_each_visible_window_from_back_to_front(Callback callback) +{ + if (for_each_visible_window_of_type_from_back_to_front(WSWindowType::Normal, callback) == IterationDecision::Abort) + return IterationDecision::Abort; + if (for_each_visible_window_of_type_from_back_to_front(WSWindowType::Menu, callback) == IterationDecision::Abort) + return IterationDecision::Abort; + return for_each_visible_window_of_type_from_back_to_front(WSWindowType::WindowSwitcher, callback); +} + +template<typename Callback> +IterationDecision WSWindowManager::for_each_visible_window_of_type_from_front_to_back(WSWindowType type, Callback callback) +{ + if (m_highlight_window && m_highlight_window->type() == type && m_highlight_window->is_visible()) { + if (callback(*m_highlight_window) == IterationDecision::Abort) + return IterationDecision::Abort; + } + + for (auto* window = m_windows_in_order.tail(); window; window = window->prev()) { + if (!window->is_visible()) + continue; + if (window->type() != type) + continue; + if (window == m_highlight_window.ptr()) + continue; + if (callback(*window) == IterationDecision::Abort) + return IterationDecision::Abort; + } + return IterationDecision::Continue; +} + +template<typename Callback> +IterationDecision WSWindowManager::for_each_visible_window_from_front_to_back(Callback callback) +{ + if (for_each_visible_window_of_type_from_front_to_back(WSWindowType::Menu, callback) == IterationDecision::Abort) + return IterationDecision::Abort; + if (for_each_visible_window_of_type_from_front_to_back(WSWindowType::Normal, callback) == IterationDecision::Abort) + return IterationDecision::Abort; + return for_each_visible_window_of_type_from_front_to_back(WSWindowType::WindowSwitcher, callback); +} diff --git a/Servers/WindowServer/WSWindowSwitcher.cpp b/Servers/WindowServer/WSWindowSwitcher.cpp new file mode 100644 index 0000000000..8bee2c382e --- /dev/null +++ b/Servers/WindowServer/WSWindowSwitcher.cpp @@ -0,0 +1,123 @@ +#include <WindowServer/WSWindowSwitcher.h> +#include <WindowServer/WSWindowManager.h> +#include <WindowServer/WSMessage.h> +#include <SharedGraphics/Font.h> + +WSWindowSwitcher::WSWindowSwitcher() +{ +} + +WSWindowSwitcher::~WSWindowSwitcher() +{ +} + +void WSWindowSwitcher::set_visible(bool visible) +{ + if (m_visible == visible) + return; + m_visible = visible; + if (m_switcher_window) + m_switcher_window->set_visible(visible); + if (!m_visible) + return; + refresh(); +} + +WSWindow* WSWindowSwitcher::selected_window() +{ + if (m_selected_index < 0 || m_selected_index >= m_windows.size()) + return nullptr; + return m_windows[m_selected_index].ptr(); +} + +void WSWindowSwitcher::on_key_event(const WSKeyEvent& event) +{ + if (event.type() == WSMessage::KeyUp) { + if (event.key() == Key_Logo) { + if (auto* window = selected_window()) { + WSWindowManager::the().set_active_window(window); + WSWindowManager::the().move_to_front(*window); + } + WSWindowManager::the().set_highlight_window(nullptr); + hide(); + } + return; + } + if (event.key() != Key_Tab) { + WSWindowManager::the().set_highlight_window(nullptr); + hide(); + return; + } + ASSERT(!m_windows.is_empty()); + m_selected_index = (m_selected_index + 1) % m_windows.size(); + ASSERT(m_selected_index < m_windows.size()); + auto* highlight_window = m_windows.at(m_selected_index).ptr(); + ASSERT(highlight_window); + WSWindowManager::the().set_highlight_window(highlight_window); + draw(); + WSWindowManager::the().invalidate(m_rect); +} + +void WSWindowSwitcher::draw() +{ + Painter painter(*m_switcher_window->backing_store()); + painter.fill_rect({ { }, m_rect.size() }, Color::LightGray); + painter.draw_rect({ { }, m_rect.size() }, Color::DarkGray); + for (int index = 0; index < m_windows.size(); ++index) { + auto& window = *m_windows.at(index); + Rect item_rect { + padding(), + padding() + index * item_height(), + m_rect.width() - padding() * 2, + item_height() + }; + Color text_color; + Color rect_text_color; + if (index == m_selected_index) { + painter.fill_rect(item_rect, Color::from_rgb(0x84351a)); + text_color = Color::White; + rect_text_color = Color::LightGray; + } else { + text_color = Color::Black; + rect_text_color = Color::DarkGray; + } + painter.blit(item_rect.location().translated(0, (item_rect.height() - window.icon().height()) / 2), window.icon(), window.icon().rect()); + painter.draw_text(item_rect.translated(window.icon().width() + 4, 0), window.title(), WSWindowManager::the().window_title_font(), TextAlignment::CenterLeft, text_color); + painter.draw_text(item_rect, window.rect().to_string(), TextAlignment::CenterRight, rect_text_color); + } +} + +void WSWindowSwitcher::refresh() +{ + WSWindow* selected_window = nullptr; + if (m_selected_index > 0 && m_windows[m_selected_index]) + selected_window = m_windows[m_selected_index].ptr(); + m_windows.clear(); + m_selected_index = 0; + int window_count = 0; + int longest_title_width = 0; + WSWindowManager::the().for_each_visible_window_of_type_from_back_to_front(WSWindowType::Normal, [&] (WSWindow& window) { + ++window_count; + longest_title_width = max(longest_title_width, WSWindowManager::the().font().width(window.title())); + if (selected_window == &window) + m_selected_index = m_windows.size(); + m_windows.append(window.make_weak_ptr()); + return IterationDecision::Continue; + }); + if (m_windows.is_empty()) { + hide(); + return; + } + int space_for_window_rect = 180; + m_rect.set_width(longest_title_width + space_for_window_rect + padding() * 2); + m_rect.set_height(window_count * item_height() + padding() * 2); + m_rect.center_within(WSWindowManager::the().m_screen_rect); + if (!m_switcher_window) + m_switcher_window = make<WSWindow>(*this, WSWindowType::WindowSwitcher); + m_switcher_window->set_rect(m_rect); + draw(); +} + +void WSWindowSwitcher::on_message(WSMessage&) +{ +} diff --git a/Servers/WindowServer/WSWindowSwitcher.h b/Servers/WindowServer/WSWindowSwitcher.h new file mode 100644 index 0000000000..418cb4481d --- /dev/null +++ b/Servers/WindowServer/WSWindowSwitcher.h @@ -0,0 +1,43 @@ +#pragma once + +#include <SharedGraphics/Rect.h> +#include <AK/Vector.h> +#include <AK/WeakPtr.h> +#include <WindowServer/WSMessageReceiver.h> + +class Painter; +class WSKeyEvent; +class WSWindow; + +class WSWindowSwitcher : public WSMessageReceiver { +public: + WSWindowSwitcher(); + virtual ~WSWindowSwitcher() 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 WSKeyEvent&); + void refresh(); + + void draw(); + + int item_height() { return 20; } + int padding() { return 8; } + + WSWindow* selected_window(); + + WSWindow* switcher_window() { return m_switcher_window.ptr(); } + +private: + virtual void on_message(WSMessage&) override; + + OwnPtr<WSWindow> m_switcher_window; + Rect m_rect; + bool m_visible { false }; + Vector<WeakPtr<WSWindow>> m_windows; + int m_selected_index { 0 }; +}; diff --git a/Servers/WindowServer/WSWindowType.h b/Servers/WindowServer/WSWindowType.h new file mode 100644 index 0000000000..7713495e0d --- /dev/null +++ b/Servers/WindowServer/WSWindowType.h @@ -0,0 +1,8 @@ +#pragma once + +enum class WSWindowType { + Invalid = 0, + Normal, + Menu, + WindowSwitcher, +}; diff --git a/Servers/WindowServer/main.cpp b/Servers/WindowServer/main.cpp new file mode 100644 index 0000000000..31c7dd04ca --- /dev/null +++ b/Servers/WindowServer/main.cpp @@ -0,0 +1,26 @@ +#include <WindowServer/WSScreen.h> +#include <WindowServer/WSWindowManager.h> +#include <WindowServer/WSMessageLoop.h> +#include <signal.h> +#include <stdio.h> + +int main(int, char**) +{ + 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; + } + + WSMessageLoop loop; + WSScreen screen(1024, 768); + WSWindowManager window_manager; + + dbgprintf("Entering WindowServer main loop.\n"); + WSMessageLoop::the().exec(); + ASSERT_NOT_REACHED(); +} |