Skip to content

Commit 7ba7680

Browse files
committed
Rocket: refactor, add unit tests
Fixes #36
1 parent fc4303e commit 7ba7680

22 files changed

+1011
-679
lines changed

apps/rocket/App.cpp

Lines changed: 103 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
#include "App.h"
22

3+
#include <unistdpp/pipe.h>
4+
35
#include <Device.h>
46

7+
#include <algorithm>
58
#include <csignal>
69
#include <fstream>
710
#include <iostream>
811

12+
#include <sys/wait.h>
913
#include <unistd.h>
1014

1115
using namespace rmlib;
@@ -15,6 +19,7 @@ namespace {
1519
pid_t
1620
runCommand(std::string_view cmd) {
1721
pid_t pid = fork();
22+
1823
if (pid == -1) {
1924
perror("Error launching");
2025
return -1;
@@ -25,21 +30,28 @@ runCommand(std::string_view cmd) {
2530
return pid;
2631
}
2732

28-
std::cout << "Running: " << cmd << std::endl;
2933
setpgid(0, 0);
34+
35+
std::cout << "Running: " << cmd << std::endl;
3036
execlp("/bin/sh", "/bin/sh", "-c", cmd.data(), nullptr);
3137
perror("Error running process");
3238
return -1;
3339
}
3440

35-
bool
36-
endsWith(std::string_view a, std::string_view end) {
37-
if (a.size() < end.size()) {
38-
return false;
41+
void
42+
stop(std::shared_ptr<AppRunInfo> runInfo) {
43+
const auto pid = runInfo->pid;
44+
45+
if (runInfo->paused) {
46+
kill(-pid, SIGCONT);
3947
}
4048

41-
return a.substr(a.size() - end.size()) == end;
49+
int res = kill(-pid, SIGTERM);
50+
if (res != 0) {
51+
perror("Error killing!");
52+
}
4253
}
54+
4355
} // namespace
4456

4557
std::optional<AppDescription>
@@ -76,17 +88,22 @@ AppDescription::read(std::string_view path, std::string_view iconDir) {
7688
}
7789

7890
if (!result.icon.empty()) {
79-
auto iconPath = std::string(iconDir) + '/' + result.icon + ".png";
80-
std::cout << "Parsing image from: " << iconPath << std::endl;
81-
result.iconImage = ImageCanvas::load(iconPath.c_str());
82-
if (result.iconImage.has_value()) {
83-
std::cout << result.iconImage->canvas.components() << std::endl;
84-
}
91+
result.iconPath = std::string(iconDir) + '/' + result.icon + ".png";
8592
}
8693

8794
return std::optional(std::move(result));
8895
}
8996

97+
std::optional<ImageCanvas>
98+
AppDescription::getIcon() const {
99+
std::cout << "Parsing image from: " << iconPath << std::endl;
100+
auto iconImage = ImageCanvas::load(iconPath.c_str());
101+
if (iconImage.has_value()) {
102+
std::cout << iconImage->canvas.components() << std::endl;
103+
}
104+
return iconImage;
105+
}
106+
90107
std::vector<AppDescription>
91108
readAppFiles(std::string_view directory) {
92109
const auto iconPath = std::string(directory) + "/icons";
@@ -95,11 +112,6 @@ readAppFiles(std::string_view directory) {
95112
std::vector<AppDescription> result;
96113

97114
for (const auto& path : paths) {
98-
if (!endsWith(path, ".draft")) {
99-
std::cerr << "skipping non draft file: " << path << std::endl;
100-
continue;
101-
}
102-
103115
auto appDesc = AppDescription::read(path, iconPath);
104116
if (!appDesc.has_value()) {
105117
std::cerr << "error parsing file: " << path << std::endl;
@@ -112,19 +124,30 @@ readAppFiles(std::string_view directory) {
112124
return result;
113125
}
114126

127+
void
128+
App::updateDescription(AppDescription desc) {
129+
mDescription = std::move(desc);
130+
iconImage = mDescription.getIcon();
131+
}
132+
115133
bool
116134
App::launch() {
117-
if (runInfo.has_value()) {
135+
if (isRunning()) {
118136
assert(false && "Shouldn't be called if the app is already running");
119137
return false;
120138
}
121139

122-
auto pid = runCommand(description.command);
140+
auto pid = runCommand(description().command);
123141
if (pid == -1) {
124142
return false;
125143
}
126144

127-
runInfo = AppRunInfo{ pid };
145+
auto runInfo = std::make_shared<AppRunInfo>();
146+
runInfo->pid = pid;
147+
148+
this->runInfo = runInfo;
149+
150+
AppManager::getInstance().runInfos.emplace_back(std::move(runInfo));
128151

129152
return true;
130153
}
@@ -133,19 +156,17 @@ void
133156
App::stop() {
134157
assert(isRunning());
135158

136-
if (isPaused()) {
137-
kill(-runInfo->pid, SIGCONT);
138-
}
139-
140-
kill(-runInfo->pid, SIGTERM);
159+
::stop(runInfo.lock());
141160
}
142161

143162
void
144163
App::pause(std::optional<MemoryCanvas> screen) {
145164
assert(isRunning() && !isPaused());
146165

147-
kill(-runInfo->pid, SIGSTOP);
148-
runInfo->paused = true;
166+
auto lockedInfo = runInfo.lock();
167+
168+
kill(-lockedInfo->pid, SIGSTOP);
169+
lockedInfo->paused = true;
149170
savedFb = std::move(screen);
150171
}
151172

@@ -160,6 +181,59 @@ App::resume(rmlib::fb::FrameBuffer* fb) {
160181
savedFb.reset();
161182
}
162183

163-
kill(-runInfo->pid, SIGCONT);
164-
runInfo->paused = false;
184+
auto lockedInfo = runInfo.lock();
185+
kill(-lockedInfo->pid, SIGCONT);
186+
lockedInfo->paused = false;
187+
}
188+
189+
AppManager&
190+
AppManager::getInstance() {
191+
static AppManager instance;
192+
return instance;
193+
}
194+
195+
bool
196+
AppManager::update() {
197+
bool anyKilled = false;
198+
199+
while (auto res = pipe.readPipe.readAll<pid_t>()) {
200+
auto pid = *res;
201+
anyKilled = true;
202+
203+
runInfos.erase(
204+
std::remove_if(runInfos.begin(),
205+
runInfos.end(),
206+
[pid](auto& info) { return info->pid == pid; }),
207+
runInfos.end());
208+
}
209+
210+
return anyKilled;
211+
}
212+
213+
void
214+
AppManager::onSigChild(int sig) {
215+
auto& inst = AppManager::getInstance();
216+
217+
pid_t childPid = 0;
218+
while ((childPid = waitpid(static_cast<pid_t>(-1), nullptr, WNOHANG)) > 0) {
219+
220+
std::cout << "Killed: " << childPid << "\n";
221+
222+
auto v = inst.pipe.writePipe.writeAll(childPid);
223+
224+
if (!v.has_value()) {
225+
std::cerr << "Error in writing pid: " << to_string(v.error()) << "\n";
226+
}
227+
}
228+
}
229+
230+
AppManager::AppManager() : pipe(unistdpp::fatalOnError(unistdpp::pipe())) {
231+
unistdpp::setNonBlocking(pipe.readPipe);
232+
std::signal(SIGCHLD, onSigChild);
233+
}
234+
235+
AppManager::~AppManager() {
236+
for (auto runInfo : runInfos) {
237+
::stop(runInfo);
238+
}
165239
}

apps/rocket/App.h

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,16 @@
33
#include <Canvas.h>
44
#include <FrameBuffer.h>
55

6+
#include <unistdpp/pipe.h>
7+
68
#include <chrono>
79
#include <optional>
810
#include <string>
911
#include <vector>
1012

1113
struct AppRunInfo {
12-
pid_t pid;
14+
pid_t pid = -1;
1315
bool paused = false;
14-
15-
// Indicates that the app should be removed when it exists
16-
bool shouldRemove = false;
1716
};
1817

1918
struct AppDescription {
@@ -25,7 +24,8 @@ struct AppDescription {
2524
std::string command;
2625
std::string icon;
2726

28-
std::optional<rmlib::ImageCanvas> iconImage;
27+
std::string iconPath;
28+
std::optional<rmlib::ImageCanvas> getIcon() const;
2929

3030
static std::optional<AppDescription> read(std::string_view path,
3131
std::string_view iconDir);
@@ -34,23 +34,15 @@ struct AppDescription {
3434
std::vector<AppDescription>
3535
readAppFiles(std::string_view directory);
3636

37-
struct App {
38-
AppDescription description;
39-
40-
std::optional<AppRunInfo> runInfo = std::nullopt;
41-
42-
std::chrono::steady_clock::time_point lastActivated;
37+
class App {
38+
public:
39+
App(AppDescription desc)
40+
: mDescription(std::move(desc)), iconImage(mDescription.getIcon()) {}
4341

44-
std::optional<rmlib::MemoryCanvas> savedFb;
45-
46-
// Used for UI:
47-
rmlib::Rect launchRect;
48-
rmlib::Rect killRect;
42+
void updateDescription(AppDescription desc);
4943

50-
App(AppDescription desc) : description(std::move(desc)) {}
51-
52-
bool isRunning() const { return runInfo.has_value(); }
53-
bool isPaused() const { return isRunning() && runInfo->paused; }
44+
bool isRunning() const { return !runInfo.expired(); }
45+
bool isPaused() const { return isRunning() && runInfo.lock()->paused; }
5446

5547
/// Starts a new instance of the app if it's not already running.
5648
/// \returns True if a new instance was started.
@@ -60,4 +52,43 @@ struct App {
6052

6153
void pause(std::optional<rmlib::MemoryCanvas> screen = std::nullopt);
6254
void resume(rmlib::fb::FrameBuffer* fb = nullptr);
55+
56+
const AppDescription& description() const { return mDescription; }
57+
58+
const std::optional<rmlib::ImageCanvas>& icon() const { return iconImage; }
59+
const std::optional<rmlib::MemoryCanvas>& savedFB() const { return savedFb; }
60+
void resetSavedFB() { savedFb.reset(); }
61+
62+
void setRemoveOnExit() { shouldRemove = true; }
63+
bool shouldRemoveOnExit() const { return shouldRemove; }
64+
65+
private:
66+
AppDescription mDescription;
67+
68+
std::weak_ptr<AppRunInfo> runInfo;
69+
70+
std::optional<rmlib::ImageCanvas> iconImage;
71+
std::optional<rmlib::MemoryCanvas> savedFb;
72+
73+
// Indicates that the app should be removed when it exists
74+
bool shouldRemove = false;
75+
};
76+
77+
class AppManager {
78+
public:
79+
static AppManager& getInstance();
80+
81+
bool update();
82+
const unistdpp::FD& getWaitFD() const { return pipe.readPipe; }
83+
84+
private:
85+
friend class App;
86+
unistdpp::Pipe pipe;
87+
88+
std::vector<std::shared_ptr<AppRunInfo>> runInfos;
89+
90+
static void onSigChild(int sig);
91+
92+
AppManager();
93+
~AppManager();
6394
};

apps/rocket/AppWidgets.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include "AppWidgets.h"
2+
3+
using namespace rmlib;
4+
5+
const MemoryCanvas&
6+
getMissingImage() {
7+
static auto image = [] {
8+
auto mem = MemoryCanvas(128, 128, 2);
9+
mem.canvas.set(greyToRGB565(0xaa));
10+
return mem;
11+
}();
12+
return image;
13+
}

0 commit comments

Comments
 (0)