diff --git a/Applications/Taskbar/TaskbarWindow.cpp b/Applications/Taskbar/TaskbarWindow.cpp index 344161e450..ebb7699d9d 100644 --- a/Applications/Taskbar/TaskbarWindow.cpp +++ b/Applications/Taskbar/TaskbarWindow.cpp @@ -74,13 +74,14 @@ void TaskbarWindow::wm_event(GWMEvent& event) } case GEvent::WM_WindowStateChanged: { auto& changed_event = static_cast(event); - printf("WM_WindowStateChanged: client_id=%d, window_id=%d, title=%s, rect=%s, is_active=%u, is_minimized=%u\n", + printf("WM_WindowStateChanged: client_id=%d, window_id=%d, title=%s, rect=%s, is_active=%u, is_minimized=%u, icon_path=%s\n", changed_event.client_id(), changed_event.window_id(), changed_event.title().characters(), changed_event.rect().to_string().characters(), changed_event.is_active(), - changed_event.is_minimized() + changed_event.is_minimized(), + changed_event.icon_path().characters() ); if (!should_include_window(changed_event.window_type())) break; @@ -89,6 +90,7 @@ void TaskbarWindow::wm_event(GWMEvent& event) window.set_rect(changed_event.rect()); window.set_active(changed_event.is_active()); window.set_minimized(changed_event.is_minimized()); + window.set_icon_path(changed_event.icon_path()); if (window.is_minimized()) { window.button()->set_foreground_color(Color::DarkGray); window.button()->set_caption(String::format("[%s]", changed_event.title().characters())); @@ -96,6 +98,7 @@ void TaskbarWindow::wm_event(GWMEvent& event) window.button()->set_foreground_color(Color::Black); window.button()->set_caption(changed_event.title()); } + window.button()->set_icon(window.icon()); window.button()->set_checked(changed_event.is_active()); break; } diff --git a/Applications/Taskbar/WindowList.h b/Applications/Taskbar/WindowList.h index ff6ca9d103..3b5b0d9d41 100644 --- a/Applications/Taskbar/WindowList.h +++ b/Applications/Taskbar/WindowList.h @@ -63,11 +63,27 @@ public: void set_minimized(bool minimized) { m_minimized = minimized; } bool is_minimized() const { return m_minimized; } + String icon_path() const { return m_icon_path; } + void set_icon_path(const String& icon_path) + { + if (m_icon_path == icon_path) + return; + auto icon = GraphicsBitmap::load_from_file(icon_path); + if (!icon) + return; + m_icon_path = icon_path; + m_icon = move(icon); + } + + const GraphicsBitmap* icon() const { return m_icon.ptr(); } + private: WindowIdentifier m_identifier; String m_title; Rect m_rect; GButton* m_button { nullptr }; + String m_icon_path; + RetainPtr m_icon; bool m_active { false }; bool m_minimized { false }; }; diff --git a/Applications/TextEditor/main.cpp b/Applications/TextEditor/main.cpp index be7a5c715b..f9b9cbe994 100644 --- a/Applications/TextEditor/main.cpp +++ b/Applications/TextEditor/main.cpp @@ -151,5 +151,7 @@ int main(int argc, char** argv) text_editor->set_focus(true); window->show(); + window->set_icon_path("/res/icons/TextEditor16.png"); + return app.exec(); } diff --git a/Games/Minesweeper/main.cpp b/Games/Minesweeper/main.cpp index b3299b992e..170a4a3700 100644 --- a/Games/Minesweeper/main.cpp +++ b/Games/Minesweeper/main.cpp @@ -52,5 +52,7 @@ int main(int argc, char** argv) window->show(); + window->set_icon_path("/res/icons/minesweeper/mine.png"); + return app.exec(); } diff --git a/LibGUI/GButton.cpp b/LibGUI/GButton.cpp index b78f0358fd..409cbcaa3b 100644 --- a/LibGUI/GButton.cpp +++ b/LibGUI/GButton.cpp @@ -45,10 +45,10 @@ void GButton::paint_event(GPaintEvent& event) auto content_rect = rect().shrunken(10, 2); auto icon_location = m_icon ? content_rect.center().translated(-(m_icon->width() / 2), -(m_icon->height() / 2)) : Point(); - if (m_being_pressed) { - content_rect.move_by(1, 1); - icon_location.move_by(1, 1); - } + if (m_icon && !m_caption.is_empty()) + icon_location.set_x(content_rect.x()); + if (m_being_pressed) + painter.translate(1, 1); if (m_icon) { if (is_enabled()) painter.blit(icon_location, *m_icon, m_icon->rect()); @@ -56,6 +56,10 @@ void GButton::paint_event(GPaintEvent& event) painter.blit_dimmed(icon_location, *m_icon, m_icon->rect()); } auto& font = (m_checkable && m_checked) ? Font::default_bold_font() : this->font(); + if (m_icon && !m_caption.is_empty()) { + content_rect.move_by(m_icon->width() + 4, 0); + content_rect.set_width(content_rect.width() - m_icon->width() - 4); + } painter.draw_text(content_rect, m_caption, font, text_alignment(), foreground_color(), TextElision::Right); } @@ -130,3 +134,11 @@ void GButton::set_action(GAction& action) action.register_button({ }, *this); set_enabled(action.is_enabled()); } + +void GButton::set_icon(RetainPtr&& icon) +{ + if (m_icon.ptr() == icon.ptr()) + return; + m_icon = move(icon); + update(); +} diff --git a/LibGUI/GButton.h b/LibGUI/GButton.h index 726a054eff..7a163bc537 100644 --- a/LibGUI/GButton.h +++ b/LibGUI/GButton.h @@ -17,7 +17,7 @@ public: String caption() const { return m_caption; } void set_caption(const String&); - void set_icon(RetainPtr&& icon) { m_icon = move(icon); } + void set_icon(RetainPtr&&); const GraphicsBitmap* icon() const { return m_icon.ptr(); } GraphicsBitmap* icon() { return m_icon.ptr(); } diff --git a/LibGUI/GEvent.h b/LibGUI/GEvent.h index d9413b2f5d..1a95331aab 100644 --- a/LibGUI/GEvent.h +++ b/LibGUI/GEvent.h @@ -69,12 +69,13 @@ public: class GWMWindowStateChangedEvent : public GWMEvent { public: - GWMWindowStateChangedEvent(int client_id, int window_id, const String& title, const Rect& rect, bool is_active, GWindowType window_type, bool is_minimized) + GWMWindowStateChangedEvent(int client_id, int window_id, const String& title, const Rect& rect, bool is_active, GWindowType window_type, bool is_minimized, const String& icon_path) : GWMEvent(GEvent::Type::WM_WindowStateChanged, client_id, window_id) , m_title(title) + , m_icon_path(icon_path) , m_rect(rect) - , m_active(is_active) , m_window_type(window_type) + , m_active(is_active) , m_minimized(is_minimized) { } @@ -84,12 +85,14 @@ public: bool is_active() const { return m_active; } GWindowType window_type() const { return m_window_type; } bool is_minimized() const { return m_minimized; } + String icon_path() const { return m_icon_path; } private: String m_title; + String m_icon_path; Rect m_rect; - bool m_active; GWindowType m_window_type; + bool m_active; bool m_minimized; }; diff --git a/LibGUI/GEventLoop.cpp b/LibGUI/GEventLoop.cpp index 11cac1ab92..895bf0a77c 100644 --- a/LibGUI/GEventLoop.cpp +++ b/LibGUI/GEventLoop.cpp @@ -174,7 +174,7 @@ void GEventLoop::handle_wm_event(const WSAPI_ServerMessage& event, GWindow& wind dbgprintf("GEventLoop: handle_wm_event: %d\n", (int)event.type); #endif if (event.type == WSAPI_ServerMessage::WM_WindowStateChanged) - return post_event(window, make(event.wm.client_id, event.wm.window_id, String(event.text, event.text_length), event.wm.rect, event.wm.is_active, (GWindowType)event.wm.window_type, event.wm.is_minimized)); + return post_event(window, make(event.wm.client_id, event.wm.window_id, String(event.text, event.text_length), event.wm.rect, event.wm.is_active, (GWindowType)event.wm.window_type, event.wm.is_minimized, String(event.wm.icon_path, event.wm.icon_path_length))); if (event.type == WSAPI_ServerMessage::WM_WindowRemoved) return post_event(window, make(event.wm.client_id, event.wm.window_id)); ASSERT_NOT_REACHED(); diff --git a/LibGUI/GWindow.cpp b/LibGUI/GWindow.cpp index 204e4c09fc..5c94d49eae 100644 --- a/LibGUI/GWindow.cpp +++ b/LibGUI/GWindow.cpp @@ -448,3 +448,19 @@ void GWindow::set_modal(bool modal) void GWindow::wm_event(GWMEvent&) { } + +void GWindow::set_icon_path(const String& path) +{ + if (m_icon_path == path) + return; + m_icon_path = path; + if (!m_window_id) + return; + WSAPI_ClientMessage message; + message.type = WSAPI_ClientMessage::Type::SetWindowIcon; + message.window_id = m_window_id; + ASSERT(path.length() < sizeof(message.text)); + strcpy(message.text, path.characters()); + message.text_length = path.length(); + GEventLoop::post_message_to_server(message); +} diff --git a/LibGUI/GWindow.h b/LibGUI/GWindow.h index eb9d9a384c..9aa9c41112 100644 --- a/LibGUI/GWindow.h +++ b/LibGUI/GWindow.h @@ -100,6 +100,9 @@ public: void set_override_cursor(GStandardCursor); + String icon_path() const { return m_icon_path; } + void set_icon_path(const String&); + virtual const char* class_name() const override { return "GWindow"; } protected: @@ -123,6 +126,7 @@ private: WeakPtr m_hovered_widget; Rect m_rect_when_windowless; String m_title_when_windowless; + String m_icon_path; Vector m_pending_paint_event_rects; Size m_size_increment; Size m_base_size; diff --git a/Servers/WindowServer/WSAPITypes.h b/Servers/WindowServer/WSAPITypes.h index f77ca19620..5ba2e28895 100644 --- a/Servers/WindowServer/WSAPITypes.h +++ b/Servers/WindowServer/WSAPITypes.h @@ -116,6 +116,8 @@ struct WSAPI_ServerMessage { bool is_active; bool is_minimized; WSAPI_WindowType window_type; + int icon_path_length; + char icon_path[256]; } wm; struct { WSAPI_Rect rect; @@ -193,6 +195,7 @@ struct WSAPI_ClientMessage { SetWindowOverrideCursor, WM_SetActiveWindow, PopupMenu, + SetWindowIcon, }; Type type { Invalid }; int window_id { -1 }; diff --git a/Servers/WindowServer/WSClientConnection.cpp b/Servers/WindowServer/WSClientConnection.cpp index 1e6f2b88d5..7df858b25e 100644 --- a/Servers/WindowServer/WSClientConnection.cpp +++ b/Servers/WindowServer/WSClientConnection.cpp @@ -339,6 +339,28 @@ void WSClientConnection::handle_request(const WSAPIGetWindowTitleRequest& reques post_message(response); } +void WSClientConnection::handle_request(const WSAPISetWindowIconRequest& request) +{ + int window_id = request.window_id(); + auto it = m_windows.find(window_id); + if (it == m_windows.end()) { + post_error("WSAPISetWindowIconRequest: Bad window ID"); + return; + } + auto& window = *(*it).value; + if (request.icon_path().is_empty()) { + window.set_default_icon(); + } else { + auto icon = GraphicsBitmap::load_from_file(request.icon_path()); + if (!icon) + return; + window.set_icon(request.icon_path(), *icon); + } + + window.frame().invalidate_title_bar(); + WSWindowManager::the().tell_wm_listeners_window_state_changed(window); +} + void WSClientConnection::handle_request(const WSAPISetWindowRectRequest& request) { int window_id = request.window_id(); @@ -601,6 +623,8 @@ void WSClientConnection::on_request(const WSAPIClientRequest& request) return handle_request(static_cast(request)); case WSMessage::APIGetWindowRectRequest: return handle_request(static_cast(request)); + case WSMessage::APISetWindowIconRequest: + return handle_request(static_cast(request)); case WSMessage::APISetClipboardContentsRequest: return handle_request(static_cast(request)); case WSMessage::APIGetClipboardContentsRequest: diff --git a/Servers/WindowServer/WSClientConnection.h b/Servers/WindowServer/WSClientConnection.h index c9a7680f22..ff1fda9c99 100644 --- a/Servers/WindowServer/WSClientConnection.h +++ b/Servers/WindowServer/WSClientConnection.h @@ -56,6 +56,7 @@ private: void handle_request(const WSAPIGetWindowTitleRequest&); void handle_request(const WSAPISetWindowRectRequest&); void handle_request(const WSAPIGetWindowRectRequest&); + void handle_request(const WSAPISetWindowIconRequest&); void handle_request(const WSAPISetClipboardContentsRequest&); void handle_request(const WSAPIGetClipboardContentsRequest&); void handle_request(const WSAPICreateWindowRequest&); diff --git a/Servers/WindowServer/WSMessage.h b/Servers/WindowServer/WSMessage.h index 7cee53ef55..628f113ffa 100644 --- a/Servers/WindowServer/WSMessage.h +++ b/Servers/WindowServer/WSMessage.h @@ -45,6 +45,7 @@ public: APIGetWindowTitleRequest, APISetWindowRectRequest, APIGetWindowRectRequest, + APISetWindowIconRequest, APIInvalidateRectRequest, APIDidFinishPaintingNotification, APIGetWindowBackingStoreRequest, @@ -463,6 +464,23 @@ private: Rect m_rect; }; +class WSAPISetWindowIconRequest final : public WSAPIClientRequest { +public: + explicit WSAPISetWindowIconRequest(int client_id, int window_id, const String& icon_path) + : WSAPIClientRequest(WSMessage::APISetWindowIconRequest, client_id) + , m_window_id(window_id) + , m_icon_path(icon_path) + { + } + + int window_id() const { return m_window_id; } + String icon_path() const { return m_icon_path; } + +private: + int m_window_id { 0 }; + String m_icon_path; +}; + class WSAPIGetWindowRectRequest final : public WSAPIClientRequest { public: explicit WSAPIGetWindowRectRequest(int client_id, int window_id) @@ -684,9 +702,10 @@ public: class WSWMWindowStateChangedEvent : public WSWMEvent { public: - WSWMWindowStateChangedEvent(int client_id, int window_id, const String& title, const Rect& rect, bool is_active, WSWindowType window_type, bool is_minimized) + WSWMWindowStateChangedEvent(int client_id, int window_id, const String& title, const Rect& rect, bool is_active, WSWindowType window_type, bool is_minimized, const String& icon_path) : WSWMEvent(WSMessage::WM_WindowStateChanged, client_id, window_id) , m_title(title) + , m_icon_path(icon_path) , m_rect(rect) , m_active(is_active) , m_window_type(window_type) @@ -695,6 +714,7 @@ public: } String title() const { return m_title; } + String icon_path() const { return m_icon_path; } Rect rect() const { return m_rect; } bool is_active() const { return m_active; } WSWindowType window_type() const { return m_window_type; } @@ -702,6 +722,7 @@ public: private: String m_title; + String m_icon_path; Rect m_rect; bool m_active; WSWindowType m_window_type; diff --git a/Servers/WindowServer/WSMessageLoop.cpp b/Servers/WindowServer/WSMessageLoop.cpp index ddb93ad8e8..938a65b1b0 100644 --- a/Servers/WindowServer/WSMessageLoop.cpp +++ b/Servers/WindowServer/WSMessageLoop.cpp @@ -293,6 +293,10 @@ void WSMessageLoop::on_receive_from_client(int client_id, const WSAPI_ClientMess case WSAPI_ClientMessage::Type::PopupMenu: post_message(client, make(client_id, message.menu.menu_id, message.menu.position)); break; + case WSAPI_ClientMessage::Type::SetWindowIcon: + ASSERT(message.text_length < (ssize_t)sizeof(message.text)); + post_message(client, make(client_id, message.window_id, String(message.text, message.text_length))); + break; case WSAPI_ClientMessage::Type::DestroyMenu: post_message(client, make(client_id, message.menu.menu_id)); break; diff --git a/Servers/WindowServer/WSWindow.cpp b/Servers/WindowServer/WSWindow.cpp index 40dd9b5925..0f3c00f048 100644 --- a/Servers/WindowServer/WSWindow.cpp +++ b/Servers/WindowServer/WSWindow.cpp @@ -5,11 +5,16 @@ #include #include +static String default_window_icon_path() +{ + return "/res/icons/16x16/window.png"; +} + static GraphicsBitmap& default_window_icon() { static GraphicsBitmap* s_icon; if (!s_icon) - s_icon = GraphicsBitmap::load_from_file("/res/icons/16x16/window.png").leak_ref(); + s_icon = GraphicsBitmap::load_from_file(default_window_icon_path()).leak_ref(); return *s_icon; } @@ -17,6 +22,7 @@ WSWindow::WSWindow(WSMessageReceiver& internal_owner, WSWindowType type) : m_internal_owner(&internal_owner) , m_type(type) , m_icon(default_window_icon()) + , m_icon_path(default_window_icon_path()) , m_frame(*this) { WSWindowManager::the().add_window(*this); @@ -28,6 +34,7 @@ WSWindow::WSWindow(WSClientConnection& client, WSWindowType window_type, int win , m_modal(modal) , m_window_id(window_id) , m_icon(default_window_icon()) + , m_icon_path(default_window_icon_path()) , m_frame(*this) { // FIXME: This should not be hard-coded here. @@ -189,6 +196,9 @@ void WSWindow::on_message(const WSMessage& message) memcpy(server_message.text, changed_event.title().characters(), changed_event.title().length()); server_message.text_length = changed_event.title().length(); server_message.wm.rect = changed_event.rect(); + ASSERT(changed_event.icon_path().length() < sizeof(server_message.wm.icon_path)); + memcpy(server_message.wm.icon_path, changed_event.icon_path().characters(), changed_event.icon_path().length()); + server_message.wm.icon_path_length = changed_event.icon_path().length(); break; } @@ -236,3 +246,9 @@ bool WSWindow::is_blocked_by_modal_window() const { return !is_modal() && client() && client()->is_showing_modal_window(); } + +void WSWindow::set_default_icon() +{ + m_icon = default_window_icon(); + m_icon_path = default_window_icon_path(); +} diff --git a/Servers/WindowServer/WSWindow.h b/Servers/WindowServer/WSWindow.h index f4f43668de..7f4461c51c 100644 --- a/Servers/WindowServer/WSWindow.h +++ b/Servers/WindowServer/WSWindow.h @@ -113,7 +113,13 @@ public: void set_base_size(const Size& size) { m_base_size = size; } const GraphicsBitmap& icon() const { return *m_icon; } - void set_icon(Retained&& icon) { m_icon = move(icon); } + String icon_path() const { return m_icon_path; } + void set_icon(const String& path, Retained&& icon) + { + m_icon_path = path; + m_icon = move(icon); + } + void set_default_icon(); const WSCursor* override_cursor() const { return m_override_cursor.ptr(); } void set_override_cursor(RetainPtr&& cursor) { m_override_cursor = move(cursor); } @@ -146,6 +152,7 @@ private: Size m_size_increment; Size m_base_size; Retained m_icon; + String m_icon_path; RetainPtr m_override_cursor; WSWindowFrame m_frame; Color m_background_color { Color::LightGray }; diff --git a/Servers/WindowServer/WSWindowManager.cpp b/Servers/WindowServer/WSWindowManager.cpp index 111e5ed181..3513fda8d5 100644 --- a/Servers/WindowServer/WSWindowManager.cpp +++ b/Servers/WindowServer/WSWindowManager.cpp @@ -363,7 +363,7 @@ void WSWindowManager::remove_window(WSWindow& window) void WSWindowManager::tell_wm_listener_about_window(WSWindow& listener, WSWindow& window) { if (window.client()) - WSMessageLoop::the().post_message(listener, make(window.client()->client_id(), window.window_id(), window.title(), window.rect(), window.is_active(), window.type(), window.is_minimized())); + WSMessageLoop::the().post_message(listener, make(window.client()->client_id(), window.window_id(), window.title(), window.rect(), window.is_active(), window.type(), window.is_minimized(), window.icon_path())); } void WSWindowManager::tell_wm_listeners_window_state_changed(WSWindow& window) diff --git a/Servers/WindowServer/WSWindowManager.h b/Servers/WindowServer/WSWindowManager.h index 1e9530ff8c..cf5b2a33de 100644 --- a/Servers/WindowServer/WSWindowManager.h +++ b/Servers/WindowServer/WSWindowManager.h @@ -111,6 +111,8 @@ public: bool any_opaque_window_contains_rect(const Rect&); bool any_opaque_window_above_this_one_contains_rect(const WSWindow&, const Rect&); + void tell_wm_listeners_window_state_changed(WSWindow&); + private: void process_mouse_event(const WSMouseEvent&, WSWindow*& event_window); bool process_ongoing_window_resize(const WSMouseEvent&, WSWindow*& event_window); @@ -135,7 +137,6 @@ private: void paint_window_frame(const WSWindow&); void flip_buffers(); void tick_clock(); - void tell_wm_listeners_window_state_changed(WSWindow&); void tell_wm_listener_about_window(WSWindow& listener, WSWindow&); void pick_new_active_window();