summaryrefslogtreecommitdiff
path: root/Servers/WindowServer
diff options
context:
space:
mode:
Diffstat (limited to 'Servers/WindowServer')
-rw-r--r--Servers/WindowServer/.gitignore3
-rw-r--r--Servers/WindowServer/Makefile53
-rw-r--r--Servers/WindowServer/WSAPITypes.h209
-rw-r--r--Servers/WindowServer/WSClientConnection.cpp544
-rw-r--r--Servers/WindowServer/WSClientConnection.h101
-rw-r--r--Servers/WindowServer/WSClipboard.cpp48
-rw-r--r--Servers/WindowServer/WSClipboard.h29
-rw-r--r--Servers/WindowServer/WSMenu.cpp168
-rw-r--r--Servers/WindowServer/WSMenu.h90
-rw-r--r--Servers/WindowServer/WSMenuBar.cpp14
-rw-r--r--Servers/WindowServer/WSMenuBar.h31
-rw-r--r--Servers/WindowServer/WSMenuItem.cpp18
-rw-r--r--Servers/WindowServer/WSMenuItem.h38
-rw-r--r--Servers/WindowServer/WSMessage.h563
-rw-r--r--Servers/WindowServer/WSMessageLoop.cpp332
-rw-r--r--Servers/WindowServer/WSMessageLoop.h62
-rw-r--r--Servers/WindowServer/WSMessageReceiver.cpp9
-rw-r--r--Servers/WindowServer/WSMessageReceiver.h13
-rw-r--r--Servers/WindowServer/WSScreen.cpp98
-rw-r--r--Servers/WindowServer/WSScreen.h48
-rw-r--r--Servers/WindowServer/WSWindow.cpp180
-rw-r--r--Servers/WindowServer/WSWindow.h126
-rw-r--r--Servers/WindowServer/WSWindowManager.cpp1241
-rw-r--r--Servers/WindowServer/WSWindowManager.h240
-rw-r--r--Servers/WindowServer/WSWindowSwitcher.cpp123
-rw-r--r--Servers/WindowServer/WSWindowSwitcher.h43
-rw-r--r--Servers/WindowServer/WSWindowType.h8
-rw-r--r--Servers/WindowServer/main.cpp26
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();
+}