diff --git a/Makefile b/Makefile index baf0855..67d7823 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ CPPC = g++ CPPC_FLAGS = -std=c++23 -s -O3 -Wall -Wextra -Wno-write-strings -lncurses -lmenu -lform -lcups DEBUG_FLAGS = -ggdb -std=c++23 -Wall -Wextra -Wno-write-strings -lncurses -lmenu -lform -lcups +DEBUG_ASANITIZE = -fsanitize=address -ggdb -fno-omit-frame-pointer -std=c++23 -lncurses -lmenu -lcups -Wall -Wextra -Wno-write-strings SRC_PATH := src @@ -19,6 +20,9 @@ all: make-build-dir $(BIN_PATH)/ParaDocs debug: CPPC_FLAGS = $(DEBUG_FLAGS) debug: make-build-dir $(BIN_PATH)/ParaDocs +asan: CPPC_FLAGS = $(DEBUG_ASANITIZE) +asan: make-build-dir $(BIN_PATH)/ParaDocs + make-build-dir: mkdir -p $(OBJ_PATH) @@ -39,4 +43,4 @@ install: clean: rm -fr build -.PHONY: all clean install debug +.PHONY: all clean install debug asan diff --git a/src/const.h b/src/const.h index 119d144..4b70c76 100644 --- a/src/const.h +++ b/src/const.h @@ -1,7 +1,20 @@ +#include #ifndef NAME #define NAME "ParaDocs" #define VERSION "0.0.1" +inline constexpr auto hash_djb2a(const std::string_view sv) { + unsigned long hash{5381}; + for (unsigned char c : sv) { + hash = ((hash << 5) + hash) ^ c; + } + return hash; +} + +inline constexpr auto operator"" _sh(const char* str, size_t len) { + return hash_djb2a(std::string_view{str, len}); +} + #endif \ No newline at end of file diff --git a/src/cups.cpp b/src/cups.cpp index e344ca4..0b68d00 100644 --- a/src/cups.cpp +++ b/src/cups.cpp @@ -5,22 +5,16 @@ #include #include #include +#include "memory.h" +#include "types.h" + +std::vector cups_allocated; -/** - * Displays a menu of available printers and prints content to the selected one - * @param content The text content to print - * @return true if printing was successful, false otherwise - */ bool printDocument(const std::string& content) { + current_allocated = &cups_allocated; // Initialize variables bool success = false; - cups_dest_t* dests = nullptr; - int num_dests = 0; - ITEM** menu_items = nullptr; - MENU* menu = nullptr; - WINDOW* menu_win = nullptr; - std::vector item_names; - std::vector item_descriptions; + complete_menu printer_menu = {nullptr, nullptr, 0, nullptr}; // Write content to a temporary file std::string tempFileName = "/tmp/print_temp_ParaDocs.txt"; @@ -32,12 +26,20 @@ bool printDocument(const std::string& content) { tempFile.close(); // Get printer destinations from CUPS - num_dests = cupsGetDests(&dests); + cups_dest_t* dests = nullptr; + int num_dests = cupsGetDests(&dests); if (num_dests <= 0) { remove(tempFileName.c_str()); return false; // No printers available } + char** item_names = new char*[num_dests]; + cups_allocated.push_back( + {CHAR_PTR_ARRAY, item_names, static_cast(num_dests)}); + char** item_descriptions = new char*[num_dests]; + cups_allocated.push_back( + {CHAR_PTR_ARRAY, item_descriptions, static_cast(num_dests)}); + // Initialize ncurses initscr(); start_color(); @@ -47,7 +49,7 @@ bool printDocument(const std::string& content) { try { // Allocate memory for menu items - menu_items = new ITEM*[num_dests + 1]; + printer_menu.items = new ITEM*[num_dests + 1]; // Prepare menu items for each printer for (int i = 0; i < num_dests; i++) { @@ -62,7 +64,7 @@ bool printDocument(const std::string& content) { // Allocate memory for name string char* name_str = new char[display_name.length() + 1]; std::strcpy(name_str, display_name.c_str()); - item_names.push_back(name_str); + item_names[i] = name_str; // Get description if available const char* desc = @@ -72,54 +74,55 @@ bool printDocument(const std::string& content) { // Allocate memory for description string char* desc_str = new char[description.length() + 1]; std::strcpy(desc_str, description.c_str()); - item_descriptions.push_back(desc_str); + item_descriptions[i] = desc_str; // Create menu item - menu_items[i] = new_item(name_str, desc_str); - if (!menu_items[i]) { + printer_menu.items[i] = new_item(name_str, desc_str); + if (!printer_menu.items[i]) { throw std::runtime_error("Failed to create menu item"); } } - menu_items[num_dests] = nullptr; // NULL-terminate the array + printer_menu.items[num_dests] = nullptr; // NULL-terminate the array // Create the menu - menu = new_menu(menu_items); - if (!menu) { + printer_menu.menu = new_menu(printer_menu.items); + if (!printer_menu.menu) { throw std::runtime_error("Failed to create menu"); } // Set up menu window int rows, cols; getmaxyx(stdscr, rows, cols); - menu_win = newwin(rows - 4, cols - 4, 2, 2); - keypad(menu_win, TRUE); + printer_menu.win = newwin(rows - 4, cols - 4, 2, 2); + keypad(printer_menu.win, TRUE); // Set menu window and sub window - set_menu_win(menu, menu_win); - set_menu_sub(menu, derwin(menu_win, rows - 6, cols - 6, 1, 1)); - set_menu_format(menu, rows - 8, 1); - set_menu_mark(menu, " * "); + set_menu_win(printer_menu.menu, printer_menu.win); + set_menu_sub(printer_menu.menu, + derwin(printer_menu.win, rows - 6, cols - 6, 1, 1)); + set_menu_format(printer_menu.menu, rows - 8, 1); + set_menu_mark(printer_menu.menu, " * "); // Post the menu with instructions mvprintw(0, 0, "Select a printer and press Enter:"); mvprintw(rows - 2, 0, "Press q to cancel"); refresh(); - post_menu(menu); - wrefresh(menu_win); + post_menu(printer_menu.menu); + wrefresh(printer_menu.win); // Menu interaction loop int c; - while ((c = wgetch(menu_win)) != 'q') { + while ((c = wgetch(printer_menu.win)) != 'q') { switch (c) { case KEY_DOWN: - menu_driver(menu, REQ_DOWN_ITEM); + menu_driver(printer_menu.menu, REQ_DOWN_ITEM); break; case KEY_UP: - menu_driver(menu, REQ_UP_ITEM); + menu_driver(printer_menu.menu, REQ_UP_ITEM); break; case 10: // Enter key { - ITEM* cur = current_item(menu); + ITEM* cur = current_item(printer_menu.menu); int index = item_index(cur); // Print the document to the selected printer @@ -142,7 +145,7 @@ bool printDocument(const std::string& content) { break; } } - wrefresh(menu_win); + wrefresh(printer_menu.win); } } catch (const std::exception& e) { mvprintw(0, 0, "Error: %s", e.what()); @@ -151,39 +154,16 @@ bool printDocument(const std::string& content) { } exit_menu: - // Clean up resources - if (menu) { - unpost_menu(menu); - free_menu(menu); - } - - if (menu_items) { - for (int i = 0; i < num_dests && menu_items[i]; i++) { - free_item(menu_items[i]); - } - delete[] menu_items; - } - - // Free allocated strings - for (char* str : item_names) { - delete[] str; - } - for (char* str : item_descriptions) { - delete[] str; - } - - if (menu_win) { - delwin(menu_win); - } - - // End ncurses - endwin(); + unpost_menu(printer_menu.menu); + wclear(printer_menu.win); + clear(); // Remove temp file remove(tempFileName.c_str()); // Free CUPS destinations cupsFreeDests(num_dests, dests); + delete_all(&cups_allocated); return success; } diff --git a/src/gameske_funkce.cpp b/src/gameske_funkce.cpp index 8c3a35c..05f1a30 100644 --- a/src/gameske_funkce.cpp +++ b/src/gameske_funkce.cpp @@ -12,7 +12,9 @@ #include #include #include +#include "memory.h" +/* size_t spawn_menu(uint16_t begin_y, uint16_t begin_x, const char** choices, size_t n_choices) { ITEM** sp_items; @@ -22,26 +24,26 @@ size_t spawn_menu(uint16_t begin_y, uint16_t begin_x, const char** choices, int i; sp_items = new ITEM*[n_choices]; - /* Create items */ + // Create items for (i = 0; i < n_choices; ++i) { sp_items[i] = new_item(choices[i], choices[i]); } - /* Crate menu */ + // Crate menu sp_menu = new_menu(sp_items); - /* Create the window to be associated with the menu */ + // Create the window to be associated with the menu sp_menu_win = newwin(10, 40, begin_y, begin_x); keypad(sp_menu_win, TRUE); - /* Set main window and sub window */ + // Set main window and sub window set_menu_win(sp_menu, sp_menu_win); set_menu_sub(sp_menu, derwin(sp_menu_win, 6, 38, begin_y - 1, begin_x - 3)); - /* Set menu mark to the string " * " */ + // Set menu mark to the string " * " set_menu_mark(sp_menu, " * "); - /* Print a border around the main window and print a title */ + // Print a border around the main window and print a title box(sp_menu_win, 0, 0); print_in_middle(sp_menu_win, 1, 0, 40, "My Menu", COLOR_PAIR(1)); mvwaddch(sp_menu_win, 2, 0, ACS_LTEE); @@ -50,7 +52,7 @@ size_t spawn_menu(uint16_t begin_y, uint16_t begin_x, const char** choices, mvprintw(LINES - 2, 0, "F1 to exit"); refresh(); - /* Post the menu */ + // Post the menu post_menu(sp_menu); wrefresh(sp_menu_win); @@ -67,7 +69,7 @@ size_t spawn_menu(uint16_t begin_y, uint16_t begin_x, const char** choices, } size_t selected = item_index(current_item(sp_menu)); - /* Unpost and free all the memory taken up */ + // Unpost and free all the memory taken up unpost_menu(sp_menu); free_menu(sp_menu); for (i = 0; i < n_choices; ++i) @@ -76,6 +78,7 @@ size_t spawn_menu(uint16_t begin_y, uint16_t begin_x, const char** choices, return selected; } +*/ void print_in_middle(WINDOW* win, int starty, int startx, int width, char* string, chtype color) { @@ -157,9 +160,17 @@ std::string spawncmd() { return std::string(cmd); } -void async_clock(WINDOW* win) { - std::this_thread::sleep_for(std::chrono::milliseconds(150)); +void async_clock(WINDOW* win, WINDOW* text_win) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + bool prev_echo_state; + uint16_t prev_y, prev_x; while (true) { + prev_echo_state = is_echo(); + getyx(stdscr, prev_y, prev_x); + noecho(); + wborder(text_win, 0, ' ', 0, 0, 0, ACS_HLINE, 0, ACS_HLINE); + mvwprintw(text_win, 1, 1, "správný čas na podání trestního oznámení je"); + wrefresh(text_win); auto now = std::chrono::current_zone()->to_local(std::chrono::system_clock::now()); std::string time_str; @@ -176,37 +187,30 @@ void async_clock(WINDOW* win) { time_str = "Error"; } - // Clear window content but preserve border werase(win); wborder(win, 0, 0, 0, 0, ACS_TTEE, 0, ACS_BTEE, 0); - // Print the time in the window mvwprintw(win, 1, 1, "%s", time_str.c_str()); wrefresh(win); + if (prev_echo_state) { + echo(); + } + move(prev_y, prev_x); + refresh(); std::this_thread::sleep_for(std::chrono::seconds(1)); } } -void show_clock_text(WINDOW* text_win) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - wborder(text_win, 0, ' ', 0, 0, 0, ACS_HLINE, 0, ACS_HLINE); - mvwprintw(text_win, 1, 1, "správný čas na podání trestního oznámení je"); - wrefresh(text_win); -} - void async_clock_init() { - // program only launches sometimes i think there is race condition //memory leak WINDOW* win = newwin(3, 10, LINES - 3, COLS - 10); - box(win, 0, 0); - wrefresh(win); - std::thread clock_thread(async_clock, win); - clock_thread.detach(); + current_allocated->push_back({WINDOW_TYPE, win, 1}); //memory leak WINDOW* text_win = newwin(3, 52, LINES - 3, COLS - 10 - 44); + current_allocated->push_back({WINDOW_TYPE, text_win, 1}); - std::thread text_thread(show_clock_text, text_win); - text_thread.detach(); + std::thread clock_thread(async_clock, win, text_win); + clock_thread.detach(); } diff --git a/src/main.cpp b/src/main.cpp index 05f0d43..3a551c3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,9 +1,11 @@ #include +#include #include #include #include "color.h" #include "const.h" #include "menu.h" +#include "signal.h" void PrintHelp() { std::cout << RED R"( ____ ____ @@ -26,6 +28,14 @@ void PrintVersion() { } int main(int argc, char* argv[]) { + // signal handlers + std::signal(SIGTERM, safe_exit); + std::signal(SIGINT, safe_exit); + std::signal(SIGQUIT, safe_exit); + std::signal(SIGHUP, safe_exit); + + // error signal handlers + signal(SIGSEGV, safe_exit); int opt; while ((opt = getopt(argc, argv, "hV")) != -1) { diff --git a/src/memory.cpp b/src/memory.cpp new file mode 100644 index 0000000..51198ec --- /dev/null +++ b/src/memory.cpp @@ -0,0 +1,99 @@ +#include "memory.h" +#include +#include +#include +// #include UNCOMENT IF IF WE USE PANELs +#include +#include "color.h" +#include "types.h" + +std::vector* current_allocated; + +template +struct NcursesDeleter { + static void delete_element(T obj); +}; + +template <> +void NcursesDeleter::delete_element(WINDOW* win) { + delwin(win); +} +/*template <> +void NcursesDeleter::delete_element(PANEL* pan) { + del_panel(pan); +} UNCOMENT IF WE USE PANELs*/ +template <> +void NcursesDeleter::delete_element(ITEM* item) { + free_item(item); +} + +template +void delete_ncurses_arrays(void* ptr, std::size_t size) { + T* array = static_cast(ptr); + for (std::size_t j = 0; j < size; ++j) { + NcursesDeleter::delete_element(array[j]); + } + delete[] array; +} + +void delete_all(std::vector* allocated) { + if (allocated == nullptr) { + return; + } + for (long long i = allocated->size() - 1; i >= 0; i--) { + switch (allocated->at(i).type) { + case WINDOW_ARRAY: { + delete_ncurses_arrays(allocated->at(i).ptr, + allocated->at(i).size); + break; + } + /* case PANEL_ARRAY: { + delete_ncurses_arrays(allocated->at(i).ptr, + allocated->at(i).size); + break; + } UNCOMENT IF WE USE PANELs*/ + case ITEM_ARRAY: { + delete_ncurses_arrays(allocated->at(i).ptr, + allocated->at(i).size); + break; + } + case CHAR_PTR_ARRAY: { + char** array = static_cast(allocated->at(i).ptr); + for (std::size_t j = 0; j < allocated->at(i).size; ++j) { + delete[] array[j]; + } + delete[] array; + break; + } + case GENERIC_ARRAY: + delete[] static_cast(allocated->at(i).ptr); + break; + case WINDOW_TYPE: + delwin(static_cast(allocated->at(i).ptr)); + break; + /*case PANEL_TYPE: + del_panel(static_cast(allocated->at(i).ptr)); + break; UNCOMENT IF WE USE PANELs */ + case MENU_TYPE: + free_menu(static_cast(allocated->at(i).ptr)); + break; + case COMPLETE_MENU_TYPE: { + free_menu(static_cast(allocated->at(i).ptr)->menu); + delwin(static_cast(allocated->at(i).ptr)->win); + delete_ncurses_arrays( + static_cast(allocated->at(i).ptr)->items, + static_cast(allocated->at(i).ptr)->items_size); + + break; + } + case GENERIC_TYPE: + delete static_cast(allocated->at(i).ptr); + break; + default: + std::cerr << RED "[!!CRITICAL!!]" << RESET " Unknown allocation type" + << "\n"; + break; + } + allocated->pop_back(); + } +} diff --git a/src/memory.h b/src/memory.h new file mode 100644 index 0000000..5715c56 --- /dev/null +++ b/src/memory.h @@ -0,0 +1,30 @@ +// header guard +#ifndef PARADOCS_MEMORY_H_ +#define PARADOCS_MEMORY_H_ + +#include +#include +#include +enum AllocationType { + WINDOW_ARRAY, + // PANEL_ARRAY, UNCOMENT IF WE USE PANELs + ITEM_ARRAY, + CHAR_PTR_ARRAY, + GENERIC_ARRAY, + WINDOW_TYPE, + // PANEL_TYPE, UNCOMENT IF WE USE PANELs + MENU_TYPE, + COMPLETE_MENU_TYPE, + GENERIC_TYPE +}; + +struct allocation { + AllocationType type; + void* ptr; + std::size_t size; +}; + +extern std::vector* current_allocated; + +void delete_all(std::vector* allocated); +#endif diff --git a/src/menu.cpp b/src/menu.cpp index d470de2..8f27667 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -1,23 +1,24 @@ #include #include +#include #include +#include #include #include +#include "const.h" +#include "cups.h" #include "gameske_funkce.h" +#include "memory.h" +#include "types.h" -#define CTRLD 4 +std::vector main_menu_allocated; const char* choices[] = {"paradox", "kompromis", "Stereotyp", "Žena"}; const char* date[] = {"2023-10-01", "2023-10-02", "2023-10-03", "2023-10-04"}; const char* choices2[] = {"PRINT", "EDIT", "DELETE", "Žena"}; void menu() { - ITEM** my_items; - int c; - MENU* my_menu; - WINDOW* my_menu_win; - int n_choices, i; - + current_allocated = &main_menu_allocated; /* Initialize curses */ setlocale(LC_ALL, ""); initscr(); @@ -32,61 +33,75 @@ void menu() { async_clock_init(); /* Create items */ - n_choices = ARRAY_SIZE(choices); - my_items = new ITEM*[ARRAY_SIZE(choices)]; - for (i = 0; i < n_choices; ++i) - my_items[i] = new_item(choices[i], date[i]); + size_t n_choices = ARRAY_SIZE(choices); + complete_menu main_menu = {nullptr, nullptr, 0, nullptr}; + main_menu_allocated.push_back({COMPLETE_MENU_TYPE, &main_menu, 1}); + + main_menu.items = new ITEM*[ARRAY_SIZE(choices) + 1]; + main_menu.items_size = ARRAY_SIZE(choices) + 1; + for (size_t i = 0; i < n_choices; ++i) { + main_menu.items[i] = new_item(choices[i], date[i]); + } + main_menu.items[n_choices] = nullptr; /* Crate menu */ - my_menu = new_menu(my_items); + main_menu.menu = new_menu(main_menu.items); /* Create the window to be associated with the menu */ - my_menu_win = newwin(10, 40, 4, 4); - keypad(my_menu_win, TRUE); + main_menu.win = newwin(10, 40, 4, 4); + keypad(main_menu.win, TRUE); /* Set main window and sub window */ - set_menu_win(my_menu, my_menu_win); - set_menu_sub(my_menu, derwin(my_menu_win, 6, 38, 3, 1)); + set_menu_win(main_menu.menu, main_menu.win); + set_menu_sub(main_menu.menu, derwin(main_menu.win, 6, 38, 3, 1)); /* Set menu mark to the string " * " */ - set_menu_mark(my_menu, " * "); + set_menu_mark(main_menu.menu, " * "); /* Print a border around the main window and print a title */ - box(my_menu_win, 0, 0); - print_in_middle(my_menu_win, 1, 0, 40, "My Menu", COLOR_PAIR(1)); - mvwaddch(my_menu_win, 2, 0, ACS_LTEE); - mvwhline(my_menu_win, 2, 1, ACS_HLINE, 38); - mvwaddch(my_menu_win, 2, 39, ACS_RTEE); + box(main_menu.win, 0, 0); + print_in_middle(main_menu.win, 1, 0, 40, "My Menu", COLOR_PAIR(1)); + mvwaddch(main_menu.win, 2, 0, ACS_LTEE); + mvwhline(main_menu.win, 2, 1, ACS_HLINE, 38); + mvwaddch(main_menu.win, 2, 39, ACS_RTEE); mvprintw(LINES - 2, 0, "F1 to exit"); refresh(); /* Post the menu */ - post_menu(my_menu); - wrefresh(my_menu_win); + post_menu(main_menu.menu); + wrefresh(main_menu.win); - while ((c = wgetch(my_menu_win)) != KEY_F(1)) { + int c; + while ((c = wgetch(main_menu.win)) != KEY_F(1)) { switch (c) { case KEY_DOWN: - menu_driver(my_menu, REQ_DOWN_ITEM); + menu_driver(main_menu.menu, REQ_DOWN_ITEM); break; case KEY_UP: - menu_driver(my_menu, REQ_UP_ITEM); + menu_driver(main_menu.menu, REQ_UP_ITEM); break; case 10: refresh(); break; case ':': - spawncmd(); + switch (hash_djb2a(spawncmd())) { + case "print"_sh: + case "p"_sh: + // DONT FORGET TO PRINT ACTUAL DOCUMENT + printDocument("test"); + current_allocated = &main_menu_allocated; + break; + default: + print_in_middle(main_menu.win, 10, 0, 40, "Unknown command", + COLOR_PAIR(1)); + break; + } } - wrefresh(my_menu_win); + wrefresh(main_menu.win); } - /* Unpost and free all the memory taken up */ - unpost_menu(my_menu); - free_menu(my_menu); - for (i = 0; i < n_choices; ++i) - free_item(my_items[i]); - delete[] my_items; + unpost_menu(main_menu.menu); + delete_all(&main_menu_allocated); endwin(); } diff --git a/src/signal.cpp b/src/signal.cpp new file mode 100644 index 0000000..d69d9a6 --- /dev/null +++ b/src/signal.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include "memory.h" +void safe_exit(int code) { + switch (code) { + case SIGTERM: + std::cerr << "\nreceived SIGTERM exiting...\n"; + break; + case SIGINT: + std::cerr << "\nreceived SIGINT exiting...\n"; + break; + case SIGQUIT: + std::cerr << "\nreceived SIGQUIT exiting...\n"; + break; + case SIGHUP: + std::cerr << "\nreceived SIGHUP exiting...\n"; + break; + + case SIGSEGV: + std::cerr << "\nreceived SIGSEGV(segmentaiton fault) exiting...\nIf this " + "repeats please report it as a bug\n"; + break; + + default: + break; + } + delete_all(current_allocated); + echo(); + endwin(); + + exit(code); +} \ No newline at end of file diff --git a/src/signal.h b/src/signal.h new file mode 100644 index 0000000..7108200 --- /dev/null +++ b/src/signal.h @@ -0,0 +1,7 @@ + +#ifndef PARADOCS_SIGNAL_H_ +#define PARADOCS_SIGNAL_H_ + +void safe_exit(int code); + +#endif \ No newline at end of file diff --git a/src/types.h b/src/types.h new file mode 100644 index 0000000..9efd57c --- /dev/null +++ b/src/types.h @@ -0,0 +1,14 @@ +#include +#include + +#ifndef PARADOCS_TYPES_H_ +#define PARADOCS_TYPES_H_ + +struct complete_menu { + WINDOW* win; + ITEM** items; + size_t items_size; + MENU* menu; +}; + +#endif \ No newline at end of file