mirror of https://gitlab.com/nakst/essence
424 lines
17 KiB
C++
424 lines
17 KiB
C++
// This file is part of the Essence operating system.
|
|
// It is released under the terms of the MIT license -- see LICENSE.md.
|
|
// Written by: nakst.
|
|
|
|
#define ES_INSTANCE_TYPE Instance
|
|
#include <essence.h>
|
|
#include <shared/array.cpp>
|
|
|
|
// TODO Single instance.
|
|
// TODO Sorting lists.
|
|
// TODO Processes: handle/thread count; IO statistics; more memory information.
|
|
|
|
struct Instance : EsInstance {
|
|
EsPanel *switcher;
|
|
EsTextbox *textboxGeneralLog;
|
|
EsListView *listViewProcesses;
|
|
EsPanel *panelMemoryStatistics;
|
|
int index;
|
|
EsCommand commandTerminateProcess;
|
|
Array<EsTextDisplay *> textDisplaysMemory;
|
|
};
|
|
|
|
#define REFRESH_INTERVAL (1000)
|
|
|
|
#define DISPLAY_PROCESSES (1)
|
|
#define DISPLAY_GENERAL_LOG (3)
|
|
#define DISPLAY_MEMORY (12)
|
|
|
|
#define PROCESSES_COLUMN_NAME (0)
|
|
#define PROCESSES_COLUMN_PID (1)
|
|
#define PROCESSES_COLUMN_MEMORY (2)
|
|
#define PROCESSES_COLUMN_CPU (3)
|
|
#define PROCESSES_COLUMN_HANDLES (4)
|
|
#define PROCESSES_COLUMN_THREADS (5)
|
|
|
|
const EsStyle styleMonospacedTextbox = {
|
|
.inherit = ES_STYLE_TEXTBOX_NO_BORDER,
|
|
|
|
.metrics = {
|
|
.mask = ES_THEME_METRICS_FONT_FAMILY,
|
|
.fontFamily = ES_FONT_MONOSPACED,
|
|
},
|
|
};
|
|
|
|
const EsStyle stylePanelMemoryStatistics = {
|
|
.inherit = ES_STYLE_PANEL_FILLED,
|
|
|
|
.metrics = {
|
|
.mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_GAP_ALL,
|
|
.insets = ES_RECT_1(8),
|
|
.gapMajor = 5,
|
|
.gapMinor = 5,
|
|
},
|
|
};
|
|
|
|
const EsStyle stylePanelMemoryCommands = {
|
|
.metrics = {
|
|
.mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_GAP_ALL,
|
|
.insets = ES_RECT_4(0, 0, 0, 5),
|
|
.gapMajor = 5,
|
|
.gapMinor = 5,
|
|
},
|
|
};
|
|
|
|
const char *pciClassCodeStrings[] = {
|
|
"Unknown",
|
|
"Mass storage controller",
|
|
"Network controller",
|
|
"Display controller",
|
|
"Multimedia controller",
|
|
"Memory controller",
|
|
"Bridge controller",
|
|
"Simple communication controller",
|
|
"Base system peripheral",
|
|
"Input device controller",
|
|
"Docking station",
|
|
"Processor",
|
|
"Serial bus controller",
|
|
"Wireless controller",
|
|
"Intelligent controller",
|
|
"Satellite communication controller",
|
|
"Encryption controller",
|
|
"Signal processing controller",
|
|
};
|
|
|
|
const char *pciSubclassCodeStrings1[] = {
|
|
"SCSI bus controller",
|
|
"IDE controller",
|
|
"Floppy disk controller",
|
|
"IPI bus controller",
|
|
"RAID controller",
|
|
"ATA controller",
|
|
"Serial ATA",
|
|
"Serial attached SCSI",
|
|
"Non-volatile memory controller",
|
|
};
|
|
|
|
const char *pciSubclassCodeStrings12[] = {
|
|
"FireWire (IEEE 1394) controller",
|
|
"ACCESS bus",
|
|
"SSA",
|
|
"USB controller",
|
|
"Fibre channel",
|
|
"SMBus",
|
|
"InfiniBand",
|
|
"IPMI interface",
|
|
"SERCOS interface (IEC 61491)",
|
|
"CANbus",
|
|
};
|
|
|
|
const char *pciProgIFStrings12_3[] = {
|
|
"UHCI",
|
|
"OHCI",
|
|
"EHCI",
|
|
"XHCI",
|
|
};
|
|
|
|
struct ProcessItem {
|
|
EsSnapshotProcessesItem data;
|
|
uintptr_t cpuUsage;
|
|
};
|
|
|
|
char generalLogBuffer[256 * 1024];
|
|
Array<ProcessItem> processes;
|
|
int64_t selectedPID = -2;
|
|
|
|
ProcessItem *FindProcessByPID(Array<ProcessItem> snapshot, int64_t pid) {
|
|
for (uintptr_t i = 0; i < snapshot.Length(); i++) {
|
|
if (pid == snapshot[i].data.pid) {
|
|
return &snapshot[i];
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void UpdateProcesses(Instance *instance) {
|
|
Array<ProcessItem> previous = processes;
|
|
processes = {};
|
|
|
|
size_t bufferSize;
|
|
EsHandle handle = EsTakeSystemSnapshot(ES_SYSTEM_SNAPSHOT_PROCESSES, &bufferSize);
|
|
EsSnapshotProcesses *snapshot = (EsSnapshotProcesses *) EsHeapAllocate(bufferSize, false);
|
|
EsConstantBufferRead(handle, snapshot);
|
|
EsHandleClose(handle);
|
|
|
|
for (uintptr_t i = 0; i < snapshot->count; i++) {
|
|
ProcessItem item = {};
|
|
item.data = snapshot->processes[i];
|
|
processes.Add(item);
|
|
|
|
if (snapshot->processes[i].isKernel) {
|
|
ProcessItem item = {};
|
|
item.data.cpuTimeSlices = snapshot->processes[i].idleTimeSlices;
|
|
item.data.pid = -1;
|
|
const char *idle = "CPU idle";
|
|
item.data.nameBytes = EsCStringLength(idle);
|
|
EsMemoryCopy(item.data.name, idle, item.data.nameBytes);
|
|
processes.Add(item);
|
|
}
|
|
}
|
|
|
|
EsHeapFree(snapshot);
|
|
|
|
for (uintptr_t i = 0; i < previous.Length(); i++) {
|
|
if (!FindProcessByPID(processes, previous[i].data.pid)) {
|
|
bool found = EsListViewFixedItemRemove(instance->listViewProcesses, previous[i].data.pid);
|
|
EsAssert(found);
|
|
previous.Delete(i--);
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < processes.Length(); i++) {
|
|
processes[i].cpuUsage = processes[i].data.cpuTimeSlices;
|
|
ProcessItem *item = FindProcessByPID(previous, processes[i].data.pid);
|
|
if (item) processes[i].cpuUsage -= item->data.cpuTimeSlices;
|
|
}
|
|
|
|
int64_t totalCPUTimeSlices = 0;
|
|
|
|
for (uintptr_t i = 0; i < processes.Length(); i++) {
|
|
totalCPUTimeSlices += processes[i].cpuUsage;
|
|
}
|
|
|
|
if (!totalCPUTimeSlices) {
|
|
totalCPUTimeSlices = 1;
|
|
}
|
|
|
|
int64_t percentageSum = 0;
|
|
|
|
for (uintptr_t i = 0; i < processes.Length(); i++) {
|
|
processes[i].cpuUsage = processes[i].cpuUsage * 100 / totalCPUTimeSlices;
|
|
percentageSum += processes[i].cpuUsage;
|
|
}
|
|
|
|
while (percentageSum < 100 && percentageSum) {
|
|
for (uintptr_t i = 0; i < processes.Length(); i++) {
|
|
if (processes[i].cpuUsage && percentageSum < 100) {
|
|
processes[i].cpuUsage++, percentageSum++;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < processes.Length(); i++) {
|
|
if (!FindProcessByPID(previous, processes[i].data.pid)) {
|
|
EsListViewIndex index = EsListViewFixedItemInsert(instance->listViewProcesses, processes[i].data.pid);
|
|
EsAssert(index == (EsListViewIndex) i);
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < processes.Length(); i++) {
|
|
EsListViewFixedItemSetString (instance->listViewProcesses, i, PROCESSES_COLUMN_NAME, processes[i].data.name, processes[i].data.nameBytes);
|
|
EsListViewFixedItemSetInteger(instance->listViewProcesses, i, PROCESSES_COLUMN_PID, processes[i].data.pid);
|
|
EsListViewFixedItemSetInteger(instance->listViewProcesses, i, PROCESSES_COLUMN_MEMORY, processes[i].data.memoryUsage);
|
|
EsListViewFixedItemSetInteger(instance->listViewProcesses, i, PROCESSES_COLUMN_CPU, processes[i].cpuUsage);
|
|
EsListViewFixedItemSetInteger(instance->listViewProcesses, i, PROCESSES_COLUMN_HANDLES, processes[i].data.handleCount);
|
|
EsListViewFixedItemSetInteger(instance->listViewProcesses, i, PROCESSES_COLUMN_THREADS, processes[i].data.threadCount);
|
|
}
|
|
|
|
EsListViewFixedItemSortAll(instance->listViewProcesses);
|
|
EsCommandSetDisabled(&instance->commandTerminateProcess, selectedPID < 0 || !FindProcessByPID(processes, selectedPID));
|
|
|
|
EsTimerSet(REFRESH_INTERVAL, [] (EsGeneric context) {
|
|
Instance *instance = (Instance *) context.p;
|
|
|
|
if (instance->index == DISPLAY_PROCESSES) {
|
|
UpdateProcesses(instance);
|
|
}
|
|
}, instance);
|
|
|
|
previous.Free();
|
|
}
|
|
|
|
void UpdateDisplay(Instance *instance, int index) {
|
|
instance->index = index;
|
|
|
|
if (index != DISPLAY_PROCESSES) {
|
|
EsCommandSetDisabled(&instance->commandTerminateProcess, true);
|
|
}
|
|
|
|
if (index == DISPLAY_PROCESSES) {
|
|
UpdateProcesses(instance);
|
|
EsPanelSwitchTo(instance->switcher, instance->listViewProcesses, ES_TRANSITION_NONE);
|
|
EsElementFocus(instance->listViewProcesses);
|
|
} else if (index == DISPLAY_GENERAL_LOG) {
|
|
size_t bytes = _EsDebugCommand(index, (uintptr_t) generalLogBuffer, sizeof(generalLogBuffer), 0);
|
|
EsTextboxSelectAll(instance->textboxGeneralLog);
|
|
EsTextboxInsert(instance->textboxGeneralLog, generalLogBuffer, bytes);
|
|
EsTextboxEnsureCaretVisible(instance->textboxGeneralLog, false);
|
|
EsPanelSwitchTo(instance->switcher, instance->textboxGeneralLog, ES_TRANSITION_NONE);
|
|
} else if (index == DISPLAY_MEMORY) {
|
|
EsMemoryStatistics statistics = {};
|
|
_EsDebugCommand(index, (uintptr_t) &statistics, 0, 0);
|
|
|
|
EsPanelSwitchTo(instance->switcher, instance->panelMemoryStatistics, ES_TRANSITION_NONE);
|
|
|
|
if (!instance->textDisplaysMemory.Length()) {
|
|
EsPanel *panel = EsPanelCreate(instance->panelMemoryStatistics, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, EsStyleIntern(&stylePanelMemoryCommands));
|
|
EsButton *button;
|
|
|
|
button = EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, EsLiteral("Leak 1 MB"));
|
|
EsButtonOnCommand(button, [] (Instance *, EsElement *, EsCommand *) { EsMemoryReserve(0x100000); });
|
|
button = EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, EsLiteral("Leak 4 MB"));
|
|
EsButtonOnCommand(button, [] (Instance *, EsElement *, EsCommand *) { EsMemoryReserve(0x400000); });
|
|
button = EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, EsLiteral("Leak 16 MB"));
|
|
EsButtonOnCommand(button, [] (Instance *, EsElement *, EsCommand *) { EsMemoryReserve(0x1000000); });
|
|
button = EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, EsLiteral("Leak 64 MB"));
|
|
EsButtonOnCommand(button, [] (Instance *, EsElement *, EsCommand *) { EsMemoryReserve(0x4000000); });
|
|
button = EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, EsLiteral("Leak 256 MB"));
|
|
EsButtonOnCommand(button, [] (Instance *, EsElement *, EsCommand *) { EsMemoryReserve(0x10000000); });
|
|
|
|
EsSpacerCreate(instance->panelMemoryStatistics, ES_CELL_H_FILL);
|
|
}
|
|
|
|
char buffer[256];
|
|
size_t bytes;
|
|
uintptr_t index = 0;
|
|
#define ADD_MEMORY_STATISTIC_DISPLAY(label, ...) \
|
|
bytes = EsStringFormat(buffer, sizeof(buffer), __VA_ARGS__); \
|
|
if (instance->textDisplaysMemory.Length() == index) { \
|
|
EsTextDisplayCreate(instance->panelMemoryStatistics, ES_CELL_H_PUSH | ES_CELL_H_RIGHT, 0, EsLiteral(label)); \
|
|
instance->textDisplaysMemory.Add(EsTextDisplayCreate(instance->panelMemoryStatistics, ES_CELL_H_PUSH | ES_CELL_H_LEFT)); \
|
|
} \
|
|
EsTextDisplaySetContents(instance->textDisplaysMemory[index++], buffer, bytes)
|
|
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Fixed heap allocation count:", "%d", statistics.fixedHeapAllocationCount);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Fixed heap graphics surfaces:", "%D (%d B)",
|
|
statistics.totalSurfaceBytes, statistics.totalSurfaceBytes);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Fixed heap normal size:", "%D (%d B)",
|
|
statistics.fixedHeapTotalSize - statistics.totalSurfaceBytes, statistics.fixedHeapTotalSize - statistics.totalSurfaceBytes);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Fixed heap total size:", "%D (%d B)",
|
|
statistics.fixedHeapTotalSize, statistics.fixedHeapTotalSize);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Core heap allocation count:", "%d", statistics.coreHeapAllocationCount);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Core heap total size:", "%D (%d B)", statistics.coreHeapTotalSize, statistics.coreHeapTotalSize);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Cached boot FS nodes:", "%d", statistics.cachedNodes);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Cached boot FS directory entries:", "%d", statistics.cachedDirectoryEntries);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Maximum object cache size:", "%D (%d pages)", statistics.maximumObjectCachePages * ES_PAGE_SIZE,
|
|
statistics.maximumObjectCachePages);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Approximate object cache size:", "%D (%d pages)", statistics.approximateObjectCacheSize,
|
|
statistics.approximateObjectCacheSize / ES_PAGE_SIZE);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Commit (pageable):", "%D (%d pages)", statistics.commitPageable * ES_PAGE_SIZE, statistics.commitPageable);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Commit (fixed):", "%D (%d pages)", statistics.commitFixed * ES_PAGE_SIZE, statistics.commitFixed);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Commit (total):", "%D (%d pages)", (statistics.commitPageable + statistics.commitFixed) * ES_PAGE_SIZE,
|
|
statistics.commitPageable + statistics.commitFixed);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Commit limit:", "%D (%d pages)", statistics.commitLimit * ES_PAGE_SIZE, statistics.commitLimit);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Commit fixed limit:", "%D (%d pages)", statistics.commitFixedLimit * ES_PAGE_SIZE, statistics.commitFixedLimit);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Commit remaining:", "%D (%d pages)", statistics.commitRemaining * ES_PAGE_SIZE, statistics.commitRemaining);
|
|
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Zeroed frames:", "%D (%d pages)", statistics.countZeroedPages * ES_PAGE_SIZE, statistics.countZeroedPages);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Free frames:", "%D (%d pages)", statistics.countFreePages * ES_PAGE_SIZE, statistics.countFreePages);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Standby frames:", "%D (%d pages)", statistics.countStandbyPages * ES_PAGE_SIZE, statistics.countStandbyPages);
|
|
ADD_MEMORY_STATISTIC_DISPLAY("Active frames:", "%D (%d pages)", statistics.countActivePages * ES_PAGE_SIZE, statistics.countActivePages);
|
|
|
|
EsTimerSet(REFRESH_INTERVAL, [] (EsGeneric context) {
|
|
Instance *instance = (Instance *) context.p;
|
|
|
|
if (instance->index == DISPLAY_MEMORY) {
|
|
UpdateDisplay(instance, DISPLAY_MEMORY);
|
|
}
|
|
}, instance);
|
|
}
|
|
}
|
|
|
|
int ListViewProcessesCallback(EsElement *element, EsMessage *message) {
|
|
if (message->type == ES_MSG_LIST_VIEW_SELECT && message->selectItem.isSelected) {
|
|
selectedPID = processes[message->selectItem.index].data.pid;
|
|
EsCommandSetDisabled(&element->instance->commandTerminateProcess, selectedPID < 0 || !FindProcessByPID(processes, selectedPID));
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
return ES_HANDLED;
|
|
}
|
|
|
|
void AddTab(EsElement *toolbar, uintptr_t index, const char *label, bool asDefault = false) {
|
|
EsButton *button = EsButtonCreate(toolbar, ES_BUTTON_RADIOBOX, 0, label);
|
|
button->userData.u = index;
|
|
|
|
EsButtonOnCommand(button, [] (Instance *instance, EsElement *element, EsCommand *) {
|
|
if (EsButtonGetCheck((EsButton *) element) == ES_CHECK_CHECKED) {
|
|
UpdateDisplay(instance, element->userData.u);
|
|
}
|
|
});
|
|
|
|
if (asDefault) EsButtonSetCheck(button, ES_CHECK_CHECKED);
|
|
}
|
|
|
|
void TerminateProcess(Instance *instance, EsElement *, EsCommand *) {
|
|
if (selectedPID == 0 /* Kernel */) {
|
|
// Terminating the kernel process is a meaningless action; the closest equivalent is shutting down.
|
|
EsSystemShowShutdownDialog();
|
|
return;
|
|
}
|
|
|
|
// TODO What should happen if the user tries to terminate the desktop process?
|
|
|
|
EsHandle handle = EsProcessOpen(selectedPID);
|
|
|
|
if (handle) {
|
|
EsProcessTerminate(handle, 1);
|
|
} else {
|
|
EsRectangle bounds = EsElementGetWindowBounds(instance->listViewProcesses);
|
|
EsAnnouncementShow(instance->window, ES_FLAGS_DEFAULT, (bounds.l + bounds.r) / 2, (bounds.t + bounds.b) / 2, EsLiteral("Could not terminate process"));
|
|
}
|
|
}
|
|
|
|
int InstanceCallback(Instance *, EsMessage *message) {
|
|
if (message->type == ES_MSG_INSTANCE_DESTROY) {
|
|
processes.Free();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ProcessApplicationMessage(EsMessage *message) {
|
|
if (message->type == ES_MSG_INSTANCE_CREATE) {
|
|
Instance *instance = EsInstanceCreate(message, "System Monitor");
|
|
instance->callback = InstanceCallback;
|
|
|
|
EsCommandRegister(&instance->commandTerminateProcess, instance, EsLiteral("Terminate process"), TerminateProcess, 1, "Del", false);
|
|
|
|
EsWindow *window = instance->window;
|
|
EsWindowSetIcon(window, ES_ICON_UTILITIES_SYSTEM_MONITOR);
|
|
EsPanel *switcher = EsPanelCreate(window, ES_CELL_FILL | ES_PANEL_SWITCHER, ES_STYLE_PANEL_WINDOW_DIVIDER);
|
|
instance->switcher = switcher;
|
|
|
|
instance->textboxGeneralLog = EsTextboxCreate(switcher, ES_TEXTBOX_MULTILINE | ES_CELL_FILL | ES_ELEMENT_DISABLED, EsStyleIntern(&styleMonospacedTextbox));
|
|
|
|
instance->listViewProcesses = EsListViewCreate(switcher, ES_CELL_FILL | ES_LIST_VIEW_COLUMNS | ES_LIST_VIEW_SINGLE_SELECT | ES_LIST_VIEW_FIXED_ITEMS);
|
|
instance->listViewProcesses->messageUser = ListViewProcessesCallback;
|
|
EsListViewRegisterColumn(instance->listViewProcesses, PROCESSES_COLUMN_NAME, "Name", -1, ES_LIST_VIEW_COLUMN_HAS_MENU, 150);
|
|
uint32_t numericColumnFlags = ES_LIST_VIEW_COLUMN_HAS_MENU | ES_TEXT_H_RIGHT | ES_DRAW_CONTENT_TABULAR
|
|
| ES_LIST_VIEW_COLUMN_DATA_INTEGERS | ES_LIST_VIEW_COLUMN_SORT_SIZE;
|
|
EsListViewRegisterColumn(instance->listViewProcesses, PROCESSES_COLUMN_PID, "PID", -1, numericColumnFlags, 120);
|
|
EsListViewRegisterColumn(instance->listViewProcesses, PROCESSES_COLUMN_MEMORY, "Memory", -1, numericColumnFlags | ES_LIST_VIEW_COLUMN_FORMAT_BYTES, 120);
|
|
EsListViewRegisterColumn(instance->listViewProcesses, PROCESSES_COLUMN_CPU, "CPU", -1, numericColumnFlags | ES_LIST_VIEW_COLUMN_FORMAT_PERCENTAGE, 120);
|
|
EsListViewRegisterColumn(instance->listViewProcesses, PROCESSES_COLUMN_HANDLES, "Handles", -1, numericColumnFlags, 120);
|
|
EsListViewRegisterColumn(instance->listViewProcesses, PROCESSES_COLUMN_THREADS, "Threads", -1, numericColumnFlags, 120);
|
|
EsListViewAddAllColumns(instance->listViewProcesses);
|
|
|
|
instance->panelMemoryStatistics = EsPanelCreate(switcher,
|
|
ES_CELL_FILL | ES_PANEL_TABLE | ES_PANEL_HORIZONTAL | ES_PANEL_V_SCROLL_AUTO, EsStyleIntern(&stylePanelMemoryStatistics));
|
|
EsPanelSetBands(instance->panelMemoryStatistics, 2 /* columns */);
|
|
|
|
EsElement *toolbar = EsWindowGetToolbar(window);
|
|
EsSpacerCreate(toolbar, ES_CELL_H_FILL);
|
|
EsElement *buttonGroup = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL | ES_ELEMENT_AUTO_GROUP);
|
|
AddTab(buttonGroup, DISPLAY_PROCESSES, "Processes", true);
|
|
EsSpacerCreate(buttonGroup, ES_CELL_V_FILL, ES_STYLE_TOOLBAR_BUTTON_GROUP_SEPARATOR);
|
|
AddTab(buttonGroup, DISPLAY_GENERAL_LOG, "System log");
|
|
EsSpacerCreate(buttonGroup, ES_CELL_V_FILL, ES_STYLE_TOOLBAR_BUTTON_GROUP_SEPARATOR);
|
|
AddTab(buttonGroup, DISPLAY_MEMORY, "Memory");
|
|
EsSpacerCreate(toolbar, ES_CELL_H_FILL);
|
|
}
|
|
}
|
|
|
|
void _start() {
|
|
_init();
|
|
|
|
while (true) {
|
|
ProcessApplicationMessage(EsMessageReceive());
|
|
}
|
|
}
|