redesign compositor protocol, start widget library

This commit is contained in:
Benji Dial 2024-07-29 19:59:52 -04:00
parent be691582ee
commit e6c3a80b01
24 changed files with 526 additions and 251 deletions

View file

@ -8,114 +8,95 @@
struct socket_state {
euler::syscall::stream_handle socket;
std::vector<window *> windows;
daguerre::hilbert_color window_bg = euler::syscall::encode_color(0, 0, 0);
window *w;
bool try_open_window() {
bool try_show_window() {
struct [[gnu::packed]] {
uint32_t width;
uint32_t height;
} body;
if (euler::syscall::read_from_stream(socket, sizeof(body), &body) !=
euler::syscall::stream_result::success)
if (w->is_shown)
return false;
window *w = new window(body.width, body.height);
w->contents.fill(window_bg);
uint16_t wid = 0;
while (wid < windows.size())
if (windows[wid] != 0)
++wid;
else
break;
if (wid == windows.size())
windows.push_back(w);
else
windows[wid] = w;
r->lock();
r->add_window(w);
r->unlock();
struct [[gnu::packed]] {
uint8_t type;
uint16_t the_window;
} response {
.type = 0x00,
.the_window = wid
};
return
euler::syscall::write_to_stream(socket, sizeof(response), &response) ==
euler::syscall::stream_result::success;
}
bool try_update_window_region() {
struct [[gnu::packed]] {
uint16_t window;
uint32_t start_x;
uint32_t start_y;
uint32_t width;
uint32_t height;
} body_head;
if (euler::syscall::read_from_stream(socket, sizeof(body_head), &body_head) !=
euler::syscall::stream_result::success)
return false;
std::vector<daguerre::hilbert_color> data(body_head.width * body_head.height);
if (euler::syscall::read_from_stream(socket, data.size() * 4, data.data()) !=
euler::syscall::stream_result::success)
return false;
daguerre::image<daguerre::hilbert_color>
data_as_image(body_head.width, body_head.height, data.data(), body_head.width, false);
if (body_head.window >= windows.size() || !windows[body_head.window])
return false;
window *w = windows[body_head.window];
r->lock();
if ((int)body_head.start_x + data_as_image.width > w->contents.width ||
(int)body_head.start_y + data_as_image.height > w->contents.height) {
r->unlock();
return false;
}
w->contents.copy_from(data_as_image, body_head.start_x, body_head.start_y);
r->unlock();
r->dispatch_render();
return true;
}
bool try_close_window() {
bool try_hide_window() {
uint16_t wid;
if (euler::syscall::read_from_stream(socket, 2, &wid) !=
euler::syscall::stream_result::success)
return false;
if (wid >= windows.size() || !windows[wid])
if (!w->is_shown)
return false;
r->lock();
r->remove_window(w);
r->unlock();
r->dispatch_render();
return true;
r->remove_window(windows[wid]);
windows[wid] = 0;
}
bool try_set_window_dimensions() {
struct [[gnu::packed]] { uint32_t width; uint32_t height; } packet;
if (euler::syscall::read_from_stream(socket, sizeof(packet), &packet) !=
euler::syscall::stream_result::success)
return false;
if (packet.width > __INT_MAX__ || packet.height > __INT_MAX__)
return false;
r->lock();
w->contents = daguerre::image<daguerre::hilbert_color>(
packet.width, packet.height);
r->unlock();
return true;
}
bool try_set_window_title() {
uint32_t length;
if (euler::syscall::read_from_stream(socket, 4, &length) !=
euler::syscall::stream_result::success)
return false;
std::string title;
title.resize(length);
if (euler::syscall::read_from_stream(socket, length, title.data()) !=
euler::syscall::stream_result::success)
return false;
r->lock();
w->title = std::move(title);
r->unlock();
r->dispatch_render();
return true;
}
bool try_update_window_region() {
struct [[gnu::packed]] {
uint32_t start_x; uint32_t start_y; uint32_t width; uint32_t height;
} packet;
if (euler::syscall::read_from_stream(socket, sizeof(packet), &packet) !=
euler::syscall::stream_result::success)
return false;
static_assert(__INT_MAX__ <= __UINT64_MAX__);
if ((uint64_t)packet.start_x + packet. width > (uint64_t)w->contents. width ||
(uint64_t)packet.start_y + packet.height > (uint64_t)w->contents.height)
return false;
daguerre::image<daguerre::hilbert_color> content(packet.width, packet.height);
if (euler::syscall::read_from_stream(
socket, packet.width * packet.height * 4, content.buffer) !=
euler::syscall::stream_result::success)
return false;
r->lock();
w->contents.copy_from(content, packet.start_x, packet.start_y);
r->unlock();
r->dispatch_render();
return true;
@ -130,9 +111,11 @@ struct socket_state {
return false;
switch (type) {
case 0x00: return try_open_window();
case 0x01: return try_update_window_region();
case 0x02: return try_close_window();
case 0x00: return try_show_window();
case 0x01: return try_hide_window();
case 0x02: return try_set_window_dimensions();
case 0x03: return try_set_window_title();
case 0x04: return try_update_window_region();
default: return false;
}
@ -144,18 +127,20 @@ struct socket_state {
euler::syscall::set_thread_name("socket thread");
window *w = new window();
socket_state *state = new socket_state {
.socket = socket, .windows = {} };
.socket = socket, .w = w };
while (state->try_process_request()) ;
r->lock();
for (unsigned i = 0; i < state->windows.size(); ++i) {
r->remove_window(state->windows[i]);
delete state->windows[i];
if (w->is_shown) {
r->lock();
r->remove_window(w);
r->unlock();
}
r->unlock();
delete state;
delete w;
euler::syscall::close_stream(socket);
euler::syscall::end_this_thread(0);

View file

@ -9,6 +9,10 @@ struct window {
int x;
int y;
window(int width, int height) : contents(width, height), x(0), y(0) {}
bool is_shown;
std::string title;
window() : x(0), y(0), is_shown(false) {}
};

View file

@ -6,7 +6,7 @@ build/%.cpp.o: source/%.cpp
$(HILBERT_CC) -c $^ -o $@
build/hello.elf: $(SOURCES:%=build/%.o)
$(HILBERT_CC) $^ -ldaguerre -o $@
$(HILBERT_CC) $^ -ldaguerre -lpake -o $@
clean:
rm -rf build

View file

@ -1,37 +1,30 @@
#include <goldman/protocol.hpp>
#include <pake/widgets/fixed-text.hpp>
#include <daguerre/psf.hpp>
#include <pake/window.hpp>
template <class color_t>
void overlay(color_t &to, const bool &from, const color_t &param) {
if (from)
to = param;
}
daguerre::fixed_font<bool> *font;
int main(int, char **) {
auto bg = euler::syscall::encode_color(0xaa, 0xaa, 0xaa);
auto fg = euler::syscall::encode_color(0x00, 0x00, 0x00);
font = new daguerre::fixed_font<bool>(
daguerre::try_load_psf("/assets/terminus-bold-18x10.psf").value());
daguerre::image<daguerre::hilbert_color> image(300, 200);
image.fill(bg);
pake::widgets::fixed_text *text =
new pake::widgets::fixed_text("Hello, world!", font,
euler::syscall::encode_color(0xaa, 0xaa, 0xaa),
euler::syscall::encode_color(0x00, 0x00, 0x00));
auto font = daguerre::try_load_psf("/assets/terminus-bold-18x10.psf");
image.render_text(*font, fg, 10, 10, "Hello, world!", &overlay);
pake::window w(300, 200, "Hello");
w.set_root(std::unique_ptr<pake::widget>(text));
w.render_and_send_to_compositor();
w.show();
euler::syscall::stream_handle s;
euler::syscall::connect_to_socket("hilbert.compositor", s);
goldman::protocol::send_open_window(s, 300, 200);
//TODO: call event loop
euler::syscall::stream_handle h1, h2;
euler::syscall::create_private_socket(h1, h2);
uint8_t byte;
euler::syscall::read_from_stream(s, 1, &byte);
auto w = goldman::protocol::get_window_opened_body(s);
goldman::protocol::send_update_window_region(
s, w, 0, 0, 300, 200, image.buffer, image.buffer_pitch);
euler::syscall::read_from_stream(s, 1, &byte);
__builtin_unreachable();
while (1)
euler::syscall::read_from_stream(h1, 1, &byte);
}

View file

@ -1,13 +1,9 @@
compositors listen on the socket id "hilbert.compositor".
when a window is opened by an application, that window can only be referred to
on that socket. the opaque value given in the "window opened" message refers to
that window in future messages on that socket. it is guaranteed to be distinct
for different windows on the same socket, and in no way guaranteed to be
distinct for different windows on different sockets. the window is bound
just to the socket, not to the application. if the socket where a window
was created is gifted to a new process, the new process has complete control
over the window, and the compositor does not need to be informed.
there is a one-to-one correspondence between sockets to the compositor and
windows. when a socket is opened, a window is created, and when a socket is
closed, its window is destroyed. this socket can be gifted to another process,
and the other process then becomes the window's owner.
data types:
@ -18,32 +14,28 @@ data types:
color rectangle:
multiple hilbert colors, top to bottom by row, left to right within row
window:
opaque word (given in "window opened" message after "open window" message)
messages from applications to compositor:
open window:
show window:
byte: 0x00
dword: window width
dword: window height
hide window:
byte: 0x01
set window dimensions:
byte: 0x02
dword: width
dword: height
set window title:
byte: 0x03
dword: length in bytes
bytes: title
update window region:
byte: 0x01
window: the window
byte: 0x04
dword: start x
dword: start y
dword: width
dword: height
color rectangle: the data
close window:
byte: 0x02
window: the window
messages from compositor to application:
window opened:
byte: 0x00
window: the window
these come in the order the open window requests were received
color rectangle: new content

12
euler/include/cassert Normal file
View file

@ -0,0 +1,12 @@
#pragma once
namespace euler {
[[noreturn]] inline void assert_failed() {
//TODO: log error and abort
while (1) ;
}
}
#define assert(cond) ((cond) ? (void)0 : ::euler::assert_failed());

View file

@ -0,0 +1,3 @@
#pragma once
#include <std/condition-variable.hpp>

View file

@ -1,4 +1,4 @@
#pragma once
#include <std/unique_lock.hpp>
#include <std/unique-lock.hpp>
#include <std/mutex.hpp>

View file

@ -0,0 +1,13 @@
#pragma once
#include <mutex>
namespace std {
class condition_variable {
//TODO
};
}

View file

@ -19,11 +19,11 @@ namespace std {
node *the_node;
bool operator ==(const generic_iterator &other) {
bool operator ==(const generic_iterator &other) const {
return the_node == other.the_node;
}
bool operator !=(const generic_iterator &other) {
bool operator !=(const generic_iterator &other) const {
return the_node != other.the_node;
}
@ -82,13 +82,14 @@ namespace std {
return iterator { .the_node = r };
}
iterator begin() const noexcept {
return iterator { .the_node = first_node };
}
iterator begin() noexcept { return iterator { .the_node = first_node }; }
iterator end() noexcept { return iterator { .the_node = 0 }; }
iterator end() const noexcept {
return iterator { .the_node = 0 };
}
const_iterator begin() const noexcept { return iterator { .the_node = first_node }; }
const_iterator end() const noexcept { return iterator { .the_node = 0 }; }
const_iterator cbegin() const noexcept { return iterator { .the_node = first_node }; }
const_iterator cend() const noexcept { return iterator { .the_node = 0 }; }
size_t remove(const T &value) {
size_t removed = 0;
@ -140,6 +141,7 @@ namespace std {
clear();
for (node *n = other.first_node; n; n = n->next)
push_back(n->value);
return *this;
}
list &operator =(list &&other) {
@ -150,6 +152,7 @@ namespace std {
other.first_node = 0;
other.last_node = 0;
other.count = 0;
return *this;
}
};

View file

@ -131,6 +131,12 @@ namespace std {
}
void clear() {
for (size_type i = 0; i < _size; ++i)
std::destroy_at(_data + i);
_size = 0;
}
constexpr size_type size() const noexcept {
return _size;
}
@ -188,6 +194,18 @@ namespace std {
++_size;
}
using iterator = T *;
using const_iterator = const T *;
iterator begin() noexcept { return _data; }
iterator end() noexcept { return _data + _size; }
const_iterator begin() const noexcept { return _data; }
const_iterator end() const noexcept { return _data + _size; }
const_iterator cbegin() const noexcept { return _data; }
const_iterator cend() const noexcept { return _data + _size; }
};
}

View file

@ -55,6 +55,8 @@ namespace daguerre {
~image();
void fill(const color_t &color);
void fill(
const color_t &color, int start_x, int start_y, int width, int height);
//does not check bounds
color_t &at(int x, int y);

View file

@ -103,6 +103,14 @@ namespace daguerre {
buffer[y * buffer_pitch + x] = color;
}
template <class color_t>
void image<color_t>::fill(
const color_t &color, int start_x, int start_y, int width, int height) {
for (int y = start_y; y < start_y + height; ++y)
for (int x = 0; x < start_x + width; ++x)
buffer[y * buffer_pitch + x] = color;
}
template <class color_t>
color_t &image<color_t>::at(int x, int y) {
return buffer[y * buffer_pitch + x];

View file

@ -1,85 +0,0 @@
#pragma once
#include <euler/syscall.hpp>
#include <memory>
//TODO: handle stream errors, make thread safe
namespace goldman::protocol {
typedef euler::syscall::encoded_color color;
typedef uint16_t window;
static inline void send_open_window(
euler::syscall::stream_handle socket, uint32_t width, uint32_t height) {
struct [[gnu::packed]] {
uint8_t type;
uint32_t width;
uint32_t height;
} packet {
.type = 0x00,
.width = width,
.height = height
};
euler::syscall::write_to_stream(socket, sizeof(packet), &packet);
}
void send_update_window_region(
euler::syscall::stream_handle socket, window the_window,
uint32_t start_x, uint32_t start_y, uint32_t width,
uint32_t height, const color *the_data, size_t data_pitch) {
struct [[gnu::packed]] {
uint8_t type;
window the_window;
uint32_t start_x;
uint32_t start_y;
uint32_t width;
uint32_t height;
} packet_head {
.type = 0x01,
.the_window = the_window,
.start_x = start_x,
.start_y = start_y,
.width = width,
.height = height
};
euler::syscall::write_to_stream(socket, sizeof(packet_head), &packet_head);
for (uint32_t y = 0; y < height; ++y)
euler::syscall::write_to_stream(
socket, width * sizeof(color), the_data + data_pitch * y);
}
void send_close_window(
euler::syscall::stream_handle socket, window the_window) {
struct [[gnu::packed]] {
uint8_t type;
window the_window;
} packet {
.type = 0x02,
.the_window = the_window
};
euler::syscall::write_to_stream(socket, sizeof(packet), &packet);
}
enum class response_id : uint8_t {
window_opened
};
window get_window_opened_body(euler::syscall::stream_handle socket) {
window w;
euler::syscall::read_from_stream(socket, sizeof(w), &w);
return w;
}
}

View file

@ -0,0 +1,27 @@
#pragma once
#include <daguerre/image.hpp>
#include <list>
namespace pake {
struct region {
int start_x;
int start_y;
int width;
int height;
};
struct dirtiable_image {
daguerre::image<daguerre::hilbert_color> image;
daguerre::image<bool> dirty;
std::vector<region> get_dirty_regions();
void clear_dirty();
dirtiable_image(int width, int height);
};
}

View file

@ -0,0 +1,19 @@
#pragma once
#include <pake/dirtiable-image.hpp>
namespace pake {
class widget {
public:
virtual ~widget() {}
virtual void render(
dirtiable_image &onto, int x_off, int y_off, bool force) = 0;
virtual void notify_size(int width, int height) = 0;
};
}

View file

@ -0,0 +1,31 @@
#pragma once
#include <daguerre/fixed-font.hpp>
#include <pake/widget.hpp>
namespace pake::widgets {
class fixed_text : public widget {
const daguerre::fixed_font<bool> *font;
daguerre::hilbert_color bg, fg;
std::string text;
int width, height;
public:
//TODO: look up font in some kind of catalogue
fixed_text(
std::string &&text,
const daguerre::fixed_font<bool> *font,
daguerre::hilbert_color bg,
daguerre::hilbert_color fg);
virtual void render(
dirtiable_image &onto, int x_off, int y_off, bool force) override;
virtual void notify_size(int width, int height) override;
};
}

View file

@ -0,0 +1,32 @@
#pragma once
#include <pake/dirtiable-image.hpp>
#include <pake/widget.hpp>
#include <memory>
namespace pake {
class window {
euler::syscall::stream_handle socket;
int width;
int height;
dirtiable_image contents;
std::unique_ptr<widget> root;
public:
window(int width, int height, const std::string &title);
~window();
void show();
void hide();
void set_root(std::unique_ptr<widget> &&w);
void render_and_send_to_compositor();
};
}

12
libraries/pake/makefile Normal file
View file

@ -0,0 +1,12 @@
SOURCES = \
widgets/fixed-text.cpp dirtiable-image.cpp window.cpp
build/%.cpp.o: source/%.cpp
@mkdir -p $(@D)
$(HILBERT_CC) -c $^ -o $@
build/libpake.a: $(SOURCES:%=build/%.o)
$(HILBERT_AR) rcs $@ $^
clean:
rm -rf build

View file

@ -0,0 +1,80 @@
#include <pake/dirtiable-image.hpp>
namespace pake {
struct dirty_region_builder {
std::vector<region> regions_not_on_bottom;
std::list<region> regions_on_bottom;
void add_row(const std::vector<region> &row) {
std::list<region> new_regions_on_bottom;
for (auto i = row.begin(); i < row.end(); ++i) {
bool expanded = false;
for (auto j = regions_on_bottom.begin(); j != regions_on_bottom.end(); ++j)
if (i->start_x == j->start_x && i->width == j->width) {
j->height += i->height;
new_regions_on_bottom.push_back(*j);
regions_on_bottom.erase(j);
expanded = true;
break;
}
if (!expanded)
new_regions_on_bottom.push_back(*i);
}
for (auto i = regions_on_bottom.begin(); i != regions_on_bottom.end(); ++i)
regions_not_on_bottom.push_back(*i);
regions_on_bottom = std::move(new_regions_on_bottom);
}
};
std::vector<region> dirtiable_image::get_dirty_regions() {
dirty_region_builder builder;
std::vector<region> row;
for (int y = 0; y < dirty.height; ++y) {
int r = 0;
for (int x = 0; x < dirty.width; ++x)
if (!dirty.at(x, y)) {
if (r != x)
row.push_back({
.start_x = r, .start_y = y,
.width = x - r, .height = 1
});
r = x + 1;
}
if (r != dirty.width)
row.push_back({
.start_x = r, .start_y = y,
.width = dirty.width - r, .height = 1
});
builder.add_row(row);
row.clear();
}
builder.add_row(row);
return builder.regions_not_on_bottom;
}
void dirtiable_image::clear_dirty() {
dirty.fill(false);
}
dirtiable_image::dirtiable_image(int width, int height)
: image(width, height), dirty(width, height) {
dirty.fill(false);
}
}

View file

@ -0,0 +1,41 @@
#include <pake/widgets/fixed-text.hpp>
static void draw_if_true(
daguerre::hilbert_color &out, const bool &in,
const daguerre::hilbert_color &param) {
if (in) out = param;
}
namespace pake::widgets {
fixed_text::fixed_text(
std::string &&text,
const daguerre::fixed_font<bool> *font,
daguerre::hilbert_color bg,
daguerre::hilbert_color fg)
: font(font), bg(bg), fg(fg), text(std::move(text)) {}
void fixed_text::render(
dirtiable_image &onto, int x_off, int y_off, bool force) {
if (force) {
onto.image.fill( bg, x_off, y_off, width, height);
onto.dirty.fill(true, x_off, y_off, width, height);
//TODO: have options for alignment
//TODO: check overflow
onto.image.render_text(
*font, fg, x_off, y_off,
text.data(), draw_if_true);
onto.dirty.fill(
true, x_off, y_off,
font->glyph_width * text.size(),
font->glyph_height);
}
}
void fixed_text::notify_size(int width, int height) {
this->width = width; this->height = height;
}
}

View file

@ -0,0 +1,78 @@
#include <pake/window.hpp>
#include <cassert>
//TODO: handle errors on socket connection, read, and write
namespace pake {
window::window(int width, int height, const std::string &title)
: width(width), height(height), contents(width, height), root() {
euler::syscall::connect_to_socket("hilbert.compositor", socket);
assert(width >= 0 && height >= 0);
struct [[gnu::packed]] { uint8_t type; uint32_t width; uint32_t height; }
dimensions_pkt = {.type = 0x02, .width = (uint32_t)width, .height = (uint32_t)height };
euler::syscall::write_to_stream(socket, sizeof(dimensions_pkt), &dimensions_pkt);
assert(title.size() <= UINT32_MAX);
struct [[gnu::packed]] { uint8_t type; uint32_t length; }
title_pkt = {.type = 0x03, .length = (uint32_t)title.size() };
euler::syscall::write_to_stream(socket, sizeof(title_pkt), &title_pkt);
euler::syscall::write_to_stream(socket, title.size(), title.data());
}
window::~window() {
euler::syscall::close_stream(socket);
}
void window::show() {
uint8_t packet = 0;
euler::syscall::write_to_stream(socket, 1, &packet);
}
void window::hide() {
uint8_t packet = 1;
euler::syscall::write_to_stream(socket, 1, &packet);
}
void window::set_root(std::unique_ptr<widget> &&w) {
root = std::move(w);
root->notify_size(width, height);
root->render(contents, 0, 0, true);
}
void window::render_and_send_to_compositor() {
root->render(contents, 0, 0, false);
auto dirties = contents.get_dirty_regions();
for (auto it = dirties.cbegin(); it != dirties.cend(); ++it) {
struct [[gnu::packed]] {
uint8_t type;
uint32_t start_x; uint32_t start_y;
uint32_t width; uint32_t height;
} update_pkt = {
.type = 0x04,
.start_x = (uint32_t)it->start_x, .start_y = (uint32_t)it->start_y,
. width = (uint32_t)it-> width, . height = (uint32_t)it-> height
};
euler::syscall::write_to_stream(socket, sizeof(update_pkt), &update_pkt);
for (int y = it->start_y; y < it->start_y + it->height; ++y)
euler::syscall::write_to_stream(socket,
it->width * sizeof(daguerre::hilbert_color),
&contents.image.at(it->start_x, y));
}
contents.clear_dirty();
}
}

View file

@ -8,7 +8,7 @@ HILBERT_NASM = nasm -f elf64
HILBERT_CC = ${TOOLCHAIN_DIR}/usr/bin/x86_64-elf-c++ -std=c++20 \
${EXTRA_CC_ARGS} -static -mno-sse -I include -I $(abspath euler/include) \
-I $(abspath libraries/daguerre/include) -I ${MINTSUKI_HEADERS_DIR} \
-I $(abspath libraries/goldman/include)
-I $(abspath libraries/pake/include)
HILBERT_AR = ${TOOLCHAIN_DIR}/usr/bin/x86_64-elf-ar
HILBERT_LD = ${TOOLCHAIN_DIR}/usr/bin/x86_64-elf-ld -z noexecstack
@ -25,6 +25,7 @@ LIBSTDCPP_DEP = toolchain/.libstdcpp-done
EULER_DEP = toolchain/.euler-done
DAGUERRE_DEP = toolchain/.daguerre-done
PAKE_DEP = toolchain/.pake-done
APP_DEPS = ${EULER_DEP}
LIBRARY_DEPS = ${LIBSTDCPP_DEP}
@ -37,13 +38,14 @@ run: build/disk.iso
gdb -x qemu.gdb
clean:
rm -rf build ${EULER_DEP} ${DAGUERRE_DEP}
rm -rf build ${EULER_DEP} ${DAGUERRE_DEP} ${PAKE_DEP}
make -C euler clean
make -C kernel clean
make -C applications/init clean
make -C applications/goldman clean
make -C applications/hello clean
make -C libraries/daguerre clean
make -C libraries/pake clean
clean-dependencies: clean
rm -rf toolchain dependencies
@ -110,6 +112,11 @@ ${DAGUERRE_DEP}: ${LIBRARY_DEPS}
cp libraries/daguerre/build/libdaguerre.a ${LIB_DIR}/
touch $@
${PAKE_DEP}: ${LIBRARY_DEPS}
+make -C libraries/pake build/libpake.a
cp libraries/pake/build/libpake.a ${LIB_DIR}/
touch $@
kernel/build/kernel.elf: ${GCC_DEP} ${MINTSUKI_HEADERS_DEP} ${LIMINE_DEP}
+make -C kernel build/kernel.elf
@ -119,7 +126,7 @@ applications/init/build/init.elf: ${APP_DEPS}
applications/goldman/build/goldman.elf: ${APP_DEPS} ${DAGUERRE_DEP}
+make -C applications/goldman build/goldman.elf
applications/hello/build/hello.elf: ${APP_DEPS} ${DAGUERRE_DEP}
applications/hello/build/hello.elf: ${APP_DEPS} ${DAGUERRE_DEP} ${PAKE_DEP}
+make -C applications/hello build/hello.elf
build/initfs.tgz: applications/init/build/init.elf \