feat: add Power GUI app (powermgr)

This commit is contained in:
2026-07-05 11:34:27 +02:00
parent 35e5aba23f
commit fcf8ae1e0b
7 changed files with 505 additions and 5 deletions
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once #pragma once
#define MONTAUK_BUILD_NUMBER 14 #define MONTAUK_BUILD_NUMBER 17
+9 -4
View File
@@ -59,7 +59,7 @@ BINDIR := bin
PROGRAMS := $(notdir $(wildcard src/*)) PROGRAMS := $(notdir $(wildcard src/*))
# Programs with custom Makefiles (built separately). # Programs with custom Makefiles (built separately).
CUSTOM_BUILDS := 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr calculator desktop login shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs test_dialogs test_dl libloader libhello crashpad CUSTOM_BUILDS := 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr powermgr calculator desktop login shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs test_dialogs test_dl libloader libhello crashpad
SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS)) SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS))
# Build targets: system programs go to bin/os/, apps go to bin/apps/<name>/. # Build targets: system programs go to bin/os/, apps go to bin/apps/<name>/.
@@ -89,9 +89,9 @@ CONFIGDIR := data/config
CONFIGSRC := $(wildcard $(CONFIGDIR)/*.toml) CONFIGSRC := $(wildcard $(CONFIGDIR)/*.toml)
CONFIGDST := $(patsubst $(CONFIGDIR)/%,$(BINDIR)/config/%,$(CONFIGSRC)) CONFIGDST := $(patsubst $(CONFIGDIR)/%,$(BINDIR)/config/%,$(CONFIGSRC))
.PHONY: all clean 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr calculator login desktop shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs bearssl libc tls libjpeg libjpegwrite install-apps libloader libs libhello test_dialogs test_dl crashpad check-syscalls gen-syscalls .PHONY: all clean 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr powermgr calculator login desktop shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs bearssl libc tls libjpeg libjpegwrite install-apps libloader libs libhello test_dialogs test_dl crashpad check-syscalls gen-syscalls
all: bearssl libc libjpeg libjpegwrite tls libloader libs libhello $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr calculator 2048 doom rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell icons fonts install-apps test_dialogs test_dl crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(FWDST) all: bearssl libc libjpeg libjpegwrite tls libloader libs libhello $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr powermgr calculator 2048 doom rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell icons fonts install-apps test_dialogs test_dl crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(FWDST)
# Build BearSSL static library (cross-compiled for freestanding x86_64). # Build BearSSL static library (cross-compiled for freestanding x86_64).
BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc) BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc)
@@ -215,6 +215,10 @@ klog: libc
procmgr: libc procmgr: libc
$(MAKE) -C src/procmgr $(MAKE) -C src/procmgr
# Build power manager standalone GUI tool (depends on libc).
powermgr: libc
$(MAKE) -C src/powermgr
# Build calculator standalone GUI tool (depends on libc). # Build calculator standalone GUI tool (depends on libc).
calculator: libc calculator: libc
$(MAKE) -C src/calculator $(MAKE) -C src/calculator
@@ -327,7 +331,7 @@ test_dialogs: libc libloader dialogs
$(MAKE) -C src/test_dialogs $(MAKE) -C src/test_dialogs
# Install app bundles (manifests, icons, data files) into bin/apps/<name>/. # Install app bundles (manifests, icons, data files) into bin/apps/<name>/.
install-apps: 2048 doom rpgdemo paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr calculator screenshot texteditor mandelbrot printers timezone crashpad install-apps: 2048 doom rpgdemo paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr powermgr calculator screenshot texteditor mandelbrot printers timezone crashpad
../scripts/install_apps.sh ../scripts/install_apps.sh
# Copy man pages into bin/man/ so mkramdisk.sh picks them up. # Copy man pages into bin/man/ so mkramdisk.sh picks them up.
@@ -391,6 +395,7 @@ clean:
$(MAKE) -C src/terminal clean $(MAKE) -C src/terminal clean
$(MAKE) -C src/klog clean $(MAKE) -C src/klog clean
$(MAKE) -C src/procmgr clean $(MAKE) -C src/procmgr clean
$(MAKE) -C src/powermgr clean
$(MAKE) -C src/calculator clean $(MAKE) -C src/calculator clean
$(MAKE) -C src/printers clean $(MAKE) -C src/printers clean
$(MAKE) -C src/timezone clean $(MAKE) -C src/timezone clean
+77
View File
@@ -0,0 +1,77 @@
# Makefile for powermgr (standalone Power Manager) on MontaukOS
# Copyright (c) 2026 Daniel Hammer
MAKEFLAGS += -rR
.SUFFIXES:
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
CXX := $(TOOLCHAIN_PREFIX)g++
else
CXX := g++
endif
PROG_INC := ../../include
LINK_LD := ../../link.ld
BINDIR := ../../bin
OBJDIR := obj
LIBDIR := ../../lib
CXXFLAGS := \
-std=gnu++20 \
-g -O2 -pipe \
-Wall \
-Wextra \
-Wno-unused-parameter \
-Wno-unused-function \
-nostdinc \
-ffreestanding \
-fno-stack-protector \
-fno-stack-check \
-fno-PIC \
-fno-rtti \
-fno-exceptions \
-ffunction-sections \
-fdata-sections \
-m64 \
-march=x86-64 \
-msse \
-msse2 \
-mno-red-zone \
-mcmodel=small \
-MMD -MP \
-I $(PROG_INC) \
-isystem $(PROG_INC)/libc \
-isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \
-isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include
LDFLAGS := \
-nostdlib \
-static \
-Wl,--build-id=none \
-Wl,--gc-sections \
-Wl,-m,elf_x86_64 \
-z max-page-size=0x1000 \
-T $(LINK_LD)
SRCS := main.cpp stb_truetype_impl.cpp
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
TARGET := $(BINDIR)/apps/powermgr/powermgr.elf
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS) $(LINK_LD) Makefile
mkdir -p $(BINDIR)/apps/powermgr
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBDIR)/libc/liblibc.a -o $@
$(OBJDIR)/%.o: %.cpp Makefile
mkdir -p $(OBJDIR)
$(CXX) $(CXXFLAGS) -c $< -o $@
-include $(OBJS:.o=.d)
clean:
rm -rf $(OBJDIR) $(TARGET)
+373
View File
@@ -0,0 +1,373 @@
/*
* main.cpp
* MontaukOS Power Manager - GUI equivalent of the `power` command.
*
* Polls the kernel's CPU power/thermal snapshot (SYS_POWERINFO) once per
* second and plots package temperature, average active frequency, and the
* thermal governor's frequency ceiling as scrolling line graphs, plus a
* details panel with the HWP/EPP/idle configuration.
* Copyright (c) 2026 Daniel Hammer
*/
#include <montauk/syscall.h>
#include <montauk/string.h>
#include <montauk/heap.h>
#include <gui/mtk.hpp>
extern "C" {
#include <stdio.h>
}
using namespace gui;
static constexpr int INIT_W = 640;
static constexpr int INIT_H = 520;
static constexpr int PW_TOOLBAR_H = 36;
static constexpr int PW_POLL_MS = 1000;
static constexpr int PW_GRAPH_CAP = 120;
static constexpr Color PW_TOOLBAR_BG = Color::from_rgb(0xF5, 0xF5, 0xF5);
static constexpr Color PW_HEADER_BG = Color::from_rgb(0xF0, 0xF0, 0xF0);
static constexpr Color PW_BORDER = Color::from_rgb(0xD0, 0xD0, 0xD0);
static constexpr Color PW_DIM_TEXT = Color::from_rgb(0x66, 0x66, 0x66);
static constexpr Color PW_SUCCESS = Color::from_rgb(0x22, 0x88, 0x22);
static constexpr Color PW_WARNING = Color::from_rgb(0xCC, 0x88, 0x00);
static constexpr Color PW_DANGER = Color::from_rgb(0xCC, 0x33, 0x33);
static constexpr Color PW_INFO = Color::from_rgb(0x33, 0x66, 0xCC);
static constexpr Color PW_CEILING = Color::from_rgb(0x00, 0x88, 0x88);
struct PowerMgrState {
montauk::abi::PowerInfo info;
bool supported;
int mouse_x;
int mouse_y;
uint64_t last_poll_ms;
uint64_t temp_samples[PW_GRAPH_CAP];
uint64_t freq_samples[PW_GRAPH_CAP];
uint64_t ceiling_samples[PW_GRAPH_CAP];
mtk::GraphHistory temp_history;
mtk::GraphHistory freq_history;
mtk::GraphHistory ceiling_history;
};
static WsWindow g_win;
static PowerMgrState g_pw = {};
static mtk::Theme pw_theme() {
mtk::Theme theme = mtk::make_theme();
theme.surface = PW_TOOLBAR_BG;
theme.surface_alt = PW_HEADER_BG;
theme.border = PW_BORDER;
theme.text_muted = PW_DIM_TEXT;
theme.text_subtle = PW_DIM_TEXT;
return theme;
}
static void init_graph_histories() {
mtk::graph_history_init(&g_pw.temp_history, g_pw.temp_samples, PW_GRAPH_CAP);
mtk::graph_history_init(&g_pw.freq_history, g_pw.freq_samples, PW_GRAPH_CAP);
mtk::graph_history_init(&g_pw.ceiling_history, g_pw.ceiling_samples, PW_GRAPH_CAP);
}
static bool read_info() {
return montauk::syscall1(montauk::abi::SYS_POWERINFO, (uint64_t)&g_pw.info) == 0;
}
static bool refresh_state(bool force) {
if (!g_pw.supported)
return false;
uint64_t now = montauk::get_milliseconds();
if (!force && now - g_pw.last_poll_ms < PW_POLL_MS)
return false;
g_pw.last_poll_ms = now;
if (!read_info())
return false;
if (g_pw.info.tempC != 0)
mtk::graph_history_push(&g_pw.temp_history, g_pw.info.tempC);
mtk::graph_history_push(&g_pw.freq_history, g_pw.info.effMHz);
mtk::graph_history_push(&g_pw.ceiling_history, g_pw.info.curMaxPerf);
return true;
}
static Rect toolbar_rect() {
return {0, 0, g_win.width, PW_TOOLBAR_H};
}
static Rect refresh_button_rect() {
int btn_h = 26;
int btn_y = (PW_TOOLBAR_H - btn_h) / 2;
return {8, btn_y, 82, btn_h};
}
static bool mouse_in_rect(const Rect& rect) {
return rect.contains(g_pw.mouse_x, g_pw.mouse_y);
}
static void render_toolbar(Canvas& c, const mtk::Theme& theme, int fh) {
Rect bar = toolbar_rect();
c.fill_rect(bar.x, bar.y, bar.w, bar.h, theme.surface);
c.hline(bar.x, bar.y + bar.h - 1, bar.w, theme.border);
Rect refresh_rect = refresh_button_rect();
mtk::draw_button(c, refresh_rect, "Refresh", mtk::BUTTON_TONAL,
mtk::widget_state(false, mouse_in_rect(refresh_rect),
g_pw.supported), theme);
char status[96];
if (!g_pw.supported) {
snprintf(status, sizeof(status), "No power management");
} else if (g_pw.info.throttling) {
snprintf(status, sizeof(status), "%u C, avg %u MHz - THROTTLING",
(unsigned)g_pw.info.tempC, (unsigned)g_pw.info.effMHz);
} else if (g_pw.info.tempC != 0) {
snprintf(status, sizeof(status), "%u C, avg %u MHz",
(unsigned)g_pw.info.tempC, (unsigned)g_pw.info.effMHz);
} else {
snprintf(status, sizeof(status), "avg %u MHz", (unsigned)g_pw.info.effMHz);
}
int status_w = text_width(status);
Color status_color = (g_pw.supported && g_pw.info.throttling)
? PW_DANGER : theme.text;
c.text(g_win.width - status_w - 12, (PW_TOOLBAR_H - fh) / 2, status, status_color);
}
static void render_graph(Canvas& c,
const Rect& rect,
const mtk::GraphHistory& history,
uint64_t max_value,
const char* title,
const char* value,
Color line,
const mtk::Theme& theme) {
mtk::GraphStyle style = mtk::make_graph_style(theme, line);
style.background = theme.window_bg;
style.plot_bg = Color::from_rgb(0xFA, 0xFA, 0xFA);
style.grid = mtk::mix(style.plot_bg, line, 16);
style.fill = mtk::lighten(line, 225);
mtk::draw_line_graph(c, rect, history, max_value, title, value, theme, style);
}
static constexpr int PW_DETAIL_ROWS = 4;
static int details_row_h(int fh) {
return fh + 10;
}
static int details_panel_h(int fh) {
// Mirrors the graph cards: padding, title line, 7px gap, then rows.
return 8 * 2 + fh + 7 + PW_DETAIL_ROWS * details_row_h(fh);
}
static void render_details(Canvas& c, const Rect& rect,
const mtk::Theme& theme, int fh) {
mtk::draw_rounded_frame(c, rect, theme.radius_md, theme.window_bg, theme.border);
const montauk::abi::PowerInfo& info = g_pw.info;
int pad = 8;
int x = rect.x + pad;
int y = rect.y + pad;
int w = rect.w - pad * 2;
int row_h = details_row_h(fh);
char buf[96];
c.text(x, y, "Details", theme.text_subtle);
y += fh + 7;
struct Row {
const char* label;
const char* value;
Color color;
} rows[PW_DETAIL_ROWS];
char hwp[64];
if (info.hwpActive) {
snprintf(hwp, sizeof(hwp), "active (EPP 0x%02X, perf range %u-%u)",
(unsigned)info.epp, (unsigned)info.lowestPerf,
(unsigned)info.highestPerf);
} else {
snprintf(hwp, sizeof(hwp), "not active");
}
rows[0] = {"HWP", hwp, theme.text};
snprintf(buf, sizeof(buf), "base %u MHz, turbo %u MHz",
(unsigned)info.baseMHz, (unsigned)info.maxMHz);
rows[1] = {"Frequency", buf, theme.text};
char gov[64];
Color gov_color = theme.text;
if (!info.hwpActive) {
snprintf(gov, sizeof(gov), "inactive");
} else if (info.throttling) {
snprintf(gov, sizeof(gov), "THROTTLING - ceiling %u of %u",
(unsigned)info.curMaxPerf, (unsigned)info.highestPerf);
gov_color = PW_DANGER;
} else {
snprintf(gov, sizeof(gov), "not throttling (ceiling %u)",
(unsigned)info.curMaxPerf);
gov_color = PW_SUCCESS;
}
rows[2] = {"Governor", gov, gov_color};
char idle[48];
snprintf(idle, sizeof(idle), "MWAIT hint 0x%02X%s",
(unsigned)(info.apIdleHint & 0xFF),
info.apIdleHint == 0 ? " (C1 only)" : "");
rows[3] = {"AP idle", idle, theme.text};
for (int i = 0; i < PW_DETAIL_ROWS; i++) {
c.text(x, y, rows[i].label, theme.text);
int value_w = text_width(rows[i].value);
c.text(x + w - value_w, y, rows[i].value, rows[i].color);
y += row_h;
if (i < PW_DETAIL_ROWS - 1)
mtk::draw_separator(c, x, y - 5, w, theme);
}
}
static void render_unsupported(Canvas& c, const mtk::Theme& theme, int fh) {
const char* line1 = "No CPU power management available";
const char* line2 = "(non-Intel CPU or running under emulation)";
int y = gui_max((g_win.height - fh * 2 - 8) / 2, PW_TOOLBAR_H + 12);
c.text(gui_max((g_win.width - text_width(line1)) / 2, 12), y,
line1, theme.text);
c.text(gui_max((g_win.width - text_width(line2)) / 2, 12), y + fh + 8,
line2, theme.text_muted);
}
static void render_body(Canvas& c, const mtk::Theme& theme, int fh) {
const montauk::abi::PowerInfo& info = g_pw.info;
Rect body = {0, PW_TOOLBAR_H, g_win.width,
gui_max(g_win.height - PW_TOOLBAR_H, 0)};
int margin = 12;
int gap = 10;
int details_h = details_panel_h(fh);
int graph_area_h = gui_max(body.h - margin * 2 - gap * 2 - details_h, 96);
int temp_h = (graph_area_h * 3) / 5;
int row_h = graph_area_h - temp_h - gap;
int col_w = gui_max((body.w - margin * 2 - gap) / 2, 80);
int x0 = body.x + margin;
int y0 = body.y + margin;
int y1 = y0 + temp_h + gap;
int y2 = y1 + row_h + gap;
Rect temp_rect = {x0, y0, body.w - margin * 2, temp_h};
Rect freq_rect = {x0, y1, col_w, row_h};
Rect ceiling_rect = {x0 + col_w + gap, y1, col_w, row_h};
Rect details_rect = {x0, y2, body.w - margin * 2, details_h};
// Scale the plot to TjMax so the top edge is the hardware throttle
// point; the line turns red while the governor is actively limiting.
char temp_value[48];
if (info.tempC != 0) {
snprintf(temp_value, sizeof(temp_value), "%u C (throttle at %u C)",
(unsigned)info.tempC, (unsigned)info.tjMaxC);
} else {
snprintf(temp_value, sizeof(temp_value), "unavailable");
}
render_graph(c, temp_rect, g_pw.temp_history,
info.tjMaxC > 0 ? info.tjMaxC : 100,
"Package Temperature", temp_value,
info.throttling ? PW_DANGER : PW_WARNING, theme);
char freq_value[32];
snprintf(freq_value, sizeof(freq_value), "%u MHz", (unsigned)info.effMHz);
render_graph(c, freq_rect, g_pw.freq_history,
info.maxMHz > 0 ? info.maxMHz : 1,
"Avg Frequency", freq_value, PW_INFO, theme);
char ceiling_value[32];
snprintf(ceiling_value, sizeof(ceiling_value), "%u / %u",
(unsigned)info.curMaxPerf, (unsigned)info.highestPerf);
render_graph(c, ceiling_rect, g_pw.ceiling_history,
info.highestPerf > 0 ? info.highestPerf : 1,
"Frequency Ceiling", ceiling_value, PW_CEILING, theme);
render_details(c, details_rect, theme, fh);
}
static void render() {
mtk::StandaloneHost host(&g_win);
Canvas c = host.canvas();
mtk::Theme theme = pw_theme();
c.fill(theme.window_bg);
int fh = system_font_height();
render_toolbar(c, theme, fh);
if (g_pw.supported)
render_body(c, theme, fh);
else
render_unsupported(c, theme, fh);
}
static bool handle_mouse(const montauk::abi::WinEvent& ev) {
bool refresh_hover_before = mouse_in_rect(refresh_button_rect());
g_pw.mouse_x = ev.mouse.x;
g_pw.mouse_y = ev.mouse.y;
bool redraw = refresh_hover_before != mouse_in_rect(refresh_button_rect());
bool left_pressed = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
if (!left_pressed)
return redraw;
if (refresh_button_rect().contains(ev.mouse.x, ev.mouse.y))
return refresh_state(true) || redraw;
return redraw;
}
extern "C" void _start() {
if (!fonts::init())
montauk::exit(1);
if (!g_win.create("Power", INIT_W, INIT_H))
montauk::exit(1);
montauk::memset(&g_pw, 0, sizeof(g_pw));
init_graph_histories();
g_pw.mouse_x = -1;
g_pw.mouse_y = -1;
g_pw.supported = read_info();
refresh_state(true);
render();
g_win.present();
while (!g_win.closed) {
bool redraw = refresh_state(false);
montauk::abi::WinEvent ev;
int r = g_win.poll(&ev);
if (r < 0) break;
if (r == 0) {
if (redraw) {
render();
g_win.present();
}
montauk::sleep_ms(16);
continue;
}
if (ev.type == 3)
break;
if (ev.type == 1) redraw |= handle_mouse(ev);
else if (ev.type == 2 || ev.type == 4) redraw = true;
if (redraw) {
render();
g_win.present();
}
}
g_win.destroy();
montauk::exit(0);
}
+11
View File
@@ -0,0 +1,11 @@
[app]
name = "Power"
binary = "powermgr.elf"
icon = "gnome-power-statistics.svg"
[menu]
category = "System"
visible = true
[launch]
pass_home_dir = false
@@ -0,0 +1,33 @@
/*
* stb_truetype_impl.cpp
* Single compilation unit for stb_truetype in MontaukOS freestanding environment
* Copyright (c) 2026 Daniel Hammer
*/
#include <cstdint>
#include <cstddef>
#include <montauk/heap.h>
#include <montauk/string.h>
#include <gui/stb_math.h>
#define STBTT_ifloor(x) ((int) stb_floor(x))
#define STBTT_iceil(x) ((int) stb_ceil(x))
#define STBTT_sqrt(x) stb_sqrt(x)
#define STBTT_pow(x,y) stb_pow(x,y)
#define STBTT_fmod(x,y) stb_fmod(x,y)
#define STBTT_cos(x) stb_cos(x)
#define STBTT_acos(x) stb_acos(x)
#define STBTT_fabs(x) stb_fabs(x)
#define STBTT_malloc(x,u) ((void)(u), montauk::malloc(x))
#define STBTT_free(x,u) ((void)(u), montauk::mfree(x))
#define STBTT_memcpy(d,s,n) montauk::memcpy(d,s,n)
#define STBTT_memset(d,v,n) montauk::memset(d,v,n)
#define STBTT_strlen(x) montauk::slen(x)
#define STBTT_assert(x) ((void)(x))
#define STB_TRUETYPE_IMPLEMENTATION
#include <gui/stb_truetype.h>
+1
View File
@@ -35,6 +35,7 @@ APPS=(
"terminal|apps/scalable/utilities-terminal.svg" "terminal|apps/scalable/utilities-terminal.svg"
"klog|apps/scalable/utilities-terminal.svg" "klog|apps/scalable/utilities-terminal.svg"
"procmgr|apps/scalable/system-monitor.svg" "procmgr|apps/scalable/system-monitor.svg"
"powermgr|apps/scalable/gnome-power-statistics.svg"
"calculator|apps/scalable/accessories-calculator.svg" "calculator|apps/scalable/accessories-calculator.svg"
"printers|devices/scalable/preferences-devices-printer.svg" "printers|devices/scalable/preferences-devices-printer.svg"
"timezone|categories/scalable/preferences-system-time.svg" "timezone|categories/scalable/preferences-system-time.svg"