mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 22:02:44 +00:00 
			
		
		
		
	Rework WindowServer to use select() in its main event loop.
The system can finally idle without burning CPU. :^) There are some issues with scheduling making the mouse cursor sloppy and unresponsive that need to be dealt with.
This commit is contained in:
		
							parent
							
								
									f7ca6d254d
								
							
						
					
					
						commit
						4fef895eda
					
				
					 15 changed files with 121 additions and 33 deletions
				
			
		|  | @ -125,7 +125,12 @@ ssize_t Keyboard::read(Process&, byte* buffer, size_t size) | ||||||
|     while ((size_t)nread < size) { |     while ((size_t)nread < size) { | ||||||
|         if (m_queue.is_empty()) |         if (m_queue.is_empty()) | ||||||
|             break; |             break; | ||||||
|         buffer[nread++] = m_queue.dequeue().character; |         // Don't return partial data frames.
 | ||||||
|  |         if ((size - nread) < 2) | ||||||
|  |             break; | ||||||
|  |         auto key = m_queue.dequeue(); | ||||||
|  |         buffer[nread++] = key.character; | ||||||
|  |         buffer[nread++] = key.modifiers; | ||||||
|     } |     } | ||||||
|     return nread; |     return nread; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -32,16 +32,16 @@ public: | ||||||
| 
 | 
 | ||||||
|     void set_client(KeyboardClient* client) { m_client = client; } |     void set_client(KeyboardClient* client) { m_client = client; } | ||||||
| 
 | 
 | ||||||
|  |     // ^CharacterDevice
 | ||||||
|  |     virtual ssize_t read(Process&, byte* buffer, size_t) override; | ||||||
|  |     virtual bool can_read(Process&) const override; | ||||||
|  |     virtual ssize_t write(Process&, const byte* buffer, size_t) override; | ||||||
|  |     virtual bool can_write(Process&) const override { return true; } | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
|     // ^IRQHandler
 |     // ^IRQHandler
 | ||||||
|     virtual void handle_irq() override; |     virtual void handle_irq() override; | ||||||
| 
 | 
 | ||||||
|     // ^CharacterDevice
 |  | ||||||
|     virtual ssize_t read(Process&, byte* buffer, size_t) override; |  | ||||||
|     virtual ssize_t write(Process&, const byte* buffer, size_t) override; |  | ||||||
|     virtual bool can_read(Process&) const override; |  | ||||||
|     virtual bool can_write(Process&) const override { return true; } |  | ||||||
| 
 |  | ||||||
|     void emit(byte); |     void emit(byte); | ||||||
| 
 | 
 | ||||||
|     KeyboardClient* m_client { nullptr }; |     KeyboardClient* m_client { nullptr }; | ||||||
|  |  | ||||||
|  | @ -537,7 +537,7 @@ bool MemoryManager::validate_user_read(const Process& process, LinearAddress lad | ||||||
|     auto pte = PageTableEntry(&pde.pageTableBase()[pageTableIndex]); |     auto pte = PageTableEntry(&pde.pageTableBase()[pageTableIndex]); | ||||||
|     if (!pte.is_present()) |     if (!pte.is_present()) | ||||||
|         return false; |         return false; | ||||||
|     if (!pte.is_user_allowed()) |     if (process.isRing3() && !pte.is_user_allowed()) | ||||||
|         return false; |         return false; | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
|  | @ -552,7 +552,7 @@ bool MemoryManager::validate_user_write(const Process& process, LinearAddress la | ||||||
|     auto pte = PageTableEntry(&pde.pageTableBase()[pageTableIndex]); |     auto pte = PageTableEntry(&pde.pageTableBase()[pageTableIndex]); | ||||||
|     if (!pte.is_present()) |     if (!pte.is_present()) | ||||||
|         return false; |         return false; | ||||||
|     if (!pte.is_user_allowed()) |     if (process.isRing3() && !pte.is_user_allowed()) | ||||||
|         return false; |         return false; | ||||||
|     if (!pte.is_writable()) |     if (!pte.is_writable()) | ||||||
|         return false; |         return false; | ||||||
|  |  | ||||||
|  | @ -129,6 +129,7 @@ ssize_t PS2MouseDevice::read(Process&, byte* buffer, size_t size) | ||||||
|     while ((size_t)nread < size) { |     while ((size_t)nread < size) { | ||||||
|         if (m_queue.is_empty()) |         if (m_queue.is_empty()) | ||||||
|             break; |             break; | ||||||
|  |         // FIXME: Don't return partial data frames.
 | ||||||
|         buffer[nread++] = m_queue.dequeue(); |         buffer[nread++] = m_queue.dequeue(); | ||||||
|     } |     } | ||||||
|     return nread; |     return nread; | ||||||
|  |  | ||||||
|  | @ -1295,6 +1295,7 @@ int Process::sys$open(const char* path, int options) | ||||||
|     if (number_of_open_file_descriptors() >= m_max_open_file_descriptors) |     if (number_of_open_file_descriptors() >= m_max_open_file_descriptors) | ||||||
|         return -EMFILE; |         return -EMFILE; | ||||||
|     int error = -EWHYTHO; |     int error = -EWHYTHO; | ||||||
|  |     ASSERT(cwd_inode()); | ||||||
|     auto descriptor = VFS::the().open(path, error, options, cwd_inode()->identifier()); |     auto descriptor = VFS::the().open(path, error, options, cwd_inode()->identifier()); | ||||||
|     if (!descriptor) |     if (!descriptor) | ||||||
|         return error; |         return error; | ||||||
|  | @ -1960,8 +1961,15 @@ int Process::sys$select(const Syscall::SC_select_params* params) | ||||||
|     transfer_fds(writefds, m_select_write_fds); |     transfer_fds(writefds, m_select_write_fds); | ||||||
|     transfer_fds(readfds, m_select_read_fds); |     transfer_fds(readfds, m_select_read_fds); | ||||||
| 
 | 
 | ||||||
|     block(BlockedSelect); | #ifdef DEBUG_IO | ||||||
|     Scheduler::yield(); |     dbgprintf("%s<%u> selecting on (read:%u, write:%u)\n", name().characters(), pid(), m_select_read_fds.size(), m_select_write_fds.size()); | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  |     if (!m_wakeup_requested) { | ||||||
|  |         block(BlockedSelect); | ||||||
|  |         Scheduler::yield(); | ||||||
|  |     } | ||||||
|  |     m_wakeup_requested = false; | ||||||
| 
 | 
 | ||||||
|     int markedfds = 0; |     int markedfds = 0; | ||||||
| 
 | 
 | ||||||
|  | @ -1989,3 +1997,11 @@ int Process::sys$select(const Syscall::SC_select_params* params) | ||||||
| 
 | 
 | ||||||
|     return markedfds; |     return markedfds; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | Inode* Process::cwd_inode() | ||||||
|  | { | ||||||
|  |     // FIXME: This is retarded factoring.
 | ||||||
|  |     if (!m_cwd) | ||||||
|  |         m_cwd = VFS::the().root_inode(); | ||||||
|  |     return m_cwd.ptr(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -236,7 +236,7 @@ public: | ||||||
|     template<typename T> bool validate_read_typed(T* value, size_t count = 1) { return validate_read(value, sizeof(T) * count); } |     template<typename T> bool validate_read_typed(T* value, size_t count = 1) { return validate_read(value, sizeof(T) * count); } | ||||||
|     template<typename T> bool validate_write_typed(T* value, size_t count = 1) { return validate_write(value, sizeof(T) * count); } |     template<typename T> bool validate_write_typed(T* value, size_t count = 1) { return validate_write(value, sizeof(T) * count); } | ||||||
| 
 | 
 | ||||||
|     Inode* cwd_inode() { return m_cwd.ptr(); } |     Inode* cwd_inode(); | ||||||
|     Inode* executable_inode() { return m_executable.ptr(); } |     Inode* executable_inode() { return m_executable.ptr(); } | ||||||
| 
 | 
 | ||||||
|     size_t number_of_open_file_descriptors() const; |     size_t number_of_open_file_descriptors() const; | ||||||
|  | @ -256,6 +256,9 @@ public: | ||||||
|     Vector<GUI_Event>& gui_events() { return m_gui_events; } |     Vector<GUI_Event>& gui_events() { return m_gui_events; } | ||||||
|     SpinLock& gui_events_lock() { return m_gui_events_lock; } |     SpinLock& gui_events_lock() { return m_gui_events_lock; } | ||||||
| 
 | 
 | ||||||
|  |     bool wakeup_requested() { return m_wakeup_requested; } | ||||||
|  |     void request_wakeup() { m_wakeup_requested = true; } | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
|     friend class MemoryManager; |     friend class MemoryManager; | ||||||
|     friend class Scheduler; |     friend class Scheduler; | ||||||
|  | @ -357,6 +360,8 @@ private: | ||||||
|     Vector<GUI_Event> m_gui_events; |     Vector<GUI_Event> m_gui_events; | ||||||
|     SpinLock m_gui_events_lock; |     SpinLock m_gui_events_lock; | ||||||
|     int m_next_window_id { 1 }; |     int m_next_window_id { 1 }; | ||||||
|  | 
 | ||||||
|  |     dword m_wakeup_requested { false }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| extern Process* current; | extern Process* current; | ||||||
|  |  | ||||||
|  | @ -108,8 +108,7 @@ int Process::gui$invalidate_window(int window_id) | ||||||
|     if (it == m_windows.end()) |     if (it == m_windows.end()) | ||||||
|         return -EBADWINDOW; |         return -EBADWINDOW; | ||||||
|     auto& window = *(*it).value; |     auto& window = *(*it).value; | ||||||
|     // FIXME: This should queue up a message that the window server process can read.
 |  | ||||||
|     //        Poking into its data structures is not good.
 |  | ||||||
|     WSEventLoop::the().post_event(&window, make<WSEvent>(WSEvent::WM_Invalidate)); |     WSEventLoop::the().post_event(&window, make<WSEvent>(WSEvent::WM_Invalidate)); | ||||||
|  |     WSEventLoop::the().server_process().request_wakeup(); | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -65,6 +65,11 @@ bool Scheduler::pick_next() | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (process.state() == Process::BlockedSelect) { |         if (process.state() == Process::BlockedSelect) { | ||||||
|  |             if (process.wakeup_requested()) { | ||||||
|  |                 process.m_wakeup_requested = false; | ||||||
|  |                 process.unblock(); | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|             for (int fd : process.m_select_read_fds) { |             for (int fd : process.m_select_read_fds) { | ||||||
|                 if (process.m_fds[fd].descriptor->can_read(process)) { |                 if (process.m_fds[fd].descriptor->can_read(process)) { | ||||||
|                     process.unblock(); |                     process.unblock(); | ||||||
|  |  | ||||||
|  | @ -37,6 +37,7 @@ VirtualConsole* tty3; | ||||||
| Keyboard* keyboard; | Keyboard* keyboard; | ||||||
| PS2MouseDevice* ps2mouse; | PS2MouseDevice* ps2mouse; | ||||||
| GUIEventDevice* gui_event_device; | GUIEventDevice* gui_event_device; | ||||||
|  | VFS* vfs; | ||||||
| 
 | 
 | ||||||
| #ifdef STRESS_TEST_SPAWNING | #ifdef STRESS_TEST_SPAWNING | ||||||
| static void spawn_stress() NORETURN; | static void spawn_stress() NORETURN; | ||||||
|  | @ -62,8 +63,6 @@ static void init_stage2() | ||||||
| { | { | ||||||
|     Syscall::initialize(); |     Syscall::initialize(); | ||||||
| 
 | 
 | ||||||
|     auto vfs = make<VFS>(); |  | ||||||
| 
 |  | ||||||
|     auto dev_zero = make<ZeroDevice>(); |     auto dev_zero = make<ZeroDevice>(); | ||||||
|     vfs->register_character_device(*dev_zero); |     vfs->register_character_device(*dev_zero); | ||||||
| 
 | 
 | ||||||
|  | @ -138,6 +137,9 @@ void init() | ||||||
|     gdt_init(); |     gdt_init(); | ||||||
|     idt_init(); |     idt_init(); | ||||||
| 
 | 
 | ||||||
|  |     VFS::initialize_globals(); | ||||||
|  |     vfs = new VFS; | ||||||
|  | 
 | ||||||
|     keyboard = new Keyboard; |     keyboard = new Keyboard; | ||||||
|     ps2mouse = new PS2MouseDevice; |     ps2mouse = new PS2MouseDevice; | ||||||
|     gui_event_device = new GUIEventDevice; |     gui_event_device = new GUIEventDevice; | ||||||
|  | @ -153,7 +155,6 @@ void init() | ||||||
| 
 | 
 | ||||||
|     MemoryManager::initialize(); |     MemoryManager::initialize(); | ||||||
| 
 | 
 | ||||||
|     VFS::initialize_globals(); |  | ||||||
|     StringImpl::initialize_globals(); |     StringImpl::initialize_globals(); | ||||||
| 
 | 
 | ||||||
|     PIT::initialize(); |     PIT::initialize(); | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ mknod mnt/dev/tty0 c 4 0 | ||||||
| mknod mnt/dev/tty1 c 4 1 | mknod mnt/dev/tty1 c 4 1 | ||||||
| mknod mnt/dev/tty2 c 4 2 | mknod mnt/dev/tty2 c 4 2 | ||||||
| mknod mnt/dev/tty3 c 4 3 | mknod mnt/dev/tty3 c 4 3 | ||||||
|  | mknod mnt/dev/keyboard c 85 1 | ||||||
| mknod mnt/dev/psaux c 10 1 | mknod mnt/dev/psaux c 10 1 | ||||||
| mknod mnt/dev/ptmx c 5 2 | mknod mnt/dev/ptmx c 5 2 | ||||||
| mknod mnt/dev/ptm0 c 10 0 | mknod mnt/dev/ptm0 c 10 0 | ||||||
|  |  | ||||||
|  | @ -4,7 +4,9 @@ | ||||||
| #include "WSWindowManager.h" | #include "WSWindowManager.h" | ||||||
| #include "WSScreen.h" | #include "WSScreen.h" | ||||||
| #include "PS2MouseDevice.h" | #include "PS2MouseDevice.h" | ||||||
| #include "Scheduler.h" | #include <Kernel/Keyboard.h> | ||||||
|  | #include <AK/Bitmap.h> | ||||||
|  | #include "Process.h" | ||||||
| 
 | 
 | ||||||
| //#define WSEVENTLOOP_DEBUG
 | //#define WSEVENTLOOP_DEBUG
 | ||||||
| 
 | 
 | ||||||
|  | @ -34,10 +36,19 @@ WSEventLoop& WSEventLoop::the() | ||||||
| int WSEventLoop::exec() | int WSEventLoop::exec() | ||||||
| { | { | ||||||
|     m_server_process = current; |     m_server_process = current; | ||||||
|  | 
 | ||||||
|  |     m_keyboard_fd = m_server_process->sys$open("/dev/keyboard", O_RDONLY); | ||||||
|  |     m_mouse_fd = m_server_process->sys$open("/dev/psaux", O_RDONLY); | ||||||
|  | 
 | ||||||
|  |     ASSERT(m_keyboard_fd >= 0); | ||||||
|  |     ASSERT(m_mouse_fd >= 0); | ||||||
|  | 
 | ||||||
|     m_running = true; |     m_running = true; | ||||||
|     for (;;) { |     for (;;) { | ||||||
|  | 
 | ||||||
|         if (m_queued_events.is_empty()) |         if (m_queued_events.is_empty()) | ||||||
|             waitForEvent(); |             wait_for_event(); | ||||||
|  | 
 | ||||||
|         Vector<QueuedEvent> events; |         Vector<QueuedEvent> events; | ||||||
|         { |         { | ||||||
|             LOCKER(m_lock); |             LOCKER(m_lock); | ||||||
|  | @ -69,20 +80,48 @@ void WSEventLoop::post_event(WSEventReceiver* receiver, OwnPtr<WSEvent>&& event) | ||||||
|     dbgprintf("WSEventLoop::post_event: {%u} << receiver=%p, event=%p\n", m_queued_events.size(), receiver, event.ptr()); |     dbgprintf("WSEventLoop::post_event: {%u} << receiver=%p, event=%p\n", m_queued_events.size(), receiver, event.ptr()); | ||||||
| #endif | #endif | ||||||
|     m_queued_events.append({ receiver, move(event) }); |     m_queued_events.append({ receiver, move(event) }); | ||||||
|  | 
 | ||||||
|  |     if (current != m_server_process) | ||||||
|  |         m_server_process->request_wakeup(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void WSEventLoop::waitForEvent() | void WSEventLoop::wait_for_event() | ||||||
|  | { | ||||||
|  |     fd_set rfds; | ||||||
|  |     memset(&rfds, 0, sizeof(rfds)); | ||||||
|  |     auto bitmap = Bitmap::wrap((byte*)&rfds, FD_SETSIZE); | ||||||
|  |     bitmap.set(m_keyboard_fd, true); | ||||||
|  |     bitmap.set(m_mouse_fd, true); | ||||||
|  |     Syscall::SC_select_params params; | ||||||
|  |     params.nfds = max(m_keyboard_fd, m_mouse_fd) + 1; | ||||||
|  |     params.readfds = &rfds; | ||||||
|  |     params.writefds = nullptr; | ||||||
|  |     params.exceptfds = nullptr; | ||||||
|  |     params.timeout = nullptr; | ||||||
|  |     int rc = m_server_process->sys$select(¶ms); | ||||||
|  |     memory_barrier(); | ||||||
|  |     if (rc < 0) { | ||||||
|  |         ASSERT_NOT_REACHED(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     //if (bitmap.get(m_keyboard_fd))
 | ||||||
|  |         drain_keyboard(); | ||||||
|  |     //if (bitmap.get(m_mouse_fd))
 | ||||||
|  |         drain_mouse(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void WSEventLoop::drain_mouse() | ||||||
| { | { | ||||||
|     auto& mouse = PS2MouseDevice::the(); |  | ||||||
|     auto& screen = WSScreen::the(); |     auto& screen = WSScreen::the(); | ||||||
|  |     auto& mouse = PS2MouseDevice::the(); | ||||||
|     bool prev_left_button = screen.left_mouse_button_pressed(); |     bool prev_left_button = screen.left_mouse_button_pressed(); | ||||||
|     bool prev_right_button = screen.right_mouse_button_pressed(); |     bool prev_right_button = screen.right_mouse_button_pressed(); | ||||||
|     int dx = 0; |     int dx = 0; | ||||||
|     int dy = 0; |     int dy = 0; | ||||||
|     while (mouse.can_read(*m_server_process)) { |     while (mouse.can_read(*m_server_process)) { | ||||||
|         signed_byte data[3]; |         signed_byte data[3]; | ||||||
|         ssize_t nread = mouse.read(*m_server_process, (byte*)data, 3); |         ssize_t nread = mouse.read(*m_server_process, (byte*)data, sizeof(data)); | ||||||
|         ASSERT(nread == 3); |         ASSERT(nread == sizeof(data)); | ||||||
|         bool left_button = data[0] & 1; |         bool left_button = data[0] & 1; | ||||||
|         bool right_button = data[0] & 2; |         bool right_button = data[0] & 2; | ||||||
|         dx += data[1]; |         dx += data[1]; | ||||||
|  | @ -96,3 +135,18 @@ void WSEventLoop::waitForEvent() | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void WSEventLoop::drain_keyboard() | ||||||
|  | { | ||||||
|  |     auto& screen = WSScreen::the(); | ||||||
|  |     auto& keyboard = Keyboard::the(); | ||||||
|  |     while (keyboard.can_read(*m_server_process)) { | ||||||
|  |         byte data[2]; | ||||||
|  |         ssize_t nread = keyboard.read(*m_server_process, (byte*)data, sizeof(data)); | ||||||
|  |         ASSERT(nread == sizeof(data)); | ||||||
|  |         Keyboard::Key key; | ||||||
|  |         key.character = data[0]; | ||||||
|  |         key.modifiers = data[1]; | ||||||
|  |         screen.on_receive_keyboard_data(key); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -25,7 +25,9 @@ public: | ||||||
|     Process& server_process() { return *m_server_process; } |     Process& server_process() { return *m_server_process; } | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     void waitForEvent(); |     void wait_for_event(); | ||||||
|  |     void drain_mouse(); | ||||||
|  |     void drain_keyboard(); | ||||||
| 
 | 
 | ||||||
|     SpinLock m_lock; |     SpinLock m_lock; | ||||||
| 
 | 
 | ||||||
|  | @ -37,4 +39,7 @@ private: | ||||||
| 
 | 
 | ||||||
|     Process* m_server_process { nullptr }; |     Process* m_server_process { nullptr }; | ||||||
|     bool m_running { false }; |     bool m_running { false }; | ||||||
|  | 
 | ||||||
|  |     int m_keyboard_fd { -1 }; | ||||||
|  |     int m_mouse_fd { -1 }; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ class WSFrameBuffer final : public WSScreen { | ||||||
| public: | public: | ||||||
|     WSFrameBuffer(unsigned width, unsigned height); |     WSFrameBuffer(unsigned width, unsigned height); | ||||||
|     WSFrameBuffer(RGBA32*, unsigned width, unsigned height); |     WSFrameBuffer(RGBA32*, unsigned width, unsigned height); | ||||||
|     virtual ~WSFrameBuffer() override; |     ~WSFrameBuffer(); | ||||||
| 
 | 
 | ||||||
|     void show(); |     void show(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -25,8 +25,6 @@ WSScreen::WSScreen(unsigned width, unsigned height) | ||||||
|     s_the = this; |     s_the = this; | ||||||
| 
 | 
 | ||||||
|     m_cursor_location = rect().center(); |     m_cursor_location = rect().center(); | ||||||
| 
 |  | ||||||
|     Keyboard::the().set_client(this); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| WSScreen::~WSScreen() | WSScreen::~WSScreen() | ||||||
|  | @ -62,7 +60,7 @@ void WSScreen::on_receive_mouse_data(int dx, int dy, bool left_button, bool righ | ||||||
|         WSWindowManager::the().draw_cursor(); |         WSWindowManager::the().draw_cursor(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void WSScreen::on_key_pressed(Keyboard::Key key) | void WSScreen::on_receive_keyboard_data(Keyboard::Key key) | ||||||
| { | { | ||||||
|     auto event = make<KeyEvent>(WSEvent::KeyDown, 0); |     auto event = make<KeyEvent>(WSEvent::KeyDown, 0); | ||||||
|     int key_code = 0; |     int key_code = 0; | ||||||
|  |  | ||||||
|  | @ -4,9 +4,9 @@ | ||||||
| #include <Widgets/Size.h> | #include <Widgets/Size.h> | ||||||
| #include <Kernel/Keyboard.h> | #include <Kernel/Keyboard.h> | ||||||
| 
 | 
 | ||||||
| class WSScreen : public KeyboardClient { | class WSScreen { | ||||||
| public: | public: | ||||||
|     virtual ~WSScreen(); |     ~WSScreen(); | ||||||
| 
 | 
 | ||||||
|     int width() const { return m_width; } |     int width() const { return m_width; } | ||||||
|     int height() const { return m_height; } |     int height() const { return m_height; } | ||||||
|  | @ -23,14 +23,12 @@ public: | ||||||
|     bool right_mouse_button_pressed() const { return m_right_mouse_button_pressed; } |     bool right_mouse_button_pressed() const { return m_right_mouse_button_pressed; } | ||||||
| 
 | 
 | ||||||
|     void on_receive_mouse_data(int dx, int dy, bool left_button, bool right_button); |     void on_receive_mouse_data(int dx, int dy, bool left_button, bool right_button); | ||||||
|  |     void on_receive_keyboard_data(Keyboard::Key); | ||||||
| 
 | 
 | ||||||
| protected: | protected: | ||||||
|     WSScreen(unsigned width, unsigned height); |     WSScreen(unsigned width, unsigned height); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     // ^KeyboardClient
 |  | ||||||
|     virtual void on_key_pressed(Keyboard::Key) final; |  | ||||||
| 
 |  | ||||||
|     int m_width { 0 }; |     int m_width { 0 }; | ||||||
|     int m_height { 0 }; |     int m_height { 0 }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling