From 166aadc4e1ac8260ebbfab6a3c6b48342e04680e Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Thu, 28 Feb 2019 01:43:50 +0100 Subject: [PATCH] ProcessManager: Start working on a graphical process manager. I need a table view widget for this thing, so I'm also using this to prototype a model/view thingy. --- Applications/ProcessManager/.gitignore | 3 + Applications/ProcessManager/Makefile | 33 +++ Applications/ProcessManager/ProcessView.cpp | 284 ++++++++++++++++++++ Applications/ProcessManager/ProcessView.h | 37 +++ Applications/ProcessManager/main.cpp | 63 +++++ Base/res/icons/gear16.png | Bin 0 -> 321 bytes Base/res/icons/gear16.rgb | Bin 0 -> 1024 bytes Base/res/icons/kill16.png | Bin 0 -> 354 bytes Base/res/icons/kill16.rgb | Bin 0 -> 1024 bytes Kernel/init.cpp | 6 +- Kernel/makeall.sh | 2 + Kernel/sync.sh | 1 + LibGUI/GModelIndex.h | 19 ++ LibGUI/GTableModel.h | 20 ++ 14 files changed, 467 insertions(+), 1 deletion(-) create mode 100644 Applications/ProcessManager/.gitignore create mode 100644 Applications/ProcessManager/Makefile create mode 100644 Applications/ProcessManager/ProcessView.cpp create mode 100644 Applications/ProcessManager/ProcessView.h create mode 100644 Applications/ProcessManager/main.cpp create mode 100644 Base/res/icons/gear16.png create mode 100644 Base/res/icons/gear16.rgb create mode 100644 Base/res/icons/kill16.png create mode 100644 Base/res/icons/kill16.rgb create mode 100644 LibGUI/GModelIndex.h create mode 100644 LibGUI/GTableModel.h diff --git a/Applications/ProcessManager/.gitignore b/Applications/ProcessManager/.gitignore new file mode 100644 index 0000000000..17f342df2c --- /dev/null +++ b/Applications/ProcessManager/.gitignore @@ -0,0 +1,3 @@ +*.o +*.d +ProcessManager diff --git a/Applications/ProcessManager/Makefile b/Applications/ProcessManager/Makefile new file mode 100644 index 0000000000..bc14fe2364 --- /dev/null +++ b/Applications/ProcessManager/Makefile @@ -0,0 +1,33 @@ +OBJS = \ + ProcessView.o \ + main.o + +APP = ProcessManager + +STANDARD_FLAGS = -std=c++17 +WARNING_FLAGS = -Wextra -Wall -Wundef -Wcast-qual -Wwrite-strings -Wimplicit-fallthrough +FLAVOR_FLAGS = -fno-exceptions -fno-rtti +OPTIMIZATION_FLAGS = -Os +INCLUDE_FLAGS = -I../.. -I. -I../../LibC + +DEFINES = -DSERENITY -DSANITIZE_PTRS -DUSERLAND + +CXXFLAGS = -MMD -MP $(WARNING_FLAGS) $(OPTIMIZATION_FLAGS) $(FLAVOR_FLAGS) $(STANDARD_FLAGS) $(INCLUDE_FLAGS) $(DEFINES) +CXX = i686-pc-serenity-g++ +LD = i686-pc-serenity-ld +AR = i686-pc-serenity-ar +LDFLAGS = -L../../LibC -L../../LibGUI + +all: $(APP) + +$(APP): $(OBJS) + $(LD) -o $(APP) $(LDFLAGS) $(OBJS) -lgui -lc + +.cpp.o: + @echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $< + +-include $(OBJS:%.o=%.d) + +clean: + @echo "CLEAN"; rm -f $(APPS) $(OBJS) *.d + diff --git a/Applications/ProcessManager/ProcessView.cpp b/Applications/ProcessManager/ProcessView.cpp new file mode 100644 index 0000000000..aca44b7751 --- /dev/null +++ b/Applications/ProcessManager/ProcessView.cpp @@ -0,0 +1,284 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ProcessView.h" + +static HashMap* s_usernames; + +class ProcessTableModel final : public GTableModel { +public: + ProcessTableModel() + { + + } + virtual ~ProcessTableModel() override { } + + virtual int row_count() const override { return m_processes.size(); } + virtual int column_count() const override { return 3; } + + virtual String column_name(int column) const override + { + switch (column) { + case 0: return "PID"; + case 1: return "State"; + case 2: return "Name"; + default: ASSERT_NOT_REACHED(); + } + } + virtual int column_width(int column) const override + { + switch (column) { + case 0: return 30; + case 1: return 80; + case 2: return 100; + default: ASSERT_NOT_REACHED(); + } + } + + virtual GModelIndex selected_index() const override { return { m_selected_row, 0 }; } + virtual void set_selected_index(GModelIndex index) override + { + if (index.row() >= 0 && index.row() < m_pids.size()) + m_selected_row = index.row(); + } + + virtual String data(int row, int column) const override + { + if (row < 0 || row >= row_count()) + return { }; + if (column < 0 || column >= column_count()) + return { }; + auto it = m_processes.find(m_pids[row]); + auto& process = *(*it).value; + switch (column) { + case 0: return String::format("%d", process.current_state.pid); + case 1: return process.current_state.state; + case 2: return process.current_state.name; + } + ASSERT_NOT_REACHED(); + } + + virtual void update() override + { + FILE* fp = fopen("/proc/all", "r"); + if (!fp) { + perror("failed to open /proc/all"); + exit(1); + } + + HashTable live_pids; + for (;;) { + char buf[BUFSIZ]; + char* ptr = fgets(buf, sizeof(buf), fp); + if (!ptr) + break; + auto parts = String(buf, Chomp).split(','); + if (parts.size() < 17) + break; + bool ok; + pid_t pid = parts[0].to_uint(ok); + ASSERT(ok); + unsigned nsched = parts[1].to_uint(ok); + ASSERT(ok); + ProcessState state; + state.pid = pid; + state.nsched = nsched; + unsigned uid = parts[5].to_uint(ok); + ASSERT(ok); + //state.user = s_usernames->get(uid); + state.user = String::format("%u", uid); + state.priority = parts[16]; + state.state = parts[7]; + state.name = parts[11]; + state.linear = parts[12].to_uint(ok); + ASSERT(ok); + state.committed = parts[13].to_uint(ok); + ASSERT(ok); + + { + auto it = m_processes.find(pid); + if (it == m_processes.end()) + m_processes.set(pid, make()); + } + auto it = m_processes.find(pid); + ASSERT(it != m_processes.end()); + (*it).value->previous_state = (*it).value->current_state; + (*it).value->current_state = state; + + live_pids.set(pid); + } + int rc = fclose(fp); + ASSERT(rc == 0); + + m_pids.clear(); + Vector pids_to_remove; + for (auto& it : m_processes) { + if (!live_pids.contains(it.key)) { + pids_to_remove.append(it.key); + continue; + } + m_pids.append(it.key); + } + for (auto pid : pids_to_remove) + m_processes.remove(pid); + } + + pid_t selected_pid() const + { + if (m_selected_row == -1) + return -1; + return m_pids[m_selected_row]; + } + +private: + struct ProcessState { + pid_t pid; + unsigned nsched; + String name; + String state; + String user; + String priority; + unsigned linear; + unsigned committed; + unsigned nsched_since_prev; + float cpu_percent; + }; + + struct Process { + ProcessState current_state; + ProcessState previous_state; + }; + + HashMap> m_processes; + Vector m_pids; + int m_selected_row { -1 }; +}; + +ProcessView::ProcessView(GWidget* parent) + : GWidget(parent) +{ + m_process_icon = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/gear16.rgb", { 16, 16 }); + + m_scrollbar = new GScrollBar(Orientation::Vertical, this); + m_scrollbar->set_step(4); + m_scrollbar->set_big_step(30); + m_scrollbar->on_change = [this] (int) { + update(); + }; + + m_model = make(); + + start_timer(1000); + reload(); +} + +ProcessView::~ProcessView() +{ +} + +void ProcessView::timer_event(GTimerEvent&) +{ + reload(); +} + +void ProcessView::resize_event(GResizeEvent& event) +{ + m_scrollbar->set_relative_rect(event.size().width() - m_scrollbar->preferred_size().width(), 0, m_scrollbar->preferred_size().width(), event.size().height()); +} + +void ProcessView::reload() +{ + m_model->update(); + + int excess_height = max(0, (item_count() * item_height()) - height()); + m_scrollbar->set_range(0, excess_height); + + set_status_message(String::format("%d processes", item_count())); + update(); +} + +Rect ProcessView::row_rect(int item_index) const +{ + return { 0, header_height() + (item_index * item_height()), width(), item_height() }; +} + +void ProcessView::mousedown_event(GMouseEvent& event) +{ + if (event.button() == GMouseButton::Left) { + for (int i = 0; i < item_count(); ++i) { + if (!row_rect(i).contains(event.position())) + continue; + m_model->set_selected_index({ i, 0 }); + update(); + } + } +} + +void ProcessView::paint_event(GPaintEvent&) +{ + Painter painter(*this); + + int horizontal_padding = 5; + int painted_item_index = 0; + + int x_offset = 0; + for (int column_index = 0; column_index < m_model->column_count(); ++column_index) { + Rect cell_rect(horizontal_padding + x_offset, 0, m_model->column_width(column_index), item_height()); + painter.draw_text(cell_rect, m_model->column_name(column_index), TextAlignment::CenterLeft, Color::Black); + x_offset += m_model->column_width(column_index) + horizontal_padding; + } + painter.draw_line({ 0, 0 }, { width() - 1, 0 }, Color::White); + painter.draw_line({ 0, header_height() - 1 }, { width() - 1, header_height() - 1 }, Color::DarkGray); + + int y_offset = header_height(); + + for (int row_index = 0; row_index < m_model->row_count(); ++row_index) { + int y = y_offset + painted_item_index * item_height(); + + Color background_color; + Color text_color; + if (row_index == m_model->selected_index().row()) { + background_color = Color::from_rgb(0x84351a); + text_color = Color::White; + } else { + background_color = painted_item_index % 2 ? Color(210, 210, 210) : Color::White; + text_color = Color::Black; + } + + painter.fill_rect(row_rect(painted_item_index), background_color); + + int x_offset = 0; + for (int column_index = 0; column_index < m_model->column_count(); ++column_index) { + Rect cell_rect(horizontal_padding + x_offset, y, m_model->column_width(column_index), item_height()); + painter.draw_text(cell_rect, m_model->data(row_index, column_index), TextAlignment::CenterLeft, text_color); + x_offset += m_model->column_width(column_index) + horizontal_padding; + } + ++painted_item_index; + }; + + Rect unpainted_rect(0, painted_item_index * item_height(), width(), height()); + unpainted_rect.intersect(rect()); + painter.fill_rect(unpainted_rect, Color::White); +} + +void ProcessView::set_status_message(String&& message) +{ + if (on_status_message) + on_status_message(move(message)); +} + +int ProcessView::item_count() const +{ + return m_model->row_count(); +} + +pid_t ProcessView::selected_pid() const +{ + return m_model->selected_pid(); +} diff --git a/Applications/ProcessManager/ProcessView.h b/Applications/ProcessManager/ProcessView.h new file mode 100644 index 0000000000..99e9bd79bd --- /dev/null +++ b/Applications/ProcessManager/ProcessView.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +class GScrollBar; +class ProcessTableModel; + +class ProcessView final : public GWidget { +public: + ProcessView(GWidget* parent); + virtual ~ProcessView() override; + + void reload(); + + Function on_status_message; + + int header_height() const { return 16; } + int item_height() const { return 16; } + int item_count() const; + + pid_t selected_pid() const; + +private: + virtual void paint_event(GPaintEvent&) override; + virtual void resize_event(GResizeEvent&) override; + virtual void mousedown_event(GMouseEvent&) override; + virtual void timer_event(GTimerEvent&) override; + + void set_status_message(String&&); + Rect row_rect(int item_index) const; + + RetainPtr m_process_icon; + GScrollBar* m_scrollbar { nullptr }; + OwnPtr m_model; +}; diff --git a/Applications/ProcessManager/main.cpp b/Applications/ProcessManager/main.cpp new file mode 100644 index 0000000000..cf6b4faa7e --- /dev/null +++ b/Applications/ProcessManager/main.cpp @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ProcessView.h" + +int main(int argc, char** argv) +{ + GApplication app(argc, argv); + + auto* widget = new GWidget; + widget->set_layout(make(Orientation::Vertical)); + + auto* toolbar = new GToolBar(widget); + auto* process_view = new ProcessView(widget); + auto* statusbar = new GStatusBar(widget); + process_view->on_status_message = [statusbar] (String message) { + statusbar->set_text(move(message)); + }; + + auto kill_action = GAction::create("Kill process", GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/kill16.rgb", { 16, 16 }), [process_view] (const GAction&) { + pid_t pid = process_view->selected_pid(); + if (pid != -1) + kill(pid, SIGKILL); + }); + toolbar->add_action(kill_action.copy_ref()); + + auto menubar = make(); + auto app_menu = make("ProcessManager"); + app_menu->add_action(GAction::create("Quit", String(), [] (const GAction&) { + GApplication::the().quit(0); + return; + })); + menubar->add_menu(move(app_menu)); + + auto file_menu = make("Process"); + file_menu->add_action(kill_action.copy_ref()); + menubar->add_menu(move(file_menu)); + + auto help_menu = make("Help"); + help_menu->add_action(GAction::create("About", [] (const GAction&) { + dbgprintf("FIXME: Implement Help/About\n"); + })); + menubar->add_menu(move(help_menu)); + + app.set_menubar(move(menubar)); + + auto* window = new GWindow; + window->set_title("ProcessManager"); + window->set_rect(20, 200, 320, 300); + window->set_main_widget(widget); + window->set_should_exit_app_on_close(true); + window->show(); + + return app.exec(); +} diff --git a/Base/res/icons/gear16.png b/Base/res/icons/gear16.png new file mode 100644 index 0000000000000000000000000000000000000000..f015ba37c72745403fb1ab3cd250cac5c680998b GIT binary patch literal 321 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7*pj^6T^Rm@;DWu&Co?cG za29w(7BevDDT6R$#Zvn+1_lQ95>H=O_Qy=pLRxCf5_LNn7#Ji=Tq8=H^K)}k^GX<; zi&7IyQd1PlGfOfQ+&z5*!W;R-85kH=db&7R~p<+`l$Y|{UvEzVCO88ZV2+gI+iu3P89hP39 zm9>_^!9?qa`=iO>#xwL*znb*7 z56y?4T)!ASY2R@~I=;qCh)sFs=1qp1rv~ksVeMbvSai-@n>8yl^ylUBe~i0?JCWO1uU|{fc^>bP0l+XkK&JlGr literal 0 HcmV?d00001 diff --git a/Base/res/icons/gear16.rgb b/Base/res/icons/gear16.rgb new file mode 100644 index 0000000000000000000000000000000000000000..032dd46ee0a4ee6a120261fadb859fe8a012bc21 GIT binary patch literal 1024 zcmZR;5B@VSFn}>dyw>PL_s7zXHEk57UE8c?+G!h%v~@0@ReAq^uDy3ez;6>%hJe>> zN+Ro4owE77=e?`Ooo#E4yPY0JXr}vlKa4W}9QI&d?rH~jC6;XuBfsz0E;zR_$Tnty z^7;cYGekKW-4&-rKH}|ZmTffhKJ)a~fx_)ycNMF)88EB~UnDMhZ*DO=gJBEfgKz&H z6x2->buMpmF)3&0kr+admm!i=M{Gz`(%Z>FVdQ&MBb@ E0FNYvkN^Mx literal 0 HcmV?d00001 diff --git a/Base/res/icons/kill16.rgb b/Base/res/icons/kill16.rgb new file mode 100644 index 0000000000000000000000000000000000000000..dd93ba0b50a25e58c60a439720e9df985766940b GIT binary patch literal 1024 zcmZP|188cl{SU&N3=RKD)xKrRmj83+%pprZOfNb5VR}LChGAmek4rBx`eAxuZYITU zWW6B$APmwEvKNH$h5b;}s}j)-P8%S9B4e1J@%a~|SHLFYKcBuoiWxAy_`(aO7o;DA wVP=5zf-tFiVeW+K#it)+7syT!2C-p!VKhiD2*dd3G)NB&qsxK#`1FG00eSuEqW}N^ literal 0 HcmV?d00001 diff --git a/Kernel/init.cpp b/Kernel/init.cpp index bbabc84acf..a5a1e3350c 100644 --- a/Kernel/init.cpp +++ b/Kernel/init.cpp @@ -26,7 +26,8 @@ #define SPAWN_LAUNCHER //#define SPAWN_GUITEST2 -#define SPAWN_FILE_MANAGER +//#define SPAWN_FILE_MANAGER +#define SPAWN_PROCESS_MANAGER //#define SPAWN_FONTEDITOR //#define SPAWN_MULTIPLE_SHELLS //#define STRESS_TEST_SPAWNING @@ -98,6 +99,9 @@ VFS* vfs; #ifdef SPAWN_FILE_MANAGER Process::create_user_process("/bin/FileManager", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, { }, tty0); #endif +#ifdef SPAWN_PROCESS_MANAGER + Process::create_user_process("/bin/ProcessManager", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, { }, tty0); +#endif #ifdef SPAWN_FONTEDITOR Process::create_user_process("/bin/FontEditor", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, move(environment), tty0); #endif diff --git a/Kernel/makeall.sh b/Kernel/makeall.sh index 7858fee3c5..b6c85c756e 100755 --- a/Kernel/makeall.sh +++ b/Kernel/makeall.sh @@ -26,6 +26,8 @@ $make_cmd -C ../Applications/Launcher clean && \ $make_cmd -C ../Applications/Launcher && \ $make_cmd -C ../Applications/FileManager clean && \ $make_cmd -C ../Applications/FileManager && \ +$make_cmd -C ../Applications/ProcessManager clean && \ +$make_cmd -C ../Applications/ProcessManager && \ $make_cmd -C ../Applications/About clean && \ $make_cmd -C ../Applications/About && \ $make_cmd clean &&\ diff --git a/Kernel/sync.sh b/Kernel/sync.sh index 45f6fba070..25627ed93e 100755 --- a/Kernel/sync.sh +++ b/Kernel/sync.sh @@ -78,6 +78,7 @@ cp -v ../Applications/Terminal/Terminal mnt/bin/Terminal cp -v ../Applications/FontEditor/FontEditor mnt/bin/FontEditor cp -v ../Applications/Launcher/Launcher mnt/bin/Launcher cp -v ../Applications/FileManager/FileManager mnt/bin/FileManager +cp -v ../Applications/ProcessManager/ProcessManager mnt/bin/ProcessManager cp -v ../Applications/About/About mnt/bin/About cp -v ../WindowServer/WindowServer mnt/bin/WindowServer cp -v kernel.map mnt/ diff --git a/LibGUI/GModelIndex.h b/LibGUI/GModelIndex.h new file mode 100644 index 0000000000..9cd19ba3ac --- /dev/null +++ b/LibGUI/GModelIndex.h @@ -0,0 +1,19 @@ +#pragma once + +class GModelIndex { +public: + GModelIndex() { } + GModelIndex(int row, int column) + : m_row(row) + , m_column(column) + { + } + + bool is_valid() const { return m_row != -1 && m_column != -1; } + int row() const { return m_row; } + int column() const { return m_column; } + +private: + int m_row { -1 }; + int m_column { -1 }; +}; diff --git a/LibGUI/GTableModel.h b/LibGUI/GTableModel.h new file mode 100644 index 0000000000..c05db160d8 --- /dev/null +++ b/LibGUI/GTableModel.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +class GTableModel { +public: + GTableModel() { } + virtual ~GTableModel() { } + + virtual int row_count() const = 0; + virtual int column_count() const = 0; + virtual String row_name(int) const { return { }; } + virtual String column_name(int) const { return { }; } + virtual int column_width(int) const { return 0; } + virtual String data(int row, int column) const = 0; + virtual void set_selected_index(GModelIndex) { } + virtual GModelIndex selected_index() const { return GModelIndex(); } + virtual void update() = 0; +};