1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-06-01 08:28:11 +00:00

Port Terminal to LibGUI.

To facilitate listening for action on arbitrary file descriptors,
I've added a GNotifier class. It's quite simple but very useful:

GNotifier notifier(fd, GNotifier::Read);
notifier.on_ready_to_read = [this] (GNotifier& fd) {
    // read from fd or whatever else you like :^)
};

The callback will get invoked by GEventLoop when select() says we
have something to read on the fd.
This commit is contained in:
Andreas Kling 2019-02-10 14:28:39 +01:00
parent ae4811fbae
commit 53d34a0885
15 changed files with 268 additions and 151 deletions

View file

@ -2,6 +2,7 @@
#include "GEvent.h"
#include "GObject.h"
#include "GWindow.h"
#include <LibGUI/GNotifier.h>
#include <LibC/unistd.h>
#include <LibC/stdio.h>
#include <LibC/fcntl.h>
@ -60,7 +61,7 @@ int GEventLoop::exec()
auto* receiver = queued_event.receiver;
auto& event = *queued_event.event;
#ifdef GEVENTLOOP_DEBUG
dbgprintf("GEventLoop: GObject{%p} event %u (%s)\n", receiver, (unsigned)event.type(), event.name());
dbgprintf("GEventLoop: %s{%p} event %u\n", receiver->class_name(), receiver, (unsigned)event.type());
#endif
if (!receiver) {
switch (event.type()) {
@ -149,12 +150,31 @@ void GEventLoop::handle_mouse_event(const GUI_Event& event, GWindow& window)
void GEventLoop::wait_for_event()
{
fd_set rfds;
fd_set wfds;
FD_ZERO(&rfds);
FD_SET(m_event_fd, &rfds);
FD_ZERO(&wfds);
int max_fd = 0;
auto add_fd_to_set = [&max_fd] (int fd, fd_set& set){
FD_SET(fd, &set);
if (fd > max_fd)
max_fd = fd;
};
add_fd_to_set(m_event_fd, rfds);
for (auto& notifier : m_notifiers) {
if (notifier->event_mask() & GNotifier::Read)
add_fd_to_set(notifier->fd(), rfds);
if (notifier->event_mask() & GNotifier::Write)
add_fd_to_set(notifier->fd(), wfds);
if (notifier->event_mask() & GNotifier::Exceptional)
ASSERT_NOT_REACHED();
}
struct timeval timeout = { 0, 0 };
if (!m_timers.is_empty())
get_next_timer_expiration(timeout);
int rc = select(m_event_fd + 1, &rfds, nullptr, nullptr, (m_queued_events.is_empty() && m_timers.is_empty()) ? nullptr : &timeout);
int rc = select(m_event_fd + 1, &rfds, &wfds, nullptr, (m_queued_events.is_empty() && m_timers.is_empty()) ? nullptr : &timeout);
if (rc < 0) {
ASSERT_NOT_REACHED();
}
@ -175,6 +195,17 @@ void GEventLoop::wait_for_event()
}
}
for (auto& notifier : m_notifiers) {
if (FD_ISSET(notifier->fd(), &rfds)) {
if (notifier->on_ready_to_read)
notifier->on_ready_to_read(*notifier);
}
if (FD_ISSET(notifier->fd(), &wfds)) {
if (notifier->on_ready_to_write)
notifier->on_ready_to_write(*notifier);
}
}
if (!FD_ISSET(m_event_fd, &rfds))
return;
@ -266,3 +297,13 @@ bool GEventLoop::unregister_timer(int timer_id)
m_timers.remove(it);
return true;
}
void GEventLoop::register_notifier(Badge<GNotifier>, GNotifier& notifier)
{
m_notifiers.set(&notifier);
}
void GEventLoop::unregister_notifier(Badge<GNotifier>, GNotifier& notifier)
{
m_notifiers.remove(&notifier);
}

View file

@ -1,11 +1,13 @@
#pragma once
#include "GEvent.h"
#include <AK/Badge.h>
#include <AK/HashMap.h>
#include <AK/OwnPtr.h>
#include <AK/Vector.h>
class GObject;
class GNotifier;
class GWindow;
struct GUI_Event;
@ -27,6 +29,9 @@ public:
int register_timer(GObject&, int milliseconds, bool should_reload);
bool unregister_timer(int timer_id);
void register_notifier(Badge<GNotifier>, GNotifier&);
void unregister_notifier(Badge<GNotifier>, GNotifier&);
void exit(int);
private:
@ -64,4 +69,5 @@ private:
};
HashMap<int, OwnPtr<EventLoopTimer>> m_timers;
HashTable<GNotifier*> m_notifiers;
};

15
LibGUI/GNotifier.cpp Normal file
View file

@ -0,0 +1,15 @@
#include <LibGUI/GNotifier.h>
#include <LibGUI/GEventLoop.h>
GNotifier::GNotifier(int fd, unsigned event_mask)
: m_fd(fd)
, m_event_mask(event_mask)
{
GEventLoop::main().register_notifier(Badge<GNotifier>(), *this);
}
GNotifier::~GNotifier()
{
GEventLoop::main().unregister_notifier(Badge<GNotifier>(), *this);
}

25
LibGUI/GNotifier.h Normal file
View file

@ -0,0 +1,25 @@
#pragma once
#include <AK/Function.h>
class GNotifier {
public:
enum Event {
None = 0,
Read = 1,
Write = 2,
Exceptional = 4,
};
GNotifier(int fd, unsigned event_mask);
~GNotifier();
Function<void(GNotifier&)> on_ready_to_read;
Function<void(GNotifier&)> on_ready_to_write;
int fd() const { return m_fd; }
unsigned event_mask() const { return m_event_mask; }
private:
int m_fd { -1 };
unsigned m_event_mask { 0 };
};

View file

@ -34,16 +34,11 @@ void GWidget::set_relative_rect(const Rect& rect)
update();
}
void GWidget::repaint(const Rect& rect)
{
// FIXME: Implement.
}
void GWidget::event(GEvent& event)
{
switch (event.type()) {
case GEvent::Paint:
m_has_pending_paint_event = false;
m_pending_paint_event_rects.clear();
return handle_paint_event(static_cast<GPaintEvent&>(event));
case GEvent::Resize:
return handle_resize_event(static_cast<GResizeEvent&>(event));
@ -171,14 +166,21 @@ void GWidget::focusout_event(GEvent&)
}
void GWidget::update()
{
update(rect());
}
void GWidget::update(const Rect& rect)
{
auto* w = window();
if (!w)
return;
if (m_has_pending_paint_event)
return;
m_has_pending_paint_event = true;
w->update(window_relative_rect());
for (auto& pending_rect : m_pending_paint_event_rects) {
if (pending_rect.contains(rect))
return;
}
m_pending_paint_event_rects.append(rect);
w->update(rect.translated(window_relative_rect().location()));
}
Rect GWidget::window_relative_rect() const

View file

@ -58,7 +58,7 @@ public:
Size size() const { return m_relative_rect.size(); }
void update();
void repaint(const Rect&);
void update(const Rect&);
virtual bool accepts_focus() const { return false; }
@ -136,6 +136,7 @@ private:
SizePolicy m_vertical_size_policy { SizePolicy::Fill };
Size m_preferred_size;
bool m_has_pending_paint_event { false };
Vector<Rect> m_pending_paint_event_rects;
bool m_fill_with_background_color { true };
};

View file

@ -163,13 +163,17 @@ void GWindow::event(GEvent& event)
}
if (event.is_key_event()) {
if (!m_focused_widget)
return;
return m_focused_widget->event(event);
if (m_focused_widget)
return m_focused_widget->event(event);
if (m_main_widget)
return m_main_widget->event(event);
return;
}
if (event.type() == GEvent::WindowBecameActive || event.type() == GEvent::WindowBecameInactive) {
m_is_active = event.type() == GEvent::WindowBecameActive;
if (m_main_widget)
m_main_widget->event(event);
if (m_focused_widget)
m_focused_widget->update();
return;
@ -202,9 +206,18 @@ void GWindow::set_main_widget(GWidget* widget)
if (m_main_widget == widget)
return;
m_main_widget = widget;
m_main_widget->set_relative_rect({ 0, 0, width(), height() });
if (widget)
widget->set_window(this);
if (m_main_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();
}

View file

@ -26,6 +26,7 @@ public:
int height() const { return rect().height(); }
Rect rect() const;
Size size() const { return rect().size(); }
void set_rect(const Rect&);
void set_rect(int x, int y, int width, int height) { set_rect({ x, y, width, height }); }
@ -58,6 +59,8 @@ public:
void set_should_exit_app_on_close(bool b) { m_should_exit_app_on_close = b; }
private:
virtual const char* class_name() const override { return "GWindow"; }
RetainPtr<GraphicsBitmap> m_backing;
int m_window_id { 0 };
bool m_is_active { false };

View file

@ -13,6 +13,7 @@ LIBGUI_OBJS = \
GLabel.o \
GListBox.o \
GObject.o \
GNotifier.o \
GTextBox.o \
GScrollBar.o \
GStatusBar.o \