mirror of
https://github.com/RGBCube/serenity
synced 2025-05-17 12:55:07 +00:00

WindowServer will now send out a WindowStateChanged message to clients when one of their windows is minimized. This is then forwarded to the GWindow, which will try to mark its underlying window backing store as volatile. This allows the kernel to steal the memory used by minimized windows in case it starts running low. Very cool! :^)
710 lines
23 KiB
C++
710 lines
23 KiB
C++
#include <AK/HashMap.h>
|
|
#include <AK/JsonObject.h>
|
|
#include <AK/StringBuilder.h>
|
|
#include <LibC/SharedBuffer.h>
|
|
#include <LibC/stdio.h>
|
|
#include <LibC/stdlib.h>
|
|
#include <LibC/unistd.h>
|
|
#include <LibDraw/GraphicsBitmap.h>
|
|
#include <LibGUI/GApplication.h>
|
|
#include <LibGUI/GEvent.h>
|
|
#include <LibGUI/GPainter.h>
|
|
#include <LibGUI/GWidget.h>
|
|
#include <LibGUI/GWindow.h>
|
|
#include <LibGUI/GWindowServerConnection.h>
|
|
|
|
//#define UPDATE_COALESCING_DEBUG
|
|
|
|
static HashTable<GWindow*> all_windows;
|
|
static HashMap<int, GWindow*> reified_windows;
|
|
|
|
GWindow* GWindow::from_window_id(int window_id)
|
|
{
|
|
auto it = reified_windows.find(window_id);
|
|
if (it != reified_windows.end())
|
|
return (*it).value;
|
|
return nullptr;
|
|
}
|
|
|
|
GWindow::GWindow(CObject* parent)
|
|
: CObject(parent)
|
|
{
|
|
all_windows.set(this);
|
|
m_rect_when_windowless = { 100, 400, 140, 140 };
|
|
m_title_when_windowless = "GWindow";
|
|
}
|
|
|
|
GWindow::~GWindow()
|
|
{
|
|
all_windows.remove(this);
|
|
hide();
|
|
}
|
|
|
|
void GWindow::close()
|
|
{
|
|
hide();
|
|
}
|
|
|
|
void GWindow::move_to_front()
|
|
{
|
|
if (!m_window_id)
|
|
return;
|
|
|
|
GWindowServerConnection::the().send_sync<WindowServer::MoveWindowToFront>(m_window_id);
|
|
}
|
|
|
|
void GWindow::show()
|
|
{
|
|
if (m_window_id)
|
|
return;
|
|
auto response = GWindowServerConnection::the().send_sync<WindowServer::CreateWindow>(
|
|
m_rect_when_windowless,
|
|
m_has_alpha_channel,
|
|
m_modal,
|
|
m_resizable,
|
|
m_fullscreen,
|
|
m_show_titlebar,
|
|
m_opacity_when_windowless,
|
|
m_background_color,
|
|
m_base_size,
|
|
m_size_increment,
|
|
(i32)m_window_type,
|
|
m_title_when_windowless);
|
|
m_window_id = response->window_id();
|
|
|
|
apply_icon();
|
|
|
|
reified_windows.set(m_window_id, this);
|
|
GApplication::the().did_create_window({});
|
|
update();
|
|
}
|
|
|
|
void GWindow::hide()
|
|
{
|
|
if (!m_window_id)
|
|
return;
|
|
reified_windows.remove(m_window_id);
|
|
GWindowServerConnection::the().send_sync<WindowServer::DestroyWindow>(m_window_id);
|
|
m_window_id = 0;
|
|
m_pending_paint_event_rects.clear();
|
|
m_back_bitmap = nullptr;
|
|
m_front_bitmap = nullptr;
|
|
|
|
bool app_has_visible_windows = false;
|
|
for (auto& window : all_windows) {
|
|
if (window->is_visible()) {
|
|
app_has_visible_windows = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!app_has_visible_windows)
|
|
GApplication::the().did_delete_last_window({});
|
|
}
|
|
|
|
void GWindow::set_title(const StringView& title)
|
|
{
|
|
m_title_when_windowless = title;
|
|
if (!m_window_id)
|
|
return;
|
|
GWindowServerConnection::the().send_sync<WindowServer::SetWindowTitle>(m_window_id, title);
|
|
}
|
|
|
|
String GWindow::title() const
|
|
{
|
|
if (!m_window_id)
|
|
return m_title_when_windowless;
|
|
return GWindowServerConnection::the().send_sync<WindowServer::GetWindowTitle>(m_window_id)->title();
|
|
}
|
|
|
|
Rect GWindow::rect() const
|
|
{
|
|
if (!m_window_id)
|
|
return m_rect_when_windowless;
|
|
return GWindowServerConnection::the().send_sync<WindowServer::GetWindowRect>(m_window_id)->rect();
|
|
}
|
|
|
|
void GWindow::set_rect(const Rect& a_rect)
|
|
{
|
|
m_rect_when_windowless = a_rect;
|
|
if (!m_window_id) {
|
|
if (m_main_widget)
|
|
m_main_widget->resize(m_rect_when_windowless.size());
|
|
return;
|
|
}
|
|
GWindowServerConnection::the().send_sync<WindowServer::SetWindowRect>(m_window_id, a_rect);
|
|
if (m_back_bitmap && m_back_bitmap->size() != a_rect.size())
|
|
m_back_bitmap = nullptr;
|
|
if (m_front_bitmap && m_front_bitmap->size() != a_rect.size())
|
|
m_front_bitmap = nullptr;
|
|
if (m_main_widget)
|
|
m_main_widget->resize(a_rect.size());
|
|
}
|
|
|
|
void GWindow::set_window_type(GWindowType window_type)
|
|
{
|
|
m_window_type = window_type;
|
|
}
|
|
|
|
void GWindow::set_override_cursor(GStandardCursor cursor)
|
|
{
|
|
if (!m_window_id)
|
|
return;
|
|
GWindowServerConnection::the().send_sync<WindowServer::SetWindowOverrideCursor>(m_window_id, (u32)cursor);
|
|
}
|
|
|
|
void GWindow::event(CEvent& event)
|
|
{
|
|
if (event.type() == GEvent::Drop) {
|
|
auto& drop_event = static_cast<GDropEvent&>(event);
|
|
if (!m_main_widget)
|
|
return;
|
|
auto result = m_main_widget->hit_test(drop_event.position());
|
|
auto local_event = make<GDropEvent>(result.local_position, drop_event.text(), drop_event.data_type(), drop_event.data());
|
|
ASSERT(result.widget);
|
|
return result.widget->dispatch_event(*local_event, this);
|
|
}
|
|
|
|
if (event.type() == GEvent::MouseUp || event.type() == GEvent::MouseDown || event.type() == GEvent::MouseDoubleClick || event.type() == GEvent::MouseMove || event.type() == GEvent::MouseWheel) {
|
|
auto& mouse_event = static_cast<GMouseEvent&>(event);
|
|
if (m_global_cursor_tracking_widget) {
|
|
auto window_relative_rect = m_global_cursor_tracking_widget->window_relative_rect();
|
|
Point local_point { mouse_event.x() - window_relative_rect.x(), mouse_event.y() - window_relative_rect.y() };
|
|
auto local_event = make<GMouseEvent>((GEvent::Type)event.type(), local_point, mouse_event.buttons(), mouse_event.button(), mouse_event.modifiers(), mouse_event.wheel_delta());
|
|
m_global_cursor_tracking_widget->dispatch_event(*local_event, this);
|
|
return;
|
|
}
|
|
if (m_automatic_cursor_tracking_widget) {
|
|
auto window_relative_rect = m_automatic_cursor_tracking_widget->window_relative_rect();
|
|
Point local_point { mouse_event.x() - window_relative_rect.x(), mouse_event.y() - window_relative_rect.y() };
|
|
auto local_event = make<GMouseEvent>((GEvent::Type)event.type(), local_point, mouse_event.buttons(), mouse_event.button(), mouse_event.modifiers(), mouse_event.wheel_delta());
|
|
m_automatic_cursor_tracking_widget->dispatch_event(*local_event, this);
|
|
if (mouse_event.buttons() == 0)
|
|
m_automatic_cursor_tracking_widget = nullptr;
|
|
return;
|
|
}
|
|
if (!m_main_widget)
|
|
return;
|
|
auto result = m_main_widget->hit_test(mouse_event.position());
|
|
auto local_event = make<GMouseEvent>((GEvent::Type)event.type(), result.local_position, mouse_event.buttons(), mouse_event.button(), mouse_event.modifiers(), mouse_event.wheel_delta());
|
|
ASSERT(result.widget);
|
|
set_hovered_widget(result.widget);
|
|
if (mouse_event.buttons() != 0 && !m_automatic_cursor_tracking_widget)
|
|
m_automatic_cursor_tracking_widget = result.widget->make_weak_ptr();
|
|
if (result.widget != m_global_cursor_tracking_widget.ptr())
|
|
return result.widget->dispatch_event(*local_event, this);
|
|
return;
|
|
}
|
|
|
|
if (event.type() == GEvent::MultiPaint) {
|
|
if (!m_window_id)
|
|
return;
|
|
if (!m_main_widget)
|
|
return;
|
|
auto& paint_event = static_cast<GMultiPaintEvent&>(event);
|
|
auto rects = paint_event.rects();
|
|
ASSERT(!rects.is_empty());
|
|
if (m_back_bitmap && m_back_bitmap->size() != paint_event.window_size()) {
|
|
// Eagerly discard the backing store if we learn from this paint event that it needs to be bigger.
|
|
// Otherwise we would have to wait for a resize event to tell us. This way we don't waste the
|
|
// effort on painting into an undersized bitmap that will be thrown away anyway.
|
|
m_back_bitmap = nullptr;
|
|
}
|
|
bool created_new_backing_store = !m_back_bitmap;
|
|
if (!m_back_bitmap) {
|
|
m_back_bitmap = create_backing_bitmap(paint_event.window_size());
|
|
} else if (m_double_buffering_enabled) {
|
|
bool still_has_pixels = m_back_bitmap->shared_buffer()->set_nonvolatile();
|
|
if (!still_has_pixels) {
|
|
m_back_bitmap = create_backing_bitmap(paint_event.window_size());
|
|
created_new_backing_store = true;
|
|
}
|
|
}
|
|
|
|
auto rect = rects.first();
|
|
if (rect.is_empty() || created_new_backing_store) {
|
|
rects.clear();
|
|
rects.append({ {}, paint_event.window_size() });
|
|
}
|
|
|
|
for (auto& rect : rects)
|
|
m_main_widget->dispatch_event(*make<GPaintEvent>(rect), this);
|
|
|
|
paint_keybinds();
|
|
|
|
if (m_double_buffering_enabled)
|
|
flip(rects);
|
|
else if (created_new_backing_store)
|
|
set_current_backing_bitmap(*m_back_bitmap, true);
|
|
|
|
if (m_window_id) {
|
|
Vector<Rect> rects_to_send;
|
|
for (auto& r : rects)
|
|
rects_to_send.append(r);
|
|
GWindowServerConnection::the().post_message(WindowServer::DidFinishPainting(m_window_id, rects_to_send));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (event.type() == GEvent::KeyUp || event.type() == GEvent::KeyDown) {
|
|
auto keyevent = static_cast<GKeyEvent&>(event);
|
|
if (keyevent.logo() && event.type() == GEvent::KeyUp) {
|
|
if (m_keybind_mode) {
|
|
m_keybind_mode = false;
|
|
} else {
|
|
m_keybind_mode = true;
|
|
collect_keyboard_activation_targets();
|
|
m_entered_keybind = "";
|
|
}
|
|
update();
|
|
return;
|
|
}
|
|
|
|
if (m_keybind_mode) {
|
|
if (event.type() == GEvent::KeyUp) {
|
|
StringBuilder builder;
|
|
builder.append(m_entered_keybind);
|
|
builder.append(keyevent.text());
|
|
m_entered_keybind = builder.to_string();
|
|
|
|
auto found_widget = m_keyboard_activation_targets.find(m_entered_keybind);
|
|
if (found_widget != m_keyboard_activation_targets.end()) {
|
|
m_keybind_mode = false;
|
|
auto event = make<GMouseEvent>(GEvent::MouseDown, Point(), 0, GMouseButton::Left, 0, 0);
|
|
found_widget->value->dispatch_event(*event, this);
|
|
event = make<GMouseEvent>(GEvent::MouseUp, Point(), 0, GMouseButton::Left, 0, 0);
|
|
found_widget->value->dispatch_event(*event, this);
|
|
} else if (m_entered_keybind.length() >= m_max_keybind_length) {
|
|
m_keybind_mode = false;
|
|
}
|
|
update();
|
|
}
|
|
} else {
|
|
if (m_focused_widget)
|
|
return m_focused_widget->dispatch_event(event, this);
|
|
if (m_main_widget)
|
|
return m_main_widget->dispatch_event(event, this);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (event.type() == GEvent::WindowBecameActive || event.type() == GEvent::WindowBecameInactive) {
|
|
if (event.type() == GEvent::WindowBecameInactive && m_keybind_mode) {
|
|
m_keybind_mode = false;
|
|
update();
|
|
}
|
|
|
|
m_is_active = event.type() == GEvent::WindowBecameActive;
|
|
if (m_main_widget)
|
|
m_main_widget->dispatch_event(event, this);
|
|
if (m_focused_widget)
|
|
m_focused_widget->update();
|
|
return;
|
|
}
|
|
|
|
if (event.type() == GEvent::WindowCloseRequest) {
|
|
if (on_close_request) {
|
|
if (on_close_request() == GWindow::CloseRequestDecision::StayOpen)
|
|
return;
|
|
}
|
|
close();
|
|
return;
|
|
}
|
|
|
|
if (event.type() == GEvent::WindowLeft) {
|
|
set_hovered_widget(nullptr);
|
|
return;
|
|
}
|
|
|
|
if (event.type() == GEvent::Resize) {
|
|
auto new_size = static_cast<GResizeEvent&>(event).size();
|
|
if (m_back_bitmap && m_back_bitmap->size() != new_size)
|
|
m_back_bitmap = nullptr;
|
|
if (!m_pending_paint_event_rects.is_empty()) {
|
|
m_pending_paint_event_rects.clear_with_capacity();
|
|
m_pending_paint_event_rects.append({ {}, new_size });
|
|
}
|
|
m_rect_when_windowless = { {}, new_size };
|
|
m_main_widget->set_relative_rect({ {}, new_size });
|
|
return;
|
|
}
|
|
|
|
if (event.type() > GEvent::__Begin_WM_Events && event.type() < GEvent::__End_WM_Events)
|
|
return wm_event(static_cast<GWMEvent&>(event));
|
|
|
|
CObject::event(event);
|
|
}
|
|
|
|
void GWindow::paint_keybinds()
|
|
{
|
|
if (!m_keybind_mode)
|
|
return;
|
|
GPainter painter(*m_main_widget);
|
|
|
|
for (auto& keypair : m_keyboard_activation_targets) {
|
|
if (!keypair.value)
|
|
continue;
|
|
auto& widget = *keypair.value;
|
|
bool could_be_keybind = true;
|
|
for (size_t i = 0; i < m_entered_keybind.length(); ++i) {
|
|
if (keypair.key.characters()[i] != m_entered_keybind.characters()[i]) {
|
|
could_be_keybind = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (could_be_keybind) {
|
|
Rect rect { widget.x() - 5, widget.y() - 5, 4 + Font::default_font().width(keypair.key), 16 };
|
|
Rect highlight_rect { widget.x() - 3, widget.y() - 5, 0, 16 };
|
|
|
|
painter.fill_rect(rect, Color::WarmGray);
|
|
painter.draw_rect(rect, Color::Black);
|
|
painter.draw_text(rect, keypair.key.characters(), TextAlignment::Center, Color::Black);
|
|
painter.draw_text(highlight_rect, m_entered_keybind.characters(), TextAlignment::CenterLeft, Color::MidGray);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void collect_keyboard_activation_targets_impl(GWidget& widget, Vector<GWidget*>& targets)
|
|
{
|
|
widget.for_each_child_widget([&](auto& child) {
|
|
if (child.supports_keyboard_activation()) {
|
|
targets.append(&child);
|
|
collect_keyboard_activation_targets_impl(child, targets);
|
|
}
|
|
return IterationDecision::Continue;
|
|
});
|
|
}
|
|
|
|
void GWindow::collect_keyboard_activation_targets()
|
|
{
|
|
m_keyboard_activation_targets.clear();
|
|
if (!m_main_widget)
|
|
return;
|
|
|
|
Vector<GWidget*> targets;
|
|
collect_keyboard_activation_targets_impl(*m_main_widget, targets);
|
|
|
|
m_max_keybind_length = ceil_div(targets.size(), ('z' - 'a'));
|
|
size_t buffer_length = m_max_keybind_length + 1;
|
|
char keybind_buffer[buffer_length];
|
|
for (size_t i = 0; i < buffer_length - 1; ++i)
|
|
keybind_buffer[i] = 'a';
|
|
keybind_buffer[buffer_length - 1] = '\0';
|
|
|
|
for (auto& widget : targets) {
|
|
m_keyboard_activation_targets.set(String(keybind_buffer), widget->make_weak_ptr());
|
|
for (size_t i = 0; i < buffer_length - 1; ++i) {
|
|
if (keybind_buffer[i] >= 'z') {
|
|
keybind_buffer[i] = 'a';
|
|
} else {
|
|
++keybind_buffer[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool GWindow::is_visible() const
|
|
{
|
|
return m_window_id != 0;
|
|
}
|
|
|
|
void GWindow::update(const Rect& a_rect)
|
|
{
|
|
if (!m_window_id)
|
|
return;
|
|
|
|
for (auto& pending_rect : m_pending_paint_event_rects) {
|
|
if (pending_rect.contains(a_rect)) {
|
|
#ifdef UPDATE_COALESCING_DEBUG
|
|
dbgprintf("Ignoring %s since it's contained by pending rect %s\n", a_rect.to_string().characters(), pending_rect.to_string().characters());
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (m_pending_paint_event_rects.is_empty()) {
|
|
deferred_invoke([this](auto&) {
|
|
auto rects = move(m_pending_paint_event_rects);
|
|
if (rects.is_empty())
|
|
return;
|
|
Vector<Rect> rects_to_send;
|
|
for (auto& r : rects)
|
|
rects_to_send.append(r);
|
|
GWindowServerConnection::the().post_message(WindowServer::InvalidateRect(m_window_id, rects_to_send));
|
|
});
|
|
}
|
|
m_pending_paint_event_rects.append(a_rect);
|
|
}
|
|
|
|
void GWindow::set_main_widget(GWidget* widget)
|
|
{
|
|
if (m_main_widget == widget)
|
|
return;
|
|
if (m_main_widget)
|
|
remove_child(*m_main_widget);
|
|
m_main_widget = widget;
|
|
if (m_main_widget) {
|
|
add_child(*widget);
|
|
auto new_window_rect = rect();
|
|
if (m_main_widget->horizontal_size_policy() == SizePolicy::Fixed)
|
|
new_window_rect.set_width(m_main_widget->preferred_size().width());
|
|
if (m_main_widget->vertical_size_policy() == SizePolicy::Fixed)
|
|
new_window_rect.set_height(m_main_widget->preferred_size().height());
|
|
set_rect(new_window_rect);
|
|
m_main_widget->set_relative_rect({ {}, new_window_rect.size() });
|
|
m_main_widget->set_window(this);
|
|
if (m_main_widget->accepts_focus())
|
|
m_main_widget->set_focus(true);
|
|
}
|
|
update();
|
|
}
|
|
|
|
void GWindow::set_focused_widget(GWidget* widget)
|
|
{
|
|
if (m_focused_widget == widget)
|
|
return;
|
|
if (m_focused_widget) {
|
|
CEventLoop::current().post_event(*m_focused_widget, make<GEvent>(GEvent::FocusOut));
|
|
m_focused_widget->update();
|
|
}
|
|
m_focused_widget = widget ? widget->make_weak_ptr() : nullptr;
|
|
if (m_focused_widget) {
|
|
CEventLoop::current().post_event(*m_focused_widget, make<GEvent>(GEvent::FocusIn));
|
|
m_focused_widget->update();
|
|
}
|
|
}
|
|
|
|
void GWindow::set_global_cursor_tracking_widget(GWidget* widget)
|
|
{
|
|
if (widget == m_global_cursor_tracking_widget)
|
|
return;
|
|
m_global_cursor_tracking_widget = widget ? widget->make_weak_ptr() : nullptr;
|
|
}
|
|
|
|
void GWindow::set_automatic_cursor_tracking_widget(GWidget* widget)
|
|
{
|
|
if (widget == m_automatic_cursor_tracking_widget)
|
|
return;
|
|
m_automatic_cursor_tracking_widget = widget ? widget->make_weak_ptr() : nullptr;
|
|
}
|
|
|
|
void GWindow::set_has_alpha_channel(bool value)
|
|
{
|
|
if (m_has_alpha_channel == value)
|
|
return;
|
|
m_has_alpha_channel = value;
|
|
if (!m_window_id)
|
|
return;
|
|
|
|
m_pending_paint_event_rects.clear();
|
|
m_back_bitmap = nullptr;
|
|
m_front_bitmap = nullptr;
|
|
|
|
GWindowServerConnection::the().send_sync<WindowServer::SetWindowHasAlphaChannel>(m_window_id, value);
|
|
update();
|
|
}
|
|
|
|
void GWindow::set_double_buffering_enabled(bool value)
|
|
{
|
|
ASSERT(!m_window_id);
|
|
m_double_buffering_enabled = value;
|
|
}
|
|
|
|
void GWindow::set_opacity(float opacity)
|
|
{
|
|
m_opacity_when_windowless = opacity;
|
|
if (!m_window_id)
|
|
return;
|
|
GWindowServerConnection::the().send_sync<WindowServer::SetWindowOpacity>(m_window_id, opacity);
|
|
}
|
|
|
|
void GWindow::set_hovered_widget(GWidget* widget)
|
|
{
|
|
if (widget == m_hovered_widget)
|
|
return;
|
|
|
|
if (m_hovered_widget)
|
|
CEventLoop::current().post_event(*m_hovered_widget, make<GEvent>(GEvent::Leave));
|
|
|
|
m_hovered_widget = widget ? widget->make_weak_ptr() : nullptr;
|
|
|
|
if (m_hovered_widget)
|
|
CEventLoop::current().post_event(*m_hovered_widget, make<GEvent>(GEvent::Enter));
|
|
}
|
|
|
|
void GWindow::set_current_backing_bitmap(GraphicsBitmap& bitmap, bool flush_immediately)
|
|
{
|
|
GWindowServerConnection::the().send_sync<WindowServer::SetWindowBackingStore>(m_window_id, 32, bitmap.pitch(), bitmap.shared_buffer_id(), bitmap.has_alpha_channel(), bitmap.size(), flush_immediately);
|
|
}
|
|
|
|
void GWindow::flip(const Vector<Rect, 32>& dirty_rects)
|
|
{
|
|
swap(m_front_bitmap, m_back_bitmap);
|
|
|
|
set_current_backing_bitmap(*m_front_bitmap);
|
|
|
|
if (!m_back_bitmap || m_back_bitmap->size() != m_front_bitmap->size()) {
|
|
m_back_bitmap = create_backing_bitmap(m_front_bitmap->size());
|
|
memcpy(m_back_bitmap->scanline(0), m_front_bitmap->scanline(0), m_front_bitmap->size_in_bytes());
|
|
m_back_bitmap->shared_buffer()->set_volatile();
|
|
return;
|
|
}
|
|
|
|
// Copy whatever was painted from the front to the back.
|
|
Painter painter(*m_back_bitmap);
|
|
for (auto& dirty_rect : dirty_rects)
|
|
painter.blit(dirty_rect.location(), *m_front_bitmap, dirty_rect);
|
|
|
|
m_back_bitmap->shared_buffer()->set_volatile();
|
|
}
|
|
|
|
NonnullRefPtr<GraphicsBitmap> GWindow::create_shared_bitmap(GraphicsBitmap::Format format, const Size& size)
|
|
{
|
|
ASSERT(GWindowServerConnection::the().server_pid());
|
|
ASSERT(!size.is_empty());
|
|
size_t pitch = round_up_to_power_of_two(size.width() * sizeof(RGBA32), 16);
|
|
size_t size_in_bytes = size.height() * pitch;
|
|
auto shared_buffer = SharedBuffer::create_with_size(size_in_bytes);
|
|
ASSERT(shared_buffer);
|
|
shared_buffer->share_with(GWindowServerConnection::the().server_pid());
|
|
return GraphicsBitmap::create_with_shared_buffer(format, *shared_buffer, size);
|
|
}
|
|
|
|
NonnullRefPtr<GraphicsBitmap> GWindow::create_backing_bitmap(const Size& size)
|
|
{
|
|
auto format = m_has_alpha_channel ? GraphicsBitmap::Format::RGBA32 : GraphicsBitmap::Format::RGB32;
|
|
return create_shared_bitmap(format, size);
|
|
}
|
|
|
|
void GWindow::set_modal(bool modal)
|
|
{
|
|
ASSERT(!m_window_id);
|
|
m_modal = modal;
|
|
}
|
|
|
|
void GWindow::wm_event(GWMEvent&)
|
|
{
|
|
}
|
|
|
|
void GWindow::set_icon(const GraphicsBitmap* icon)
|
|
{
|
|
if (m_icon == icon)
|
|
return;
|
|
|
|
m_icon = create_shared_bitmap(GraphicsBitmap::Format::RGBA32, icon->size());
|
|
{
|
|
GPainter painter(*m_icon);
|
|
painter.blit({ 0, 0 }, *icon, icon->rect());
|
|
}
|
|
|
|
apply_icon();
|
|
}
|
|
|
|
void GWindow::apply_icon()
|
|
{
|
|
if (!m_icon)
|
|
return;
|
|
|
|
if (!m_window_id)
|
|
return;
|
|
|
|
int rc = seal_shared_buffer(m_icon->shared_buffer_id());
|
|
ASSERT(rc == 0);
|
|
|
|
rc = share_buffer_globally(m_icon->shared_buffer_id());
|
|
ASSERT(rc == 0);
|
|
|
|
static bool has_set_process_icon;
|
|
if (!has_set_process_icon)
|
|
set_process_icon(m_icon->shared_buffer_id());
|
|
|
|
GWindowServerConnection::the().send_sync<WindowServer::SetWindowIconBitmap>(m_window_id, m_icon->shared_buffer_id(), m_icon->size());
|
|
}
|
|
|
|
void GWindow::start_wm_resize()
|
|
{
|
|
GWindowServerConnection::the().post_message(WindowServer::WM_StartWindowResize(GWindowServerConnection::the().my_client_id(), m_window_id));
|
|
}
|
|
|
|
Vector<GWidget*> GWindow::focusable_widgets() const
|
|
{
|
|
if (!m_main_widget)
|
|
return {};
|
|
|
|
Vector<GWidget*> collected_widgets;
|
|
|
|
Function<void(GWidget&)> collect_focusable_widgets = [&](GWidget& widget) {
|
|
if (widget.accepts_focus())
|
|
collected_widgets.append(&widget);
|
|
widget.for_each_child_widget([&](auto& child) {
|
|
if (!child.is_visible())
|
|
return IterationDecision::Continue;
|
|
if (!child.is_enabled())
|
|
return IterationDecision::Continue;
|
|
collect_focusable_widgets(child);
|
|
return IterationDecision::Continue;
|
|
});
|
|
};
|
|
|
|
collect_focusable_widgets(const_cast<GWidget&>(*m_main_widget));
|
|
return collected_widgets;
|
|
}
|
|
|
|
void GWindow::save_to(AK::JsonObject& json)
|
|
{
|
|
json.set("title", title());
|
|
json.set("visible", is_visible());
|
|
json.set("active", is_active());
|
|
json.set("resizable", is_resizable());
|
|
json.set("fullscreen", is_fullscreen());
|
|
json.set("rect", rect().to_string());
|
|
json.set("base_size", base_size().to_string());
|
|
json.set("size_increment", size_increment().to_string());
|
|
CObject::save_to(json);
|
|
}
|
|
|
|
void GWindow::set_fullscreen(bool fullscreen)
|
|
{
|
|
if (m_fullscreen == fullscreen)
|
|
return;
|
|
m_fullscreen = fullscreen;
|
|
if (!m_window_id)
|
|
return;
|
|
GWindowServerConnection::the().send_sync<WindowServer::SetFullscreen>(m_window_id, fullscreen);
|
|
}
|
|
|
|
void GWindow::schedule_relayout()
|
|
{
|
|
if (m_layout_pending)
|
|
return;
|
|
m_layout_pending = true;
|
|
deferred_invoke([this](auto&) {
|
|
if (main_widget())
|
|
main_widget()->do_layout();
|
|
update();
|
|
m_layout_pending = false;
|
|
});
|
|
}
|
|
|
|
void GWindow::update_all_windows(Badge<GWindowServerConnection>)
|
|
{
|
|
for (auto* window : all_windows) {
|
|
window->update();
|
|
}
|
|
}
|
|
|
|
void GWindow::notify_state_changed(Badge<GWindowServerConnection>, bool minimized)
|
|
{
|
|
// When double buffering is enabled, minimization means we can mark the front bitmap volatile (in addition to the back bitmap.)
|
|
// When double buffering is disabled, there is only the back bitmap (which we can now mark volatile!)
|
|
RefPtr<GraphicsBitmap>& bitmap = m_double_buffering_enabled ? m_front_bitmap : m_back_bitmap;
|
|
if (!bitmap)
|
|
return;
|
|
if (minimized) {
|
|
bitmap->shared_buffer()->set_volatile();
|
|
} else {
|
|
if (!bitmap->shared_buffer()->set_nonvolatile())
|
|
bitmap = nullptr;
|
|
}
|
|
}
|