Is it possible to create a client window on Wayland without having to develop a compositor? #54
Replies: 5 comments 13 replies
-
Hello, funny, I'm just developing a Wayland backend right now (2.0.0 branch). So yes, it's possible. You can override the The only limitation is that you can only create a toplevel window and nothing more, and it requires the compositor to support SSD for resizing, dragging, etc. For example, this is how the Currently, you have to run the compositor with But I'm going to make it default to trying to use those backends if |
Beta Was this translation helpful? Give feedback.
-
Here’s a preview of the APIs for the SDK I'm currently developing #include <HWindow.h>
#include <iostream>
int main() {
// Create an HWindow object with default size
Ht::HWindow myWindow;
// Retrieve and display the base size of the window
Ht::HSize size = myWindow.baseSize();
std::cout << "Window base size is " << size.width() << "x" << size.height() << std::endl;
Ht::ScreenOrientation orientation = myWindow.contentOrientation();
if (orientation == Ht::ScreenOrientation::PortraitOrientation) {
std::cout << "The orientation is Portrait." << std::endl;
}
// Retrieve the cursor from the window
Ht::HCursor cursor = myWindow.cursor();
if (cursor.shape() == Ht::CursorShape::Arrow) {
std::cout << "The cursor shape is Arrow." << std::endl;
}
Ht::HString path = myWindow.filePath();
std::cout << "Window file path: " << path << std::endl;
Ht::WindowFlags flags = myWindow.flags();
if (flags == Ht::WindowFlags::Window) {
std::cout << "The flag is Window." << std::endl;
}
// Get the frame geometry of the window
Ht::HRect frame = myWindow.frameGeometry();
// Print the frame geometry
std::cout << "Frame Geometry: x=" << frame.x() << ", y=" << frame.y()
<< ", width=" << frame.width() << ", height=" << frame.height() << std::endl;
// Get the frame geometry of the window
Ht::HMargins margins = myWindow.frameMargins();
// Print the frame geometry
std::cout << "Frame Margins: top=" << margins.top() << ", right=" << margins.right()
<< ", bottom=" << margins.bottom() << ", left=" << margins.left() << std::endl;
if (myWindow.isTopLevel() == true) {
std::cout << "Window is toplevel = true " << std::endl;
}
Ht::HSize maxsize = myWindow.maximumSize();
std::cout << "Window maximumSize width is " << maxsize.width() << " height " << maxsize.height() << std::endl;
Ht::HSize minsize = myWindow.minimumSize();
std::cout << "Window minimumSize width is " << minsize.width() << " height " << minsize.height() << std::endl;
int mh = myWindow.maximumHeight();
std::cout << "Window maximumHeight is " << mh << std::endl;
std::cout << "Window opacity is " << myWindow.opacity() << std::endl;
myWindow.setOpacity(0.5);
std::cout << "Window opacity is " << myWindow.opacity() << std::endl;
std::cout << "Window title is " << myWindow.title() << std::endl;
Ht::HString title = "My Window";
myWindow.setTitle(title);
std::cout << "Window title is " << myWindow.title() << std::endl;
return 0;
} |
Beta Was this translation helpful? Give feedback.
-
Collaborating would be a fantastic opportunity!
Here is some example code for two classes i wrote yesterday to show the type of ease of use i am trying to emplament. As you will notice i still haven't settled on a name for the apis i have been working on. ProcessManagerThis is one of many components for writing a lightweight alternative to systemd. // ProcessManager.cpp
#include <ProcessManager.h>
#include <iostream>
#include <stdexcept>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
ProcessManager::ProcessManager(ProcessType type) : type(type) {}
pid_t ProcessManager::start(const std::string& command) {
pid_t pid = fork();
if (pid == -1) {
throw std::runtime_error("Failed to fork.");
} else if (pid == 0) {
if (type == ProcessType::Daemon) {
setsid(); // Detach from the controlling terminal
close(STDIN_FILENO); // Close standard input
close(STDOUT_FILENO); // Close standard output
close(STDERR_FILENO); // Close standard error
}
execl("/bin/sh", "sh", "-c", command.c_str(), nullptr);
exit(EXIT_FAILURE); // If exec fails
}
processes[pid] = command;
return pid;
}
void ProcessManager::stop(pid_t pid) {
if (processes.find(pid) != processes.end()) {
kill(pid, SIGTERM);
waitpid(pid, nullptr, 0);
processes.erase(pid);
}
}
void ProcessManager::pause(pid_t pid) {
if (processes.find(pid) != processes.end()) {
kill(pid, SIGSTOP);
}
}
void ProcessManager::resume(pid_t pid) {
if (processes.find(pid) != processes.end()) {
kill(pid, SIGCONT);
}
}
pid_t ProcessManager::restart(pid_t pid) {
if (processes.find(pid) != processes.end()) {
std::string command = processes[pid];
stop(pid);
return start(command);
} else {
std::cerr << "Failed to restart: No such process found." << std::endl;
return -1;
}
}
Example Usage#include <ProcessManager.h>
#include <iostream>
#include <ostream>
#include <sys/wait.h>
int main() {
ProcessManager pm(ProcessType::Daemon);
try {
pid_t pid = pm.start("gnome-text-editor");
std::cout << "Process started with PID: " << pid << std::endl;
sleep(5); // Wait for a bit
pm.pause(pid);
std::cout << "Process paused." << std::endl;
sleep(5);
pm.resume(pid);
std::cout << "Process resumed." << std::endl;
sleep(5);
pid = pm.restart(pid);
std::cout << "Process restarted." << std::endl;
sleep(5);
pm.stop(pid);
std::cout << "Process stopped." << std::endl;
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
} SharedMemoryThis is one component of many for creating a IPC like dbus, for the rest we use zeromq and flatbuffers. #include "SharedMemory.h"
#include <iostream>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cstring>
#include <stdexcept>
namespace Qtk {
SharedMemory::SharedMemory(const std::string& name) : name(name) {
fd = shm_open(name.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
if (fd == -1) {
throw std::runtime_error("Error creating shared memory");
}
}
SharedMemory::~SharedMemory() {
close();
}
void SharedMemory::write(const std::string& data) {
size_t size = data.size();
ftruncate(fd, size);
void* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
throw std::runtime_error("Error mapping memory");
}
memcpy(ptr, data.data(), size);
munmap(ptr, size);
}
std::string SharedMemory::read() {
struct stat stat;
fstat(fd, &stat);
size_t size = stat.st_size;
void* ptr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
throw std::runtime_error("Error mapping memory");
}
std::string data(static_cast<char*>(ptr), size);
munmap(ptr, size);
return data;
}
void SharedMemory::close() {
::close(fd);
}
} // namespace Qtk Example Usage#include "SharedMemory.h"
#include <iostream>
int main() {
Qtk::SharedMemory shm("/my_shared_memory");
std::string data = shm.read();
std::cout << "Data read from shared memory: " << data << std::endl;
return 0;
} #include "SharedMemory.h"
#include <iostream>
int main() {
Qtk::SharedMemory shm("/my_shared_memory");
shm.write("Hello from writer program");
std::cout << "Data written to shared memory" << std::endl;
return 0;
} |
Beta Was this translation helpful? Give feedback.
-
SFML and ThorVG ExamplesThis code snippet illustrates how to use ThorVG in conjunction with SFML 2.6 for rendering Lottie animations. Lottie File: // g++ -o sfml-app main3.cpp -lsfml-graphics -lsfml-window -lsfml-system -lthorvg
#include <SFML/Graphics.hpp>
#include <thorvg.h>
#include <memory>
#include <iostream>
#include <cmath>
#include <cstring>
int main()
{
// Create a window with the size 800x600 pixels and title "SFML Example with ThorVG"
sf::RenderWindow window(sf::VideoMode(800, 600), "Hydra v0.0.1");
tvg::Initializer::init(1);
window.setVerticalSyncEnabled(true);
// A canvas target buffer
static uint32_t buffer[800 * 600];
// Generate a canvas
auto canvas = tvg::SwCanvas::gen();
canvas->target(buffer, 800, 800, 600, tvg::SwCanvas::ARGB8888S);
// Generate an animation
auto animation = tvg::Animation::gen();
// Acquire a picture associated with the animation
auto picture = animation->picture();
// Load an animation file
if (picture->load("lottie.json") != tvg::Result::Success) {
std::cerr << "Could not load the Lottie animation file." << std::endl;
return -1;
}
float zoomScale = 0.5; // 50% size
picture->scale(zoomScale);
// Push the picture into the canvas
canvas->push(std::unique_ptr<tvg::Paint>(picture));
// Determine animation duration
auto duration = animation->duration();
std::cout << "Animation duration: " << duration << " seconds" << std::endl;
// Create an SFML image to hold the buffer
sf::Image image;
image.create(800, 600);
// Create a texture
sf::Texture texture;
texture.create(800, 600);
// Create a sprite
sf::Sprite sprite;
// Main loop
sf::Clock clock; // Start a clock to track elapsed time
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
// catch the resize events
if (event.type == sf::Event::Resized)
{
// update the view to the new size of the window
sf::FloatRect visibleArea(0, 0, event.size.width, event.size.height);
window.setView(sf::View(visibleArea));
}
// Set a current animation frame to display
auto progress = clock.getElapsedTime().asSeconds() / duration;
if (progress >= 1.0f) {
progress = fmod(progress, 1.0f);
clock.restart();
}
animation->frame(animation->totalFrame() * progress);
// Update the picture to be redrawn
canvas->update(picture);
canvas->draw();
canvas->sync();
// Set the pixels directly in the image
for (int y = 0; y < 600; ++y) {
for (int x = 0; x < 800; ++x) {
int index = y * 800 + x;
uint32_t argb = buffer[index];
uint8_t a = (argb >> 24) & 0xFF;
uint8_t r = (argb >> 16) & 0xFF;
uint8_t g = (argb >> 8) & 0xFF;
uint8_t b = argb & 0xFF;
image.setPixel(x, y, sf::Color(r, g, b, a));
}
}
// Update the texture with the image
texture.update(image);
// Update the sprite with the texture
sprite.setTexture(texture);
window.clear(sf::Color(23, 23, 23)); // Clear the window with white color
window.draw(sprite); // Draw the sprite
window.display(); // Update the window
memset(buffer, 0, sizeof(buffer));
}
tvg::Initializer::term(tvg::CanvasEngine::Sw);
return 0;
}
}
This code snippet demonstrates how to integrate ThorVG, SFML 2.6, and the Yoga Layout Engine. These three libraries provide all the necessary tools to create a comprehensive GUI toolkit. https://github.com/SFML/SFML // g++ -o sfml-app main4.cpp -lsfml-graphics -lsfml-window -lsfml-system -lthorvg -lyogacore
#include <SFML/Graphics.hpp>
#include <thorvg.h>
#include <memory>
#include <yoga/Yoga.h>
// Function to create a rounded rectangle using ThorVG
std::unique_ptr<tvg::Shape> createRoundedRect(float x, float y, float width, float height,
float topLeftRadius, float topRightRadius,
float bottomRightRadius, float bottomLeftRadius) {
auto shape = tvg::Shape::gen();
// Define the rounded rectangle path
shape->moveTo(x + topLeftRadius, y);
shape->lineTo(x + width - topRightRadius, y);
if (topRightRadius > 0) {
shape->cubicTo(x + width - topRightRadius, y, x + width, y, x + width, y + topRightRadius);
}
shape->lineTo(x + width, y + height - bottomRightRadius);
if (bottomRightRadius > 0) {
shape->cubicTo(x + width, y + height - bottomRightRadius, x + width, y + height, x + width - bottomRightRadius, y + height);
}
shape->lineTo(x + bottomLeftRadius, y + height);
if (bottomLeftRadius > 0) {
shape->cubicTo(x + bottomLeftRadius, y + height, x, y + height, x, y + height - bottomLeftRadius);
}
shape->lineTo(x, y + topLeftRadius);
if (topLeftRadius > 0) {
shape->cubicTo(x, y + topLeftRadius, x, y, x + topLeftRadius, y);
}
shape->close();
return shape;
}
int main() {
// Initialize SFML window
sf::RenderWindow window(sf::VideoMode(800, 600), "Hydra v0.0.1");
tvg::Initializer::init(1);
// Set up Yoga layout
YGNodeRef container = YGNodeNew();
YGNodeStyleSetWidth(container, 800);
YGNodeStyleSetHeight(container, 100);
YGNodeStyleSetFlexDirection(container, YGFlexDirectionRow);
YGNodeStyleSetPadding(container, YGEdgeAll, 20);
// Create child nodes with flexible growth
YGNodeRef children[3];
for (int i = 0; i < 3; ++i) {
children[i] = YGNodeNew();
YGNodeStyleSetFlexGrow(children[i], 1);
YGNodeInsertChild(container, children[i], 0);
YGNodeStyleSetMargin(children[i], YGEdgeAll, 5);
}
// We want to change the width of the first child. Note its first in last out so the first child is in the last possition.
// We turn flex grow of so we can set the width of the child manualy.
YGNodeStyleSetFlexGrow(children[2], 0);
YGNodeStyleSetWidth(children[2], 100);
// Calculate all the childrens layout and sizes
YGNodeCalculateLayout(container, YGUndefined, YGUndefined, YGDirectionLTR);
// Prepare canvas
static uint32_t buffer[800 * 600];
auto canvas = tvg::SwCanvas::gen();
canvas->target(buffer, 800, 800, 600, tvg::SwCanvas::ARGB8888);
// Draw rounded rectangles for each child
for (auto& child : children) {
auto roundedRect = createRoundedRect(YGNodeLayoutGetLeft(child), YGNodeLayoutGetTop(child),
YGNodeLayoutGetWidth(child), YGNodeLayoutGetHeight(child), 10, 10, 0, 0);
roundedRect->fill(143 * (child == children[0]), 143 * (child == children[1]), 143 * (child == children[2]));
canvas->push(std::move(roundedRect));
}
// Render and sync canvas
canvas->draw();
canvas->sync();
// Convert color format from ARGB to RGBA
for (int i = 0; i < 800 * 600; ++i) {
uint32_t argb = buffer[i];
uint32_t a = (argb & 0xFF000000);
uint32_t r = (argb & 0x00FF0000) >> 16;
uint32_t g = (argb & 0x0000FF00) >> 8;
uint32_t b = (argb & 0x000000FF);
buffer[i] = a | (b << 16) | (g << 8) | r;
}
// Create SFML texture from the buffer
sf::Image image;
image.create(800, 600, reinterpret_cast<const uint8_t*>(buffer));
sf::Texture texture;
texture.loadFromImage(image);
sf::Sprite sprite(texture);
// Main loop
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
window.close();
}
// catch the resize events
if (event.type == sf::Event::Resized)
{
// update the view to the new size of the window
sf::FloatRect visibleArea(0, 0, event.size.width, event.size.height);
window.setView(sf::View(visibleArea));
}
}
window.clear(sf::Color(23,23,23));
window.draw(sprite);
window.display();
}
// Clean up
YGNodeFreeRecursive(container);
tvg::Initializer::term();
return 0;
}
}
|
Beta Was this translation helpful? Give feedback.
-
I read this, and honestly, this has the implications that Louvre could also be used in some sort of UI framework, UI automation and more. I think only headless mode is needed for Louvre to fully unlock that potential. Of course if such headless mode is added to Louvre/SRM, it should be disabled by default, and is probably not a priority for now. @ehopperdietzel and @stevenlstarr what are your thoughts on this? Even though it look a bit out of scope for Louvre, I think it is still interesting as a thought experiment. |
Beta Was this translation helpful? Give feedback.
-
I'm curious if it's possible to use Louvre to create a Wayland client window directly, similar to how SFML, SDL, or GLFW allow, without needing to develop an entire compositor?
Beta Was this translation helpful? Give feedback.
All reactions