forked from Bananymous/banan-os
Userspace: Start work on GUI and WindowServer
Current implementation can create custom windows and each window has its own framebuffer. When window wants to write its framebuffer to the screen it will send a message to the WindowServer using unix sockets.
This commit is contained in:
@@ -21,6 +21,7 @@ add_subdirectory(bootloader)
|
||||
add_subdirectory(BAN)
|
||||
add_subdirectory(libc)
|
||||
add_subdirectory(LibELF)
|
||||
add_subdirectory(LibGUI)
|
||||
add_subdirectory(LibInput)
|
||||
add_subdirectory(userspace)
|
||||
|
||||
@@ -34,6 +35,7 @@ add_custom_target(headers
|
||||
DEPENDS ban-headers
|
||||
DEPENDS libc-headers
|
||||
DEPENDS libelf-headers
|
||||
DEPENDS libgui-headers
|
||||
DEPENDS libinput-headers
|
||||
)
|
||||
|
||||
@@ -43,6 +45,7 @@ add_custom_target(install-sysroot
|
||||
DEPENDS libc-install
|
||||
DEPENDS userspace-install
|
||||
DEPENDS libelf-install
|
||||
DEPENDS libgui-install
|
||||
DEPENDS libinput-install
|
||||
)
|
||||
|
||||
|
||||
24
LibGUI/CMakeLists.txt
Normal file
24
LibGUI/CMakeLists.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
cmake_minimum_required(VERSION 3.26)
|
||||
|
||||
project(libgui CXX)
|
||||
|
||||
set(LIBGUI_SOURCES
|
||||
Window.cpp
|
||||
)
|
||||
|
||||
add_custom_target(libgui-headers
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different ${CMAKE_CURRENT_SOURCE_DIR}/include/ ${BANAN_INCLUDE}/
|
||||
DEPENDS sysroot
|
||||
)
|
||||
|
||||
add_library(libgui ${LIBGUI_SOURCES})
|
||||
add_dependencies(libgui headers libc-install)
|
||||
target_link_libraries(libgui PUBLIC libc)
|
||||
|
||||
add_custom_target(libgui-install
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/libgui.a ${BANAN_LIB}/
|
||||
DEPENDS libgui
|
||||
BYPRODUCTS ${BANAN_LIB}/libgui.a
|
||||
)
|
||||
|
||||
set(CMAKE_STATIC_LIBRARY_PREFIX "")
|
||||
115
LibGUI/Window.cpp
Normal file
115
LibGUI/Window.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#include "LibGUI/Window.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/banan-os.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace LibGUI
|
||||
{
|
||||
|
||||
Window::~Window()
|
||||
{
|
||||
munmap(m_framebuffer, m_width * m_height * 4);
|
||||
close(m_server_fd);
|
||||
}
|
||||
|
||||
BAN::ErrorOr<BAN::UniqPtr<Window>> Window::create(uint32_t width, uint32_t height)
|
||||
{
|
||||
int server_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
|
||||
if (server_fd == -1)
|
||||
return BAN::Error::from_errno(errno);
|
||||
|
||||
sockaddr_un server_address;
|
||||
server_address.sun_family = AF_UNIX;
|
||||
strcpy(server_address.sun_path, s_window_server_socket.data());
|
||||
if (connect(server_fd, (sockaddr*)&server_address, sizeof(server_address)) == -1)
|
||||
{
|
||||
close(server_fd);
|
||||
return BAN::Error::from_errno(errno);
|
||||
}
|
||||
|
||||
WindowCreatePacket packet;
|
||||
packet.width = width;
|
||||
packet.height = height;
|
||||
if (send(server_fd, &packet, sizeof(packet), 0) != sizeof(packet))
|
||||
{
|
||||
close(server_fd);
|
||||
return BAN::Error::from_errno(errno);
|
||||
}
|
||||
|
||||
WindowCreateResponse response;
|
||||
if (recv(server_fd, &response, sizeof(response), 0) != sizeof(response))
|
||||
{
|
||||
close(server_fd);
|
||||
return BAN::Error::from_errno(errno);
|
||||
}
|
||||
|
||||
void* framebuffer_addr = smo_map(response.framebuffer_smo_key);
|
||||
if (framebuffer_addr == nullptr)
|
||||
{
|
||||
close(server_fd);
|
||||
return BAN::Error::from_errno(errno);
|
||||
}
|
||||
|
||||
return TRY(BAN::UniqPtr<Window>::create(
|
||||
server_fd,
|
||||
static_cast<uint32_t*>(framebuffer_addr),
|
||||
width,
|
||||
height
|
||||
));
|
||||
}
|
||||
|
||||
bool Window::invalidate()
|
||||
{
|
||||
WindowInvalidatePacket packet;
|
||||
packet.x = 0;
|
||||
packet.y = 0;
|
||||
packet.width = m_width;
|
||||
packet.height = m_height;
|
||||
return send(m_server_fd, &packet, sizeof(packet), 0) == sizeof(packet);
|
||||
}
|
||||
|
||||
void Window::poll_events()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(m_server_fd, &fds);
|
||||
timeval timeout { .tv_sec = 0, .tv_usec = 0 };
|
||||
select(m_server_fd + 1, &fds, nullptr, nullptr, &timeout);
|
||||
|
||||
if (!FD_ISSET(m_server_fd, &fds))
|
||||
break;
|
||||
|
||||
EventPacket packet;
|
||||
if (recv(m_server_fd, &packet, sizeof(packet), 0) <= 0)
|
||||
break;
|
||||
|
||||
switch (packet.type)
|
||||
{
|
||||
case EventPacket::Type::KeyEvent:
|
||||
if (m_key_event_callback)
|
||||
m_key_event_callback(packet.key_event);
|
||||
break;
|
||||
case EventPacket::Type::MouseButtonEvent:
|
||||
if (m_mouse_button_event_callback)
|
||||
m_mouse_button_event_callback(packet.mouse_button_event);
|
||||
break;
|
||||
case EventPacket::Type::MouseMoveEvent:
|
||||
if (m_mouse_move_event_callback)
|
||||
m_mouse_move_event_callback(packet.mouse_move_event);
|
||||
break;
|
||||
case EventPacket::Type::MouseScrollEvent:
|
||||
if (m_mouse_scroll_event_callback)
|
||||
m_mouse_scroll_event_callback(packet.mouse_scroll_event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
142
LibGUI/include/LibGUI/Window.h
Normal file
142
LibGUI/include/LibGUI/Window.h
Normal file
@@ -0,0 +1,142 @@
|
||||
#pragma once
|
||||
|
||||
#include <BAN/Function.h>
|
||||
#include <BAN/StringView.h>
|
||||
#include <BAN/UniqPtr.h>
|
||||
|
||||
#include <LibInput/KeyEvent.h>
|
||||
#include <LibInput/MouseEvent.h>
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace LibGUI
|
||||
{
|
||||
|
||||
static constexpr BAN::StringView s_window_server_socket = "/tmp/window-server.socket"sv;
|
||||
|
||||
enum WindowPacketType : uint8_t
|
||||
{
|
||||
INVALID,
|
||||
CreateWindow,
|
||||
Invalidate,
|
||||
};
|
||||
|
||||
struct WindowCreatePacket
|
||||
{
|
||||
WindowPacketType type = WindowPacketType::CreateWindow;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
};
|
||||
|
||||
struct WindowInvalidatePacket
|
||||
{
|
||||
WindowPacketType type = WindowPacketType::Invalidate;
|
||||
uint32_t x;
|
||||
uint32_t y;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
};
|
||||
|
||||
struct WindowCreateResponse
|
||||
{
|
||||
long framebuffer_smo_key;
|
||||
};
|
||||
|
||||
struct WindowPacket
|
||||
{
|
||||
WindowPacket()
|
||||
: type(WindowPacketType::INVALID)
|
||||
{ }
|
||||
|
||||
union
|
||||
{
|
||||
WindowPacketType type;
|
||||
WindowCreatePacket create;
|
||||
WindowInvalidatePacket invalidate;
|
||||
};
|
||||
};
|
||||
|
||||
struct EventPacket
|
||||
{
|
||||
enum class Type : uint8_t
|
||||
{
|
||||
KeyEvent,
|
||||
MouseButtonEvent,
|
||||
MouseMoveEvent,
|
||||
MouseScrollEvent,
|
||||
};
|
||||
using KeyEvent = LibInput::KeyEvent;
|
||||
using MouseButton = LibInput::MouseButton;
|
||||
struct MouseButtonEvent
|
||||
{
|
||||
MouseButton button;
|
||||
bool pressed;
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
};
|
||||
struct MouseMoveEvent
|
||||
{
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
};
|
||||
using MouseScrollEvent = LibInput::MouseScrollEvent;
|
||||
|
||||
Type type;
|
||||
union
|
||||
{
|
||||
KeyEvent key_event;
|
||||
MouseButtonEvent mouse_button_event;
|
||||
MouseMoveEvent mouse_move_event;
|
||||
MouseScrollEvent mouse_scroll_event;
|
||||
};
|
||||
};
|
||||
|
||||
class Window
|
||||
{
|
||||
public:
|
||||
~Window();
|
||||
|
||||
static BAN::ErrorOr<BAN::UniqPtr<Window>> create(uint32_t width, uint32_t height);
|
||||
|
||||
void set_pixel(uint32_t x, uint32_t y, uint32_t color)
|
||||
{
|
||||
ASSERT(x < m_width);
|
||||
ASSERT(y < m_height);
|
||||
m_framebuffer[y * m_width + x] = color;
|
||||
}
|
||||
|
||||
bool invalidate();
|
||||
|
||||
uint32_t width() const { return m_width; }
|
||||
uint32_t height() const { return m_height; }
|
||||
|
||||
void poll_events();
|
||||
void set_key_event_callback(BAN::Function<void(EventPacket::KeyEvent)> callback) { m_key_event_callback = callback; }
|
||||
void set_mouse_button_event_callback(BAN::Function<void(EventPacket::MouseButtonEvent)> callback) { m_mouse_button_event_callback = callback; }
|
||||
void set_mouse_move_event_callback(BAN::Function<void(EventPacket::MouseMoveEvent)> callback) { m_mouse_move_event_callback = callback; }
|
||||
void set_mouse_scroll_event_callback(BAN::Function<void(EventPacket::MouseScrollEvent)> callback) { m_mouse_scroll_event_callback = callback; }
|
||||
|
||||
private:
|
||||
Window(int server_fd, uint32_t* framebuffer, uint32_t width, uint32_t height)
|
||||
: m_server_fd(server_fd)
|
||||
, m_framebuffer(framebuffer)
|
||||
, m_width(width)
|
||||
, m_height(height)
|
||||
{ }
|
||||
|
||||
private:
|
||||
int m_server_fd;
|
||||
uint32_t* m_framebuffer;
|
||||
uint32_t m_width;
|
||||
uint32_t m_height;
|
||||
|
||||
BAN::Function<void(EventPacket::KeyEvent)> m_key_event_callback;
|
||||
BAN::Function<void(EventPacket::MouseButtonEvent)> m_mouse_button_event_callback;
|
||||
BAN::Function<void(EventPacket::MouseMoveEvent)> m_mouse_move_event_callback;
|
||||
BAN::Function<void(EventPacket::MouseScrollEvent)> m_mouse_scroll_event_callback;
|
||||
|
||||
friend class BAN::UniqPtr<Window>;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -39,9 +39,11 @@ set(USERSPACE_PROJECTS
|
||||
test-tcp
|
||||
test-udp
|
||||
test-unix-socket
|
||||
test-window
|
||||
touch
|
||||
u8sum
|
||||
whoami
|
||||
WindowServer
|
||||
yes
|
||||
)
|
||||
|
||||
|
||||
18
userspace/WindowServer/CMakeLists.txt
Normal file
18
userspace/WindowServer/CMakeLists.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
cmake_minimum_required(VERSION 3.26)
|
||||
|
||||
project(WindowServer CXX)
|
||||
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
Framebuffer.cpp
|
||||
WindowServer.cpp
|
||||
)
|
||||
|
||||
add_executable(WindowServer ${SOURCES})
|
||||
target_compile_options(WindowServer PUBLIC -O2 -g)
|
||||
target_link_libraries(WindowServer PUBLIC libc ban libgui libinput)
|
||||
|
||||
add_custom_target(WindowServer-install
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/WindowServer ${BANAN_BIN}/
|
||||
DEPENDS WindowServer
|
||||
)
|
||||
35
userspace/WindowServer/Cursor.h
Normal file
35
userspace/WindowServer/Cursor.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/* GIMP header image file format (RGB) */
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
static int32_t s_cursor_width = 17;
|
||||
static int32_t s_cursor_height = 26;
|
||||
static const char* s_cursor_data =
|
||||
"!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
|
||||
"`Q$`!!!!!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
|
||||
"`Q$``Q$`!!!!````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
|
||||
"`Q$``Q$``Q$`!!!!````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
|
||||
"`Q$``Q$``Q$``Q$`!!!!````````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
|
||||
"`Q$``Q$``Q$``Q$``Q$`!!!!````````````````!!!!`Q$``Q$``Q$``Q$``Q$`"
|
||||
"`Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````````!!!!`Q$``Q$``Q$`"
|
||||
"`Q$``Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````````````!!!!`Q$`"
|
||||
"`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````````````````"
|
||||
"!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````````````"
|
||||
"````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````````"
|
||||
"````````````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````"
|
||||
"````````````````````````!!!!`Q$``Q$``Q$``Q$``Q$`!!!!````````````"
|
||||
"````````````````````````````````!!!!`Q$``Q$``Q$``Q$`!!!!````````"
|
||||
"````````````````````````````````````````!!!!`Q$``Q$``Q$`!!!!````"
|
||||
"````````````````````````````````````````````````!!!!`Q$``Q$`!!!!"
|
||||
"````````````````````````````````````````````````````````!!!!`Q$`"
|
||||
"!!!!````````````````````````````````!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||
"!!!!!!!!````````````````````````````````!!!!`Q$``Q$``Q$``Q$``Q$`"
|
||||
"`Q$``Q$`!!!!````````````````!!!!````````````````!!!!`Q$``Q$``Q$`"
|
||||
"`Q$``Q$``Q$`!!!!````````````!!!!`Q$`!!!!````````````!!!!`Q$``Q$`"
|
||||
"`Q$``Q$``Q$``Q$`!!!!````````!!!!`Q$``Q$`!!!!````````````````!!!!"
|
||||
"`Q$``Q$``Q$``Q$``Q$`!!!!````!!!!`Q$``Q$``Q$``Q$`!!!!````````````"
|
||||
"!!!!`Q$``Q$``Q$``Q$``Q$`!!!!!!!!`Q$``Q$``Q$``Q$``Q$`!!!!````````"
|
||||
"````````!!!!`Q$``Q$``Q$``Q$`!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$`!!!!"
|
||||
"````````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
|
||||
"!!!!````````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
|
||||
"`Q$``Q$`!!!!!!!!!!!!`Q$``Q$``Q$``Q$``Q$`";
|
||||
45
userspace/WindowServer/Framebuffer.cpp
Normal file
45
userspace/WindowServer/Framebuffer.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "Framebuffer.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/framebuffer.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
Framebuffer open_framebuffer()
|
||||
{
|
||||
int framebuffer_fd = open("/dev/fb0", O_RDWR);
|
||||
if (framebuffer_fd == -1)
|
||||
{
|
||||
perror("open");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
framebuffer_info_t framebuffer_info;
|
||||
if (pread(framebuffer_fd, &framebuffer_info, sizeof(framebuffer_info), -1) == -1)
|
||||
{
|
||||
perror("pread");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const size_t framebuffer_bytes = framebuffer_info.width * framebuffer_info.height * (BANAN_FB_BPP / 8);
|
||||
|
||||
uint32_t* framebuffer_mmap = (uint32_t*)mmap(NULL, framebuffer_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, framebuffer_fd, 0);
|
||||
if (framebuffer_mmap == MAP_FAILED)
|
||||
{
|
||||
perror("mmap");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
memset(framebuffer_mmap, 0, framebuffer_bytes);
|
||||
msync(framebuffer_mmap, framebuffer_bytes, MS_SYNC);
|
||||
|
||||
Framebuffer framebuffer;
|
||||
framebuffer.fd = framebuffer_fd;
|
||||
framebuffer.mmap = framebuffer_mmap;
|
||||
framebuffer.width = framebuffer_info.width;
|
||||
framebuffer.height = framebuffer_info.height;
|
||||
framebuffer.bpp = BANAN_FB_BPP;
|
||||
return framebuffer;
|
||||
}
|
||||
16
userspace/WindowServer/Framebuffer.h
Normal file
16
userspace/WindowServer/Framebuffer.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "Utils.h"
|
||||
|
||||
struct Framebuffer
|
||||
{
|
||||
int fd;
|
||||
uint32_t* mmap;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
uint8_t bpp;
|
||||
|
||||
Rectangle area() const { return { 0, 0, width, height }; }
|
||||
};
|
||||
|
||||
Framebuffer open_framebuffer();
|
||||
59
userspace/WindowServer/Utils.h
Normal file
59
userspace/WindowServer/Utils.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <BAN/Math.h>
|
||||
#include <BAN/Optional.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct Position
|
||||
{
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
};
|
||||
|
||||
struct Rectangle
|
||||
{
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
|
||||
bool contains(Position position) const
|
||||
{
|
||||
if (position.x < x || position.x >= x + width)
|
||||
return false;
|
||||
if (position.y < y || position.y >= y + height)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
BAN::Optional<Rectangle> get_overlap(Rectangle other) const
|
||||
{
|
||||
const auto min_x = BAN::Math::max(x, other.x);
|
||||
const auto min_y = BAN::Math::max(y, other.y);
|
||||
const auto max_x = BAN::Math::min(x + width, other.x + other.width);
|
||||
const auto max_y = BAN::Math::min(y + height, other.y + other.height);
|
||||
if (min_x >= max_x || min_y >= max_y)
|
||||
return {};
|
||||
return Rectangle {
|
||||
.x = min_x,
|
||||
.y = min_y,
|
||||
.width = max_x - min_x,
|
||||
.height = max_y - min_y,
|
||||
};
|
||||
}
|
||||
|
||||
Rectangle get_bounding_box(Rectangle other) const
|
||||
{
|
||||
const auto min_x = BAN::Math::min(x, other.x);
|
||||
const auto min_y = BAN::Math::min(y, other.y);
|
||||
const auto max_x = BAN::Math::max(x + width, other.x + other.width);
|
||||
const auto max_y = BAN::Math::max(y + height, other.y + other.height);
|
||||
return Rectangle {
|
||||
.x = min_x,
|
||||
.y = min_y,
|
||||
.width = max_x - min_x,
|
||||
.height = max_y - min_y,
|
||||
};
|
||||
}
|
||||
};
|
||||
45
userspace/WindowServer/Window.h
Normal file
45
userspace/WindowServer/Window.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "Utils.h"
|
||||
|
||||
#include <BAN/RefPtr.h>
|
||||
|
||||
class Window : public BAN::RefCounted<Window>
|
||||
{
|
||||
public:
|
||||
Window(int fd)
|
||||
: m_client_fd(fd)
|
||||
{ }
|
||||
|
||||
void set_position(Position position)
|
||||
{
|
||||
m_area.x = position.x;
|
||||
m_area.y = position.y;
|
||||
}
|
||||
|
||||
void set_size(Position size, uint32_t* fb_addr)
|
||||
{
|
||||
m_area.width = size.x;
|
||||
m_area.height = size.y;
|
||||
m_fb_addr = fb_addr;
|
||||
}
|
||||
|
||||
bool is_deleted() const { return m_deleted; }
|
||||
void mark_deleted() { m_deleted = true; }
|
||||
|
||||
int client_fd() const { return m_client_fd; }
|
||||
|
||||
int32_t x() const { return m_area.x; }
|
||||
int32_t y() const { return m_area.y; }
|
||||
uint32_t width() const { return m_area.width; }
|
||||
uint32_t height() const { return m_area.height; }
|
||||
Rectangle size() const { return { 0, 0, m_area.width, m_area.height }; }
|
||||
const Rectangle& area() const { return m_area; }
|
||||
const uint32_t* framebuffer() const { return m_fb_addr; }
|
||||
|
||||
private:
|
||||
const int m_client_fd { -1 };
|
||||
uint32_t* m_fb_addr { nullptr };
|
||||
Rectangle m_area { 0, 0, 0, 0 };
|
||||
bool m_deleted { false };
|
||||
};
|
||||
224
userspace/WindowServer/WindowServer.cpp
Normal file
224
userspace/WindowServer/WindowServer.cpp
Normal file
@@ -0,0 +1,224 @@
|
||||
#include "Cursor.h"
|
||||
#include "WindowServer.h"
|
||||
|
||||
#include <LibGUI/Window.h>
|
||||
#include <LibInput/KeyboardLayout.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
void WindowServer::add_window(int fd, BAN::RefPtr<Window> window)
|
||||
{
|
||||
MUST(m_windows_ordered.insert(0, window));
|
||||
MUST(m_windows.insert(fd, window));
|
||||
set_focused_window(window);
|
||||
}
|
||||
|
||||
void WindowServer::for_each_window(const BAN::Function<BAN::Iteration(int, Window&)>& callback)
|
||||
{
|
||||
BAN::Vector<int> deleted_windows;
|
||||
for (auto it = m_windows.begin(); it != m_windows.end(); it++)
|
||||
{
|
||||
auto ret = callback(it->key, *it->value);
|
||||
if (it->value->is_deleted())
|
||||
MUST(deleted_windows.push_back(it->key));
|
||||
if (ret == BAN::Iteration::Break)
|
||||
break;
|
||||
ASSERT(ret == BAN::Iteration::Continue);
|
||||
}
|
||||
for (int fd : deleted_windows)
|
||||
{
|
||||
auto window = m_windows[fd];
|
||||
m_windows.remove(fd);
|
||||
for (size_t i = 0; i < m_windows_ordered.size(); i++)
|
||||
{
|
||||
if (m_windows_ordered[i] == window)
|
||||
{
|
||||
m_windows_ordered.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WindowServer::on_key_event(LibInput::KeyEvent event)
|
||||
{
|
||||
// Mod key is not passed to clients
|
||||
if (event.key == LibInput::Key::Super)
|
||||
{
|
||||
m_is_mod_key_held = event.pressed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Quick hack to stop the window server
|
||||
if (event.pressed() && event.key == LibInput::Key::Escape)
|
||||
exit(0);
|
||||
|
||||
if (m_focused_window)
|
||||
{
|
||||
LibGUI::EventPacket packet;
|
||||
packet.type = LibGUI::EventPacket::Type::KeyEvent;
|
||||
packet.key_event = event;
|
||||
send(m_focused_window->client_fd(), &packet, sizeof(packet), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowServer::on_mouse_button(LibInput::MouseButtonEvent event)
|
||||
{
|
||||
BAN::RefPtr<Window> target_window;
|
||||
for (size_t i = m_windows_ordered.size(); i > 0; i--)
|
||||
{
|
||||
if (m_windows_ordered[i - 1]->area().contains(m_cursor))
|
||||
{
|
||||
target_window = m_windows_ordered[i - 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore mouse button events which are not on top of a window
|
||||
if (!target_window)
|
||||
return;
|
||||
|
||||
set_focused_window(target_window);
|
||||
|
||||
// Handle window moving when mod key is held
|
||||
if (m_is_mod_key_held && event.pressed && event.button == LibInput::MouseButton::Left && !m_is_moving_window)
|
||||
m_is_moving_window = true;
|
||||
else if (m_is_moving_window && !event.pressed)
|
||||
m_is_moving_window = false;
|
||||
else
|
||||
{
|
||||
// NOTE: we always have target window if code reaches here
|
||||
LibGUI::EventPacket packet;
|
||||
packet.type = LibGUI::EventPacket::Type::MouseButtonEvent;
|
||||
packet.mouse_button_event.button = event.button;
|
||||
packet.mouse_button_event.pressed = event.pressed;
|
||||
packet.mouse_button_event.x = m_cursor.x - m_focused_window->x();
|
||||
packet.mouse_button_event.y = m_cursor.y - m_focused_window->y();
|
||||
send(m_focused_window->client_fd(), &packet, sizeof(packet), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowServer::on_mouse_move(LibInput::MouseMoveEvent event)
|
||||
{
|
||||
Rectangle old_cursor { m_cursor.x, m_cursor.y, s_cursor_width, s_cursor_height };
|
||||
|
||||
const int32_t new_x = BAN::Math::clamp(m_cursor.x + event.rel_x, 0, m_framebuffer.width);
|
||||
const int32_t new_y = BAN::Math::clamp(m_cursor.y - event.rel_y, 0, m_framebuffer.height);
|
||||
|
||||
event.rel_x = new_x - m_cursor.x;
|
||||
event.rel_y = new_y - m_cursor.y;
|
||||
if (event.rel_x == 0 && event.rel_y == 0)
|
||||
return;
|
||||
|
||||
m_cursor.x = new_x;
|
||||
m_cursor.y = new_y;
|
||||
|
||||
Rectangle new_cursor { m_cursor.x, m_cursor.y, s_cursor_width, s_cursor_height };
|
||||
invalidate(old_cursor.get_bounding_box(old_cursor));
|
||||
invalidate(new_cursor.get_bounding_box(old_cursor));
|
||||
|
||||
if (m_is_moving_window)
|
||||
{
|
||||
auto old_window = m_focused_window->area();
|
||||
m_focused_window->set_position({
|
||||
m_focused_window->x() + event.rel_x,
|
||||
m_focused_window->y() + event.rel_y,
|
||||
});
|
||||
invalidate(old_window);
|
||||
invalidate(m_focused_window->area());
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_focused_window)
|
||||
{
|
||||
LibGUI::EventPacket packet;
|
||||
packet.type = LibGUI::EventPacket::Type::MouseMoveEvent;
|
||||
packet.mouse_move_event.x = m_cursor.x - m_focused_window->x();
|
||||
packet.mouse_move_event.y = m_cursor.y - m_focused_window->y();
|
||||
send(m_focused_window->client_fd(), &packet, sizeof(packet), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowServer::on_mouse_scroll(LibInput::MouseScrollEvent event)
|
||||
{
|
||||
if (m_focused_window)
|
||||
{
|
||||
LibGUI::EventPacket packet;
|
||||
packet.type = LibGUI::EventPacket::Type::MouseScrollEvent;
|
||||
packet.mouse_scroll_event = event;
|
||||
send(m_focused_window->client_fd(), &packet, sizeof(packet), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowServer::set_focused_window(BAN::RefPtr<Window> window)
|
||||
{
|
||||
if (m_focused_window == window)
|
||||
return;
|
||||
|
||||
for (size_t i = m_windows_ordered.size(); i > 0; i--)
|
||||
{
|
||||
if (m_windows_ordered[i - 1] == window)
|
||||
{
|
||||
m_focused_window = window;
|
||||
m_windows_ordered.remove(i - 1);
|
||||
MUST(m_windows_ordered.push_back(window));
|
||||
invalidate(window->area());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WindowServer::invalidate(Rectangle area)
|
||||
{
|
||||
auto fb_overlap = area.get_overlap(m_framebuffer.area());
|
||||
if (!fb_overlap.has_value())
|
||||
return;
|
||||
area = fb_overlap.release_value();
|
||||
|
||||
for (int32_t y = area.y; y < area.y + area.height; y++)
|
||||
memset(&m_framebuffer.mmap[y * m_framebuffer.width + area.x], 0, area.width * 4);
|
||||
|
||||
for (auto& pwindow : m_windows_ordered)
|
||||
{
|
||||
auto& window = *pwindow;
|
||||
|
||||
auto overlap = window.area().get_overlap(area);
|
||||
if (!overlap.has_value())
|
||||
continue;
|
||||
|
||||
const int32_t src_x = overlap->x - window.x();
|
||||
const int32_t src_y = overlap->y - window.y();
|
||||
for (int32_t y_off = 0; y_off < overlap->height; y_off++)
|
||||
memcpy(
|
||||
&m_framebuffer.mmap[(overlap->y + y_off) * m_framebuffer.width + overlap->x],
|
||||
&window.framebuffer()[(src_y + y_off) * window.width() + src_x],
|
||||
overlap->width * 4
|
||||
);
|
||||
}
|
||||
|
||||
Rectangle cursor { m_cursor.x, m_cursor.y, s_cursor_width, s_cursor_height };
|
||||
auto overlap = cursor.get_overlap(area);
|
||||
if (overlap.has_value())
|
||||
{
|
||||
for (int32_t dy = overlap->y - cursor.y; dy < overlap->height; dy++)
|
||||
{
|
||||
for (int32_t dx = overlap->x - cursor.x; dx < overlap->width; dx++)
|
||||
{
|
||||
const uint32_t offset = (dy * s_cursor_width + dx) * 4;
|
||||
uint32_t r = (((s_cursor_data[offset + 0] - 33) << 2) | ((s_cursor_data[offset + 1] - 33) >> 4));
|
||||
uint32_t g = ((((s_cursor_data[offset + 1] - 33) & 0xF) << 4) | ((s_cursor_data[offset + 2] - 33) >> 2));
|
||||
uint32_t b = ((((s_cursor_data[offset + 2] - 33) & 0x3) << 6) | ((s_cursor_data[offset + 3] - 33)));
|
||||
uint32_t color = (r << 16) | (g << 8) | b;
|
||||
if (color != 0xFF00FF)
|
||||
m_framebuffer.mmap[(overlap->y + dy) * m_framebuffer.width + (overlap->x + dx)] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uintptr_t mmap_start = reinterpret_cast<uintptr_t>(m_framebuffer.mmap) + area.y * m_framebuffer.width * 4;
|
||||
uintptr_t mmap_end = mmap_start + (area.height + 1) * m_framebuffer.width * 4;
|
||||
mmap_start &= ~(uintptr_t)0xFFF;
|
||||
msync(reinterpret_cast<void*>(mmap_start), mmap_end - mmap_start, MS_SYNC);
|
||||
}
|
||||
44
userspace/WindowServer/WindowServer.h
Normal file
44
userspace/WindowServer/WindowServer.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "Framebuffer.h"
|
||||
#include "Window.h"
|
||||
|
||||
#include <BAN/Function.h>
|
||||
#include <BAN/Iteration.h>
|
||||
#include <BAN/Vector.h>
|
||||
#include <BAN/HashMap.h>
|
||||
|
||||
#include <LibInput/KeyEvent.h>
|
||||
#include <LibInput/MouseEvent.h>
|
||||
|
||||
class WindowServer
|
||||
{
|
||||
public:
|
||||
WindowServer(Framebuffer& framebuffer)
|
||||
: m_framebuffer(framebuffer)
|
||||
, m_cursor({ framebuffer.width / 2, framebuffer.height / 2 })
|
||||
{
|
||||
invalidate(m_framebuffer.area());
|
||||
}
|
||||
|
||||
void add_window(int fd, BAN::RefPtr<Window> window);
|
||||
void for_each_window(const BAN::Function<BAN::Iteration(int, Window&)>& callback);
|
||||
|
||||
void on_key_event(LibInput::KeyEvent event);
|
||||
void on_mouse_button(LibInput::MouseButtonEvent event);
|
||||
void on_mouse_move(LibInput::MouseMoveEvent event);
|
||||
void on_mouse_scroll(LibInput::MouseScrollEvent event);
|
||||
|
||||
void set_focused_window(BAN::RefPtr<Window> window);
|
||||
void invalidate(Rectangle area);
|
||||
|
||||
private:
|
||||
Framebuffer& m_framebuffer;
|
||||
BAN::Vector<BAN::RefPtr<Window>> m_windows_ordered;
|
||||
BAN::HashMap<int, BAN::RefPtr<Window>> m_windows;
|
||||
|
||||
bool m_is_mod_key_held { false };
|
||||
bool m_is_moving_window { false };
|
||||
BAN::RefPtr<Window> m_focused_window;
|
||||
Position m_cursor;
|
||||
};
|
||||
267
userspace/WindowServer/main.cpp
Normal file
267
userspace/WindowServer/main.cpp
Normal file
@@ -0,0 +1,267 @@
|
||||
#include "WindowServer.h"
|
||||
|
||||
#include <BAN/Debug.h>
|
||||
|
||||
#include <LibGUI/Window.h>
|
||||
#include <LibInput/KeyboardLayout.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/banan-os.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int open_server_fd()
|
||||
{
|
||||
struct stat st;
|
||||
if (stat(LibGUI::s_window_server_socket.data(), &st) != -1)
|
||||
unlink(LibGUI::s_window_server_socket.data());
|
||||
|
||||
int server_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
|
||||
if (server_fd == -1)
|
||||
{
|
||||
perror("socket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
sockaddr_un server_addr;
|
||||
server_addr.sun_family = AF_UNIX;
|
||||
strcpy(server_addr.sun_path, LibGUI::s_window_server_socket.data());
|
||||
if (bind(server_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1)
|
||||
{
|
||||
perror("bind");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (chmod("/tmp/resolver.sock", 0777) == -1)
|
||||
{
|
||||
perror("chmod");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (listen(server_fd, SOMAXCONN) == -1)
|
||||
{
|
||||
perror("listen");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return server_fd;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
srand(time(nullptr));
|
||||
|
||||
int server_fd = open_server_fd();
|
||||
auto framebuffer = open_framebuffer();
|
||||
if (framebuffer.bpp != 32)
|
||||
{
|
||||
dwarnln("Window server requires 32 bpp");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (tty_ctrl(STDIN_FILENO, TTY_CMD_UNSET, TTY_FLAG_ENABLE_INPUT) == -1)
|
||||
{
|
||||
perror("tty_ctrl");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
atexit([]() { tty_ctrl(STDIN_FILENO, TTY_CMD_SET, TTY_FLAG_ENABLE_INPUT); });
|
||||
|
||||
MUST(LibInput::KeyboardLayout::initialize());
|
||||
MUST(LibInput::KeyboardLayout::get().load_from_file("/usr/share/keymaps/us.keymap"sv));
|
||||
|
||||
int keyboard_fd = open("/dev/input0", O_RDONLY);
|
||||
if (keyboard_fd == -1)
|
||||
perror("open");
|
||||
|
||||
int mouse_fd = open("/dev/input1", O_RDONLY);
|
||||
if (mouse_fd == -1)
|
||||
perror("open");
|
||||
|
||||
dprintln("Window server started");
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (fork() == 0)
|
||||
{
|
||||
execl("/bin/test-window", "test-window", NULL);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
WindowServer window_server(framebuffer);
|
||||
for (;;)
|
||||
{
|
||||
int max_socket = server_fd;
|
||||
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(server_fd, &fds);
|
||||
if (keyboard_fd != -1)
|
||||
{
|
||||
FD_SET(keyboard_fd, &fds);
|
||||
max_socket = BAN::Math::max(max_socket, keyboard_fd);
|
||||
}
|
||||
if (mouse_fd != -1)
|
||||
{
|
||||
FD_SET(mouse_fd, &fds);
|
||||
max_socket = BAN::Math::max(max_socket, mouse_fd);
|
||||
}
|
||||
window_server.for_each_window(
|
||||
[&](int fd, Window&) -> BAN::Iteration
|
||||
{
|
||||
FD_SET(fd, &fds);
|
||||
max_socket = BAN::Math::max(max_socket, fd);
|
||||
return BAN::Iteration::Continue;
|
||||
}
|
||||
);
|
||||
|
||||
if (select(max_socket + 1, &fds, nullptr, nullptr, nullptr) == -1)
|
||||
{
|
||||
dwarnln("select: {}", strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
if (FD_ISSET(server_fd, &fds))
|
||||
{
|
||||
int window_fd = accept(server_fd, nullptr, nullptr);
|
||||
if (window_fd == -1)
|
||||
{
|
||||
dwarnln("accept: {}", strerror(errno));
|
||||
continue;
|
||||
}
|
||||
auto window = MUST(BAN::RefPtr<Window>::create(window_fd));
|
||||
window_server.add_window(window_fd, window);
|
||||
}
|
||||
|
||||
if (keyboard_fd != -1 && FD_ISSET(keyboard_fd, &fds))
|
||||
{
|
||||
LibInput::RawKeyEvent event;
|
||||
if (read(keyboard_fd, &event, sizeof(event)) == -1)
|
||||
{
|
||||
perror("read");
|
||||
continue;
|
||||
}
|
||||
window_server.on_key_event(LibInput::KeyboardLayout::get().key_event_from_raw(event));
|
||||
}
|
||||
|
||||
if (mouse_fd != -1 && FD_ISSET(mouse_fd, &fds))
|
||||
{
|
||||
LibInput::MouseEvent event;
|
||||
if (read(mouse_fd, &event, sizeof(event)) == -1)
|
||||
{
|
||||
perror("read");
|
||||
continue;
|
||||
}
|
||||
switch (event.type)
|
||||
{
|
||||
case LibInput::MouseEventType::MouseButtonEvent:
|
||||
window_server.on_mouse_button(event.button_event);
|
||||
break;
|
||||
case LibInput::MouseEventType::MouseMoveEvent:
|
||||
window_server.on_mouse_move(event.move_event);
|
||||
break;
|
||||
case LibInput::MouseEventType::MouseScrollEvent:
|
||||
window_server.on_mouse_scroll(event.scroll_event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
window_server.for_each_window(
|
||||
[&](int fd, Window& window) -> BAN::Iteration
|
||||
{
|
||||
if (!FD_ISSET(fd, &fds))
|
||||
return BAN::Iteration::Continue;
|
||||
|
||||
LibGUI::WindowPacket packet;
|
||||
ssize_t nrecv = recv(fd, &packet, sizeof(packet), 0);
|
||||
if (nrecv < 0)
|
||||
dwarnln("recv: {}", strerror(errno));
|
||||
if (nrecv <= 0)
|
||||
{
|
||||
window.mark_deleted();
|
||||
return BAN::Iteration::Continue;
|
||||
}
|
||||
|
||||
switch (packet.type)
|
||||
{
|
||||
case LibGUI::WindowPacketType::CreateWindow:
|
||||
{
|
||||
if (nrecv != sizeof(LibGUI::WindowCreatePacket))
|
||||
{
|
||||
dwarnln("Invalid WindowCreate packet size");
|
||||
break;
|
||||
}
|
||||
|
||||
const size_t window_fb_bytes = packet.create.width * packet.create.height * 4;
|
||||
|
||||
long smo_key = smo_create(window_fb_bytes, PROT_READ | PROT_WRITE);
|
||||
if (smo_key == -1)
|
||||
{
|
||||
dwarnln("smo_create: {}", strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
void* smo_address = smo_map(smo_key);
|
||||
if (smo_address == nullptr)
|
||||
{
|
||||
dwarnln("smo_map: {}", strerror(errno));
|
||||
break;
|
||||
}
|
||||
memset(smo_address, 0, window_fb_bytes);
|
||||
|
||||
LibGUI::WindowCreateResponse response;
|
||||
response.framebuffer_smo_key = smo_key;
|
||||
if (send(fd, &response, sizeof(response), 0) != sizeof(response))
|
||||
{
|
||||
dwarnln("send: {}", strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
window.set_size({
|
||||
static_cast<int32_t>(packet.create.width),
|
||||
static_cast<int32_t>(packet.create.height)
|
||||
}, reinterpret_cast<uint32_t*>(smo_address));
|
||||
window.set_position({
|
||||
static_cast<int32_t>(window.width() / 2),
|
||||
static_cast<int32_t>(window.height() / 2)
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case LibGUI::WindowPacketType::Invalidate:
|
||||
{
|
||||
if (nrecv != sizeof(LibGUI::WindowInvalidatePacket))
|
||||
{
|
||||
dwarnln("Invalid Invalidate packet size");
|
||||
break;
|
||||
}
|
||||
if (packet.invalidate.x + packet.invalidate.width > window.width() || packet.invalidate.y + packet.invalidate.height > window.height())
|
||||
{
|
||||
dwarnln("Invalid Invalidate packet parameters");
|
||||
break;
|
||||
}
|
||||
|
||||
window_server.invalidate({
|
||||
window.x() + static_cast<int32_t>(packet.invalidate.x),
|
||||
window.y() + static_cast<int32_t>(packet.invalidate.y),
|
||||
static_cast<int32_t>(packet.invalidate.width),
|
||||
static_cast<int32_t>(packet.invalidate.height),
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
dwarnln("Invalid window packet from {}", fd);
|
||||
}
|
||||
|
||||
return BAN::Iteration::Continue;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
16
userspace/test-window/CMakeLists.txt
Normal file
16
userspace/test-window/CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
cmake_minimum_required(VERSION 3.26)
|
||||
|
||||
project(test-window CXX)
|
||||
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(test-window ${SOURCES})
|
||||
target_compile_options(test-window PUBLIC -O2 -g)
|
||||
target_link_libraries(test-window PUBLIC libc ban libgui)
|
||||
|
||||
add_custom_target(test-window-install
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/test-window ${BANAN_BIN}/
|
||||
DEPENDS test-window
|
||||
)
|
||||
61
userspace/test-window/main.cpp
Normal file
61
userspace/test-window/main.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#include <BAN/Debug.h>
|
||||
|
||||
#include <LibGUI/Window.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void randomize_color(BAN::UniqPtr<LibGUI::Window>& window)
|
||||
{
|
||||
uint32_t color = ((rand() % 255) << 16) | ((rand() % 255) << 8) | ((rand() % 255) << 0);
|
||||
for (uint32_t y = 0; y < window->height(); y++)
|
||||
for (uint32_t x = 0; x < window->width(); x++)
|
||||
window->set_pixel(x, y, color);
|
||||
window->invalidate();
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
srand(ts.tv_nsec);
|
||||
|
||||
auto window_or_error = LibGUI::Window::create(300, 200);
|
||||
if (window_or_error.is_error())
|
||||
{
|
||||
dprintln("{}", window_or_error.error());
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto window = window_or_error.release_value();
|
||||
window->set_mouse_button_event_callback(
|
||||
[&](LibGUI::EventPacket::MouseButtonEvent event)
|
||||
{
|
||||
if (event.pressed && event.button == LibGUI::EventPacket::MouseButton::Left)
|
||||
randomize_color(window);
|
||||
|
||||
const char* button;
|
||||
switch (event.button)
|
||||
{
|
||||
case LibGUI::EventPacket::MouseButton::Left: button = "left"; break;
|
||||
case LibGUI::EventPacket::MouseButton::Right: button = "right"; break;
|
||||
case LibGUI::EventPacket::MouseButton::Middle: button = "middle"; break;
|
||||
case LibGUI::EventPacket::MouseButton::Extra1: button = "extra1"; break;
|
||||
case LibGUI::EventPacket::MouseButton::Extra2: button = "extra2"; break;
|
||||
}
|
||||
dprintln("mouse button '{}' {} at {}, {}", button, event.pressed ? "pressed" : "released", event.x, event.y);
|
||||
}
|
||||
);
|
||||
|
||||
randomize_color(window);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
window->poll_events();
|
||||
|
||||
timespec duration;
|
||||
duration.tv_sec = 0;
|
||||
duration.tv_nsec = 16'666'666;
|
||||
nanosleep(&duration, nullptr);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user