Files
MontaukOS/programs/src/charmap/main.cpp
T
daniel 95beb8ae1a feat: remove charmap in-window header, float the copy toast
The top header bar duplicated the window-manager title bar. Drop it: tabs
now sit at the top of the content area (window shrinks by the header's
height). The "Copied" confirmation moves from the header to a floating
accent toast anchored to the lower-right of the grid, drawn last.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 20:54:37 +02:00

620 lines
21 KiB
C++

/*
* main.cpp
* MontaukOS Character Map - standalone Window Server app
*
* Browse curated sets of characters, click (or use the keyboard) to select,
* and copy to the system clipboard for pasting into other apps.
*
* Character model: MontaukOS's GUI text stack is single-byte (Windows-1252,
* with the 0x80-0x9F block mapped to typographic punctuation in
* gui::decode_single_byte_codepoint). The glyph cache only holds codepoints
* 0-255, so that byte range is exactly the set of glyphs the system font can
* render. We therefore present that set and copy the *raw byte* to the
* clipboard: it renders identically here and in every other Montauk app
* (copying multi-byte UTF-8 would render as mojibake in the single-byte stack).
*
* Copyright (c) 2026 Daniel Hammer
*/
#include <cstdint>
#include <montauk/syscall.h>
#include <montauk/string.h>
#include <gui/gui.hpp>
#include <gui/canvas.hpp>
#include <gui/font.hpp>
#include <gui/clipboard.hpp>
#include <gui/standalone.hpp>
#include <gui/truetype.hpp>
#include <gui/mtk/theme.hpp>
extern "C" {
#include <stdio.h>
}
using namespace gui;
// ==== Character data ====
struct Glyph {
uint8_t byte; // Windows-1252 byte value (what gets copied / rendered)
const char* name; // human-readable description
};
struct CharSet {
const char* label;
const Glyph* glyphs;
int count;
};
// Accented Latin letters and ligatures (things you can't easily type).
static const Glyph LATIN[] = {
{0xC0, "Latin Capital Letter A With Grave"},
{0xC1, "Latin Capital Letter A With Acute"},
{0xC2, "Latin Capital Letter A With Circumflex"},
{0xC3, "Latin Capital Letter A With Tilde"},
{0xC4, "Latin Capital Letter A With Diaeresis"},
{0xC5, "Latin Capital Letter A With Ring Above"},
{0xC6, "Latin Capital Letter AE"},
{0xC7, "Latin Capital Letter C With Cedilla"},
{0xC8, "Latin Capital Letter E With Grave"},
{0xC9, "Latin Capital Letter E With Acute"},
{0xCA, "Latin Capital Letter E With Circumflex"},
{0xCB, "Latin Capital Letter E With Diaeresis"},
{0xCC, "Latin Capital Letter I With Grave"},
{0xCD, "Latin Capital Letter I With Acute"},
{0xCE, "Latin Capital Letter I With Circumflex"},
{0xCF, "Latin Capital Letter I With Diaeresis"},
{0xD0, "Latin Capital Letter Eth"},
{0xD1, "Latin Capital Letter N With Tilde"},
{0xD2, "Latin Capital Letter O With Grave"},
{0xD3, "Latin Capital Letter O With Acute"},
{0xD4, "Latin Capital Letter O With Circumflex"},
{0xD5, "Latin Capital Letter O With Tilde"},
{0xD6, "Latin Capital Letter O With Diaeresis"},
{0xD8, "Latin Capital Letter O With Stroke"},
{0xD9, "Latin Capital Letter U With Grave"},
{0xDA, "Latin Capital Letter U With Acute"},
{0xDB, "Latin Capital Letter U With Circumflex"},
{0xDC, "Latin Capital Letter U With Diaeresis"},
{0xDD, "Latin Capital Letter Y With Acute"},
{0xDE, "Latin Capital Letter Thorn"},
{0x8A, "Latin Capital Letter S With Caron"},
{0x8C, "Latin Capital Ligature OE"},
{0x8E, "Latin Capital Letter Z With Caron"},
{0x9F, "Latin Capital Letter Y With Diaeresis"},
{0xDF, "Latin Small Letter Sharp S"},
{0xE0, "Latin Small Letter A With Grave"},
{0xE1, "Latin Small Letter A With Acute"},
{0xE2, "Latin Small Letter A With Circumflex"},
{0xE3, "Latin Small Letter A With Tilde"},
{0xE4, "Latin Small Letter A With Diaeresis"},
{0xE5, "Latin Small Letter A With Ring Above"},
{0xE6, "Latin Small Letter AE"},
{0xE7, "Latin Small Letter C With Cedilla"},
{0xE8, "Latin Small Letter E With Grave"},
{0xE9, "Latin Small Letter E With Acute"},
{0xEA, "Latin Small Letter E With Circumflex"},
{0xEB, "Latin Small Letter E With Diaeresis"},
{0xEC, "Latin Small Letter I With Grave"},
{0xED, "Latin Small Letter I With Acute"},
{0xEE, "Latin Small Letter I With Circumflex"},
{0xEF, "Latin Small Letter I With Diaeresis"},
{0xF0, "Latin Small Letter Eth"},
{0xF1, "Latin Small Letter N With Tilde"},
{0xF2, "Latin Small Letter O With Grave"},
{0xF3, "Latin Small Letter O With Acute"},
{0xF4, "Latin Small Letter O With Circumflex"},
{0xF5, "Latin Small Letter O With Tilde"},
{0xF6, "Latin Small Letter O With Diaeresis"},
{0xF8, "Latin Small Letter O With Stroke"},
{0xF9, "Latin Small Letter U With Grave"},
{0xFA, "Latin Small Letter U With Acute"},
{0xFB, "Latin Small Letter U With Circumflex"},
{0xFC, "Latin Small Letter U With Diaeresis"},
{0xFD, "Latin Small Letter Y With Acute"},
{0xFE, "Latin Small Letter Thorn"},
{0xFF, "Latin Small Letter Y With Diaeresis"},
{0x9A, "Latin Small Letter S With Caron"},
{0x9C, "Latin Small Ligature OE"},
{0x9E, "Latin Small Letter Z With Caron"},
{0x83, "Latin Small Letter F With Hook"},
};
// Typographic punctuation.
static const Glyph PUNCT[] = {
{0xA1, "Inverted Exclamation Mark"},
{0xBF, "Inverted Question Mark"},
{0x91, "Left Single Quotation Mark"},
{0x92, "Right Single Quotation Mark"},
{0x93, "Left Double Quotation Mark"},
{0x94, "Right Double Quotation Mark"},
{0x82, "Single Low-9 Quotation Mark"},
{0x84, "Double Low-9 Quotation Mark"},
{0xAB, "Left-Pointing Double Angle Quotation Mark"},
{0xBB, "Right-Pointing Double Angle Quotation Mark"},
{0x8B, "Single Left-Pointing Angle Quotation Mark"},
{0x9B, "Single Right-Pointing Angle Quotation Mark"},
{0x96, "En Dash"},
{0x97, "Em Dash"},
{0x85, "Horizontal Ellipsis"},
{0x95, "Bullet"},
{0xB7, "Middle Dot"},
{0x86, "Dagger"},
{0x87, "Double Dagger"},
{0xB6, "Pilcrow Sign"},
{0xA7, "Section Sign"},
{0x89, "Per Mille Sign"},
};
// Currency symbols.
static const Glyph CURRENCY[] = {
{0x24, "Dollar Sign"},
{0xA2, "Cent Sign"},
{0xA3, "Pound Sign"},
{0xA5, "Yen Sign"},
{0x80, "Euro Sign"},
{0xA4, "Currency Sign"},
{0x83, "Latin Small Letter F With Hook (Florin)"},
};
// Math, technical, and miscellaneous symbols.
static const Glyph SYMBOLS[] = {
{0xA9, "Copyright Sign"},
{0xAE, "Registered Sign"},
{0x99, "Trade Mark Sign"},
{0xB0, "Degree Sign"},
{0xB1, "Plus-Minus Sign"},
{0xD7, "Multiplication Sign"},
{0xF7, "Division Sign"},
{0xB5, "Micro Sign"},
{0xAC, "Not Sign"},
{0xBC, "Vulgar Fraction One Quarter"},
{0xBD, "Vulgar Fraction One Half"},
{0xBE, "Vulgar Fraction Three Quarters"},
{0xB9, "Superscript One"},
{0xB2, "Superscript Two"},
{0xB3, "Superscript Three"},
{0xAA, "Feminine Ordinal Indicator"},
{0xBA, "Masculine Ordinal Indicator"},
{0xA6, "Broken Bar"},
{0xB4, "Acute Accent"},
{0xA8, "Diaeresis"},
{0xAF, "Macron"},
{0xB8, "Cedilla"},
{0x88, "Modifier Letter Circumflex Accent"},
{0x98, "Small Tilde"},
};
#define SET(arr, label) { label, arr, (int)(sizeof(arr) / sizeof(arr[0])) }
static const CharSet SETS[] = {
SET(LATIN, "Latin"),
SET(PUNCT, "Punctuation"),
SET(CURRENCY, "Currency"),
SET(SYMBOLS, "Symbols"),
};
static constexpr int SET_COUNT = (int)(sizeof(SETS) / sizeof(SETS[0]));
// ==== Layout constants ====
static constexpr int INIT_W = 680;
static constexpr int INIT_H = 448;
static constexpr int TAB_H = 40;
static constexpr int FOOTER_H = 84;
static constexpr int GRID_PAD = 12;
static constexpr int CELL_GAP = 6;
static constexpr int CELL_TARGET = 54; // desired tile edge in px
static constexpr int SCROLLBAR_W = 8;
static constexpr int TAB_SIZE = 16;
static constexpr int CELL_SIZE = 26; // glyph font size inside a tile
static constexpr int PREVIEW_SIZE = 40;
static constexpr int NAME_SIZE = 16;
static constexpr int META_SIZE = 14;
static constexpr int TOAST_SIZE = 14;
static constexpr uint64_t TOAST_MS = 1400;
// ==== App state ====
static int g_set = 0; // active character set index
static int g_sel = 0; // selected glyph index within active set
static int g_hover = -1; // hovered glyph index, or -1
static int g_tab_hover = -1; // hovered tab index, or -1
static bool g_copy_hover = false;
static bool g_copy_pressed = false;
static int g_scroll = 0; // vertical scroll offset in px
static uint64_t g_toast_until = 0;
static char g_toast[48] = {};
static mtk::Theme g_theme;
// ==== Derived layout ====
struct Layout {
Rect tabs;
Rect grid; // interior area available for the tile grid
Rect footer;
Rect copy_btn;
int cols;
int cell_w; // tile slot width (includes gap handling)
int cell_h; // tile slot height
int content_h; // full scrollable height
int max_scroll;
};
static const CharSet& cur_set() { return SETS[g_set]; }
static Layout compute_layout(int w, int h) {
Layout L {};
L.tabs = { 0, 0, w, TAB_H };
int grid_top = TAB_H;
L.footer = { 0, h - FOOTER_H, w, FOOTER_H };
L.grid = { GRID_PAD, grid_top + GRID_PAD,
w - GRID_PAD * 2, (h - FOOTER_H) - grid_top - GRID_PAD * 2 };
if (L.grid.w < CELL_TARGET) L.grid.w = CELL_TARGET;
if (L.grid.h < 1) L.grid.h = 1;
int avail = L.grid.w - SCROLLBAR_W;
if (avail < CELL_TARGET) avail = CELL_TARGET;
L.cols = (avail + CELL_GAP) / (CELL_TARGET + CELL_GAP);
if (L.cols < 1) L.cols = 1;
L.cell_w = (avail - CELL_GAP * (L.cols - 1)) / L.cols;
if (L.cell_w < 8) L.cell_w = 8;
L.cell_h = L.cell_w;
int rows = (cur_set().count + L.cols - 1) / L.cols;
L.content_h = rows > 0 ? rows * L.cell_h + (rows - 1) * CELL_GAP : 0;
L.max_scroll = L.content_h - L.grid.h;
if (L.max_scroll < 0) L.max_scroll = 0;
// Copy button in the footer, right-aligned.
int btn_w = 96, btn_h = 34;
L.copy_btn = { w - GRID_PAD - btn_w,
L.footer.y + (FOOTER_H - btn_h) / 2, btn_w, btn_h };
return L;
}
// Screen rect of tile `i` (may be off-screen; caller clips).
static Rect tile_rect(const Layout& L, int i) {
int row = i / L.cols;
int col = i % L.cols;
int x = L.grid.x + col * (L.cell_w + CELL_GAP);
int y = L.grid.y + row * (L.cell_h + CELL_GAP) - g_scroll;
return { x, y, L.cell_w, L.cell_h };
}
static int hit_tile(const Layout& L, int mx, int my) {
if (my < L.grid.y || my >= L.grid.y + L.grid.h) return -1;
if (mx < L.grid.x || mx >= L.grid.x + L.grid.w - SCROLLBAR_W) return -1;
for (int i = 0; i < cur_set().count; i++) {
Rect r = tile_rect(L, i);
if (r.contains(mx, my)) return i;
}
return -1;
}
static int hit_tab(const Layout& L, int mx, int my) {
if (my < L.tabs.y || my >= L.tabs.y + L.tabs.h) return -1;
int tab_w = L.tabs.w / SET_COUNT;
if (tab_w <= 0) return -1;
int idx = mx / tab_w;
if (idx < 0 || idx >= SET_COUNT) return -1;
return idx;
}
static void clamp_scroll(const Layout& L) {
if (g_scroll > L.max_scroll) g_scroll = L.max_scroll;
if (g_scroll < 0) g_scroll = 0;
}
// Keep the selected tile fully visible (used after keyboard navigation).
static void ensure_visible(const Layout& L) {
int row = g_sel / L.cols;
int top = row * (L.cell_h + CELL_GAP);
int bot = top + L.cell_h;
if (top - g_scroll < 0) g_scroll = top;
else if (bot - g_scroll > L.grid.h) g_scroll = bot - L.grid.h;
clamp_scroll(L);
}
// ==== Helpers ====
static void glyph_str(uint8_t byte, char out[2]) {
out[0] = (char)byte;
out[1] = '\0';
}
static int codepoint_of(uint8_t byte) {
return decode_single_byte_codepoint((int)byte);
}
static void copy_selected() {
const Glyph& g = cur_set().glyphs[g_sel];
char s[2];
glyph_str(g.byte, s);
gui::clipboard_set_text(s, 1);
// Toast: Copied "<glyph>"
int n = 0;
const char* pfx = "Copied ";
while (pfx[n]) { g_toast[n] = pfx[n]; n++; }
g_toast[n++] = '"';
g_toast[n++] = (char)g.byte;
g_toast[n++] = '"';
g_toast[n] = '\0';
g_toast_until = montauk::get_milliseconds() + TOAST_MS;
}
// ==== Rendering ====
static void draw_centered_glyph(Canvas& c, const Rect& r, uint8_t byte,
Color color, int size) {
char s[2];
glyph_str(byte, s);
int tw = text_width(fonts::system_font, s, size);
int th = text_height(fonts::system_font, size);
draw_text(c, fonts::system_font, r.x + (r.w - tw) / 2,
r.y + (r.h - th) / 2, s, color, size);
}
static void render(Canvas& c) {
Layout L = compute_layout(c.w, c.h);
clamp_scroll(L);
c.fill(g_theme.window_bg);
// ---- Tile grid (drawn first; header/tabs/footer are painted over any
// spill from partially-scrolled edge rows) ----
const CharSet& set = cur_set();
for (int i = 0; i < set.count; i++) {
Rect r = tile_rect(L, i);
if (r.y + r.h <= L.grid.y || r.y >= L.grid.y + L.grid.h) continue;
bool selected = (i == g_sel);
bool hovered = (i == g_hover);
Color fill = g_theme.surface;
Color glyph = g_theme.text;
Color border = g_theme.border;
if (selected) {
fill = g_theme.accent;
glyph = g_theme.accent_fg;
border = g_theme.accent;
} else if (hovered) {
fill = g_theme.surface_hover;
}
c.fill_rounded_rect(r.x, r.y, r.w, r.h, g_theme.radius_md, border);
c.fill_rounded_rect(r.x + 1, r.y + 1, r.w - 2, r.h - 2,
g_theme.radius_md > 0 ? g_theme.radius_md - 1 : 0, fill);
draw_centered_glyph(c, r, set.glyphs[i].byte, glyph, CELL_SIZE);
}
// ---- Scrollbar ----
if (L.max_scroll > 0 && L.content_h > 0) {
int track_x = L.grid.x + L.grid.w - SCROLLBAR_W + 2;
int track_h = L.grid.h;
int thumb_h = track_h * L.grid.h / L.content_h;
if (thumb_h < 24) thumb_h = 24;
int thumb_y = L.grid.y + (track_h - thumb_h) * g_scroll / L.max_scroll;
c.fill_rounded_rect(track_x, thumb_y, SCROLLBAR_W - 4, thumb_h,
(SCROLLBAR_W - 4) / 2, g_theme.border);
}
// ---- Tab bar (opaque) ----
c.fill_rect(L.tabs.x, L.tabs.y, L.tabs.w, L.tabs.h, g_theme.window_bg);
int tab_w = L.tabs.w / SET_COUNT;
for (int t = 0; t < SET_COUNT; t++) {
int tx = t * tab_w;
bool active = (t == g_set);
if (t == g_tab_hover && !active)
c.fill_rect(tx, L.tabs.y, tab_w, L.tabs.h, g_theme.surface_hover);
Color tc = active ? g_theme.accent
: (t == g_tab_hover ? g_theme.text : g_theme.text_muted);
int lw = text_width(fonts::system_font, SETS[t].label, TAB_SIZE);
int lh = text_height(fonts::system_font, TAB_SIZE);
draw_text(c, fonts::system_font, tx + (tab_w - lw) / 2,
L.tabs.y + (L.tabs.h - lh) / 2 - 1, SETS[t].label, tc, TAB_SIZE);
if (active) {
int uw = lw + 16;
c.fill_rounded_rect(tx + (tab_w - uw) / 2, L.tabs.y + L.tabs.h - 3,
uw, 3, 1, g_theme.accent);
}
}
c.fill_rect(L.tabs.x, L.tabs.y + L.tabs.h - 1, L.tabs.w, 1, g_theme.border);
// Bold font for the footer name and Copy button.
TrueTypeFont* title_font =
(fonts::system_bold && fonts::system_bold->valid) ? fonts::system_bold
: fonts::system_font;
// ---- Footer (opaque) ----
c.fill_rect(L.footer.x, L.footer.y, L.footer.w, L.footer.h, g_theme.surface_alt);
c.fill_rect(L.footer.x, L.footer.y, L.footer.w, 1, g_theme.border);
const Glyph& sg = set.glyphs[g_sel];
// Preview box.
int pv = FOOTER_H - 20;
Rect preview = { GRID_PAD, L.footer.y + 10, pv, pv };
c.fill_rounded_rect(preview.x, preview.y, preview.w, preview.h,
g_theme.radius_md, g_theme.border);
c.fill_rounded_rect(preview.x + 1, preview.y + 1, preview.w - 2, preview.h - 2,
g_theme.radius_md - 1, colors::WHITE);
draw_centered_glyph(c, preview, sg.byte, g_theme.text, PREVIEW_SIZE);
// Name + metadata.
int cp = codepoint_of(sg.byte);
int text_x = preview.x + preview.w + 14;
draw_text(c, title_font, text_x, L.footer.y + 16, sg.name,
g_theme.text, NAME_SIZE);
char meta[64];
snprintf(meta, sizeof(meta), "U+%04X dec %d Alt 0%d",
cp, cp, (int)sg.byte);
draw_text(c, fonts::system_font, text_x, L.footer.y + 16 + NAME_SIZE + 8,
meta, g_theme.text_subtle, META_SIZE);
// Copy button.
Color btn_bg = g_theme.accent;
if (g_copy_pressed) btn_bg = g_theme.accent_hover;
else if (g_copy_hover) btn_bg = mtk::darken(g_theme.accent, 16);
draw_button(c, title_font, L.copy_btn.x, L.copy_btn.y, L.copy_btn.w,
L.copy_btn.h, "Copy", btn_bg, g_theme.accent_fg,
g_theme.radius_md, TAB_SIZE);
// ---- Toast (drawn last; floats over the lower-right of the grid) ----
if (g_toast[0] && montauk::get_milliseconds() < g_toast_until) {
int tw = text_width(fonts::system_font, g_toast, TOAST_SIZE);
int pill_w = tw + 24, pill_h = 28;
int px = c.w - GRID_PAD - pill_w;
int py = L.footer.y - GRID_PAD - pill_h;
c.fill_rounded_rect(px, py, pill_w, pill_h, pill_h / 2, g_theme.accent);
draw_text(c, fonts::system_font, px + 12,
py + (pill_h - text_height(fonts::system_font, TOAST_SIZE)) / 2,
g_toast, g_theme.accent_fg, TOAST_SIZE);
}
}
// ==== Event handling ====
static bool handle_mouse(const montauk::abi::WinEvent& ev, int w, int h) {
Layout L = compute_layout(w, h);
clamp_scroll(L);
bool redraw = false;
int mx = ev.mouse.x, my = ev.mouse.y;
// Wheel scroll.
if (ev.mouse.scroll != 0) {
g_scroll -= ev.mouse.scroll * (L.cell_h + CELL_GAP) / 2;
clamp_scroll(L);
redraw = true;
}
int tile = hit_tile(L, mx, my);
if (tile != g_hover) { g_hover = tile; redraw = true; }
int tab = hit_tab(L, mx, my);
if (tab != g_tab_hover) { g_tab_hover = tab; redraw = true; }
bool copy_hov = L.copy_btn.contains(mx, my);
if (copy_hov != g_copy_hover) { g_copy_hover = copy_hov; redraw = true; }
bool pressed = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
bool released = !(ev.mouse.buttons & 1) && (ev.mouse.prev_buttons & 1);
if (pressed) {
if (tab >= 0 && tab != g_set) {
g_set = tab;
g_sel = 0;
g_scroll = 0;
redraw = true;
} else if (tile >= 0) {
g_sel = tile;
copy_selected(); // click a tile = select + copy
redraw = true;
} else if (copy_hov) {
g_copy_pressed = true;
redraw = true;
}
}
if (released && g_copy_pressed) {
g_copy_pressed = false;
if (copy_hov) copy_selected();
redraw = true;
}
return redraw;
}
static bool handle_key(const montauk::abi::KeyEvent& key, int w, int h) {
if (!key.pressed) return false;
Layout L = compute_layout(w, h);
clamp_scroll(L);
const int count = cur_set().count;
bool redraw = false;
switch (key.scancode) {
case 0x4B: // Left
if (g_sel > 0) { g_sel--; ensure_visible(L); redraw = true; }
break;
case 0x4D: // Right
if (g_sel < count - 1) { g_sel++; ensure_visible(L); redraw = true; }
break;
case 0x48: // Up
if (g_sel - L.cols >= 0) { g_sel -= L.cols; ensure_visible(L); redraw = true; }
break;
case 0x50: // Down
if (g_sel + L.cols < count) { g_sel += L.cols; ensure_visible(L); redraw = true; }
break;
case 0x0F: // Tab -> next character set
g_set = (g_set + (key.shift ? SET_COUNT - 1 : 1)) % SET_COUNT;
g_sel = 0; g_scroll = 0; redraw = true;
break;
case 0x1C: // Enter
case 0x39: // Space
copy_selected();
redraw = true;
break;
default:
break;
}
return redraw;
}
extern "C" void _start() {
fonts::init();
g_theme = mtk::make_theme();
WsWindow win;
if (!win.create("Character Map", INIT_W, INIT_H))
montauk::exit(1);
{
Canvas canvas = win.canvas();
render(canvas);
}
win.present();
bool toast_shown = true;
while (win.id >= 0 && !win.closed) {
montauk::abi::WinEvent ev;
int r = win.poll(&ev);
if (r < 0) break;
if (r == 0) {
// Expire the toast even when idle so the pill disappears.
if (toast_shown && g_toast[0] &&
montauk::get_milliseconds() >= g_toast_until) {
Canvas canvas = win.canvas();
render(canvas);
win.present();
toast_shown = false;
}
montauk::sleep_ms(16);
continue;
}
bool redraw = false;
if (ev.type == 3) break; // close
if (ev.type == 2 || ev.type == 4) { // resize / scale
g_hover = -1;
g_tab_hover = -1;
redraw = true;
} else if (ev.type == 1) {
redraw = handle_mouse(ev, win.width, win.height);
} else if (ev.type == 0) {
redraw = handle_key(ev.key, win.width, win.height);
}
if (redraw) {
Canvas canvas = win.canvas();
render(canvas);
win.present();
toast_shown = true;
}
}
win.destroy();
montauk::exit(0);
}