#include #include #include #include #include #include #include #include #include #include #include #include #define FILE_PATH_COLUMN 0 static GtkListStore *store; static GtkWidget *tree_view, *window; static VteTerminal *terminal; static int inotify_fd; static char *base_dir; static char *current_dir; // Function declarations static void show_new_directory_dialog(); static void create_new_directory(const char *dir_name); static void show_deletion_dialog(); static void delete_selected_item(); static int get_directory_depth(const char *dir); static gboolean recursive_delete(const char *path); static void make_file_executable(const char *path); static void make_file_not_executable(const char *path); static const char *get_language_from_path(const char *path); static void compile_and_run(const char *path, const char *language); static gboolean show_confirmation_dialog(const char *message); static void create_new_file(const char *file_name); static void show_new_file_dialog(); static void display_directory(const char *dir); static void open_file_with_appropriate_application(const char *filepath); static gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer userdata); static gboolean on_button_press(GtkWidget *widget, GdkEventButton *event, gpointer userdata); static void on_row_activated(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *col, gpointer userdata); static GtkWidget* create_tree_view(); static void on_window_destroy(GtkWidget *widget, gpointer data); static char *strip_html_markup(const char *input); void run_executable(GtkMenuItem *menu_item, gpointer user_data); void make_file_executable_menu(GtkMenuItem *menu_item, gpointer user_data); void make_file_not_executable_menu(GtkMenuItem *menu_item, gpointer user_data); static gboolean has_extension(const char *filename, const char *extension) { const char *dot = strrchr(filename, '.'); return dot && !g_strcmp0(dot + 1, extension); } static gboolean show_confirmation_dialog(const char *message) { GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL, "%s", message); gint response = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); return (response == GTK_RESPONSE_OK); } static void handle_file_open_error(const char *filepath) { gchar *msg = g_strdup_printf("\nFailed to open '%s'\n", filepath); vte_terminal_feed_child(VTE_TERMINAL(terminal), msg, -1); g_free(msg); } static void open_file_with_appropriate_application(const char *filepath) { struct stat path_stat; if (stat(filepath, &path_stat) != 0 || S_ISDIR(path_stat.st_mode)) { g_print("Attempted to open a directory or invalid path: %s\n", filepath); return; } const char *const_argv[3] = {NULL, filepath, NULL}; char *argv[3]; if (has_extension(filepath, "jpg") || has_extension(filepath, "png")) { const_argv[0] = "feh"; } else if (has_extension(filepath, "pdf")) { const_argv[0] = "zathura"; } else { const_argv[0] = "nvim"; } for (int i = 0; i < 3; i++) { argv[i] = (char *)const_argv[i]; } vte_terminal_spawn_async(VTE_TERMINAL(terminal), VTE_PTY_DEFAULT, NULL, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, -1, NULL, NULL, NULL); } static void display_directory(const char *dir) { DIR *d; struct dirent *entry; GtkTreeIter iter; struct stat statbuf; gboolean readme_found = FALSE; char *readme_path = NULL; if ((d = opendir(dir)) == NULL) { fprintf(stderr, "Failed to open directory: %s\n", strerror(errno)); return; } gtk_list_store_clear(store); while ((entry = readdir(d)) != NULL) { char *full_path = g_strdup_printf("%s/%s", dir, entry->d_name); if (stat(full_path, &statbuf) == -1) { g_free(full_path); continue; } gchar *display_name; if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0 && strcmp(entry->d_name, "codeWS") != 0) { if (S_ISDIR(statbuf.st_mode)) { display_name = g_markup_printf_escaped("%s", entry->d_name); } else if (statbuf.st_mode & S_IXUSR) { display_name = g_markup_printf_escaped("%s", entry->d_name); } else { display_name = g_strdup(entry->d_name); } gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, 0, display_name, 1, entry->d_name, -1); g_free(display_name); if (g_ascii_strcasecmp(entry->d_name, "readme.md") == 0) { readme_found = TRUE; readme_path = g_strdup_printf("%s/%s", dir, entry->d_name); } } g_free(full_path); } closedir(d); if (readme_found && readme_path) { open_file_with_appropriate_application(readme_path); g_free(readme_path); } } static void navigate_up_directory() { if (strcmp(current_dir, base_dir) == 0) { return; } char *last_slash = strrchr(current_dir, '/'); if (last_slash != NULL) { *last_slash = '\0'; if (strcmp(current_dir, base_dir) >= 0) { display_directory(current_dir); } else { strcpy(current_dir, base_dir); display_directory(current_dir); } } } static char *strip_html_markup(const char *input) { if (input == NULL) return NULL; GError *error = NULL; GRegex *regex = g_regex_new("<[^>]*>", 0, 0, &error); if (error != NULL) { g_print("Regex error: %s\n", error->message); g_error_free(error); return NULL; } gchar *stripped = g_regex_replace_literal(regex, input, -1, 0, "", 0, NULL); g_regex_unref(regex); return stripped; } gboolean is_executable(const char *path) { struct stat st; if (stat(path, &st) == 0) { return st.st_mode & S_IXUSR; } return FALSE; } static gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer userdata) { if (event->keyval == GDK_KEY_BackSpace && !vte_terminal_get_has_selection(VTE_TERMINAL(terminal))) { navigate_up_directory(); return TRUE; } if ((event->state & GDK_CONTROL_MASK) && event->keyval == GDK_KEY_n) { show_new_directory_dialog(); return TRUE; } if ((event->state & GDK_CONTROL_MASK) && event->keyval == GDK_KEY_d) { show_deletion_dialog(); return TRUE; } if ((event->state & GDK_CONTROL_MASK) && event->keyval == GDK_KEY_r) { GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); GtkTreeModel *model; GtkTreeIter iter; gchar *filename; if (gtk_tree_selection_get_selected(selection, &model, &iter)) { gtk_tree_model_get(model, &iter, 0, &filename, -1); char *clean_filename = strip_html_markup(filename); char *path = g_strdup_printf("%s/%s", current_dir, clean_filename); const char *language = get_language_from_path(path); char *clean_path = strip_html_markup(path); if (clean_path) { g_print("Cleaned path: %s\n", clean_path); // Debug print compile_and_run(clean_path, language); g_free(clean_path); } else { g_print("Failed to clean path: %s\n", path); // Debug print } g_free(clean_filename); g_free(filename); g_free(path); } return TRUE; } if ((event->state & GDK_CONTROL_MASK) && event->keyval == GDK_KEY_e) { GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); GtkTreeModel *model; GtkTreeIter iter; gchar *filename; if (gtk_tree_selection_get_selected(selection, &model, &iter)) { gtk_tree_model_get(model, &iter, 0, &filename, -1); char *clean_filename = strip_html_markup(filename); char *path = g_strdup_printf("%s/%s", current_dir, clean_filename); make_file_executable(path); display_directory(current_dir); g_free(clean_filename); g_free(filename); g_free(path); } return TRUE; } if ((event->state & GDK_CONTROL_MASK) && event->keyval == GDK_KEY_f) { show_new_file_dialog(); return TRUE; } return FALSE; } static gboolean on_button_press(GtkWidget *widget, GdkEventButton *event, gpointer userdata) { if (event->type == GDK_BUTTON_PRESS && event->button == 3) { GtkTreePath *path; GtkTreeView *tree_view = GTK_TREE_VIEW(widget); if (gtk_tree_view_get_path_at_pos(tree_view, (gint)event->x, (gint)event->y, &path, NULL, NULL, NULL)) { GtkTreeModel *model = gtk_tree_view_get_model(tree_view); GtkTreeIter iter; if (gtk_tree_model_get_iter(model, &iter, path)) { gchar *file_name; gtk_tree_model_get(model, &iter, 1, &file_name, -1); // Construct the full path gchar *file_path = g_strdup_printf("%s/%s", current_dir, file_name); struct stat path_stat; if (stat(file_path, &path_stat) == 0 && !S_ISDIR(path_stat.st_mode)) { // Check if not a directory GtkWidget *menu = gtk_menu_new(); // Create and add "Make File Executable" item GtkWidget *make_exec_item = gtk_menu_item_new_with_label("Make File Executable"); g_signal_connect(make_exec_item, "activate", G_CALLBACK(make_file_executable_menu), g_strdup(file_path)); gtk_menu_shell_append(GTK_MENU_SHELL(menu), make_exec_item); // Create and add "Make File Not Executable" item GtkWidget *make_not_exec_item = gtk_menu_item_new_with_label("Make File Not Executable"); g_signal_connect(make_not_exec_item, "activate", G_CALLBACK(make_file_not_executable_menu), g_strdup(file_path)); gtk_menu_shell_append(GTK_MENU_SHELL(menu), make_not_exec_item); // Create and add "Run" item if the file is executable if (is_executable(file_path)) { GtkWidget *run_item = gtk_menu_item_new_with_label("Run"); g_signal_connect(run_item, "activate", G_CALLBACK(run_executable), g_strdup(file_path)); gtk_menu_shell_append(GTK_MENU_SHELL(menu), run_item); } gtk_widget_show_all(menu); gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event); } else { g_print("Right-clicked on a directory or invalid path: %s\n", file_path); } g_free(file_path); g_free(file_name); } gtk_tree_path_free(path); } return TRUE; } return FALSE; } void run_executable(GtkMenuItem *menu_item, gpointer user_data) { const char *file_path = (const char *)user_data; gchar *clean_path = g_strdup(file_path); // Duplicate the file path gchar *command = g_strdup_printf("clear && '%s'", clean_path); // Use single quotes to handle spaces in paths gchar *argv[] = {"bash", "-c", command, NULL}; g_print("Running executable: %s\n", clean_path); // Debug print g_print("Command: %s\n", command); // Print the command for debugging vte_terminal_spawn_async( VTE_TERMINAL(terminal), VTE_PTY_DEFAULT, current_dir, // Set the working directory argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, -1, NULL, NULL, NULL); g_free(command); g_free(clean_path); g_free((char *)file_path); // Free the strdup-ed file path } void make_file_executable_menu(GtkMenuItem *menu_item, gpointer user_data) { const char *file_path = (const char *)user_data; g_print("Making file executable: %s\n", file_path); make_file_executable(file_path); display_directory(current_dir); // Update the directory view immediately g_free((char *)file_path); // Free the strdup-ed file path } void make_file_not_executable_menu(GtkMenuItem *menu_item, gpointer user_data) { const char *file_path = (const char *)user_data; g_print("Making file not executable: %s\n", file_path); make_file_not_executable(file_path); display_directory(current_dir); // Update the directory view immediately g_free((char *)file_path); // Free the strdup-ed file path } static void on_row_activated(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *col, gpointer userdata) { GtkTreeModel *model = gtk_tree_view_get_model(treeview); GtkTreeIter iter; gchar *actual_name; if (gtk_tree_model_get_iter(model, &iter, path)) { gtk_tree_model_get(model, &iter, 1, &actual_name, -1); char *new_path = g_strdup_printf("%s/%s", current_dir, actual_name); struct stat path_stat; if (stat(new_path, &path_stat) == 0) { if (S_ISDIR(path_stat.st_mode)) { g_free(current_dir); current_dir = new_path; display_directory(current_dir); } else { open_file_with_appropriate_application(new_path); g_free(new_path); } } else { g_printerr("Failed to access %s: %s\n", new_path, strerror(errno)); g_free(new_path); } g_free(actual_name); } } static GtkWidget* create_tree_view() { tree_view = gtk_tree_view_new(); store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING); gtk_tree_view_set_model(GTK_TREE_VIEW(tree_view), GTK_TREE_MODEL(store)); GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes("Entries", renderer, "markup", 0, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column); g_signal_connect(tree_view, "row-activated", G_CALLBACK(on_row_activated), NULL); g_signal_connect(tree_view, "key-press-event", G_CALLBACK(on_key_press), NULL); g_signal_connect(tree_view, "button-press-event", G_CALLBACK(on_button_press), NULL); return tree_view; } static void on_window_destroy(GtkWidget *widget, gpointer data) { close(inotify_fd); g_free(base_dir); g_free(current_dir); gtk_main_quit(); } static char *extract_first_directory(const char *path) { if (!path) return NULL; path += strlen(base_dir); if (*path == '/') path++; const char *end = strchr(path, '/'); if (!end) end = path + strlen(path); size_t len = end - path; char *dir = malloc(len + 1); if (dir) { strncpy(dir, path, len); dir[len] = '\0'; } return dir; } static const char *get_language_from_path(const char *path) { if (!path) { printf("Error: Path is NULL\n"); return NULL; } char *language_dir = extract_first_directory(path); if (!language_dir) { printf("No language directory found or error in path processing.\n"); return NULL; } const char *language = NULL; if (strcmp(language_dir, "C") == 0) { language = "C"; } else if (strcmp(language_dir, "Python3") == 0) { language = "Python3"; } else if (strcmp(language_dir, "Asm") == 0) { language = "Assembly"; } if (language) { printf("Detected language: %s\n", language); } else { printf("No recognized language found in the directory name.\n"); } free(language_dir); return language; } static void compile_and_run(const char *path, const char *language) { if (path == NULL) { g_print("Error: Path is NULL\n"); return; } char *clean_path = strip_html_markup(path); if (clean_path == NULL) { g_print("Failed to clean path from HTML markup.\n"); return; } gchar *compile_cmd = NULL; gchar *cmd = g_strdup_printf("Are you sure you want to compile and run this %s program?", language); if (show_confirmation_dialog(cmd)) { gchar *dir_path = g_path_get_dirname(clean_path); if (dir_path == NULL) { g_print("Error: Unable to determine directory from path: %s\n", clean_path); g_free(cmd); g_free(clean_path); return; } if (strcasecmp(language, "C") == 0) { compile_cmd = g_strdup_printf("clear && cd '%s' && make clean && make", dir_path); } else if (strcasecmp(language, "Python3") == 0) { compile_cmd = g_strdup_printf("clear && cd %s && python3 %s\n", dir_path, clean_path); } else if (strcasecmp(language, "Assembly") == 0) { compile_cmd = g_strdup_printf("clear && cd %s && nasm -f elf64 %s -o %s.o && ld %s.o -o %s && ./%s\n", dir_path, clean_path, clean_path, clean_path, clean_path, clean_path); } g_free(dir_path); if (compile_cmd) { g_print("Compile command: %s\n", compile_cmd); vte_terminal_spawn_async( VTE_TERMINAL(terminal), VTE_PTY_DEFAULT, NULL, (char *[]){ "bash", "-c", compile_cmd, NULL }, NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, -1, NULL, NULL, NULL); g_free(compile_cmd); } else { g_print("Unsupported language: %s\n", language); } } g_free(cmd); g_free(clean_path); } static void make_file_executable(const char *path) { if (path == NULL) return; gchar *command = g_strdup_printf("chmod +x \"%s\"", path); gchar *argv[] = {"bash", "-c", command, NULL}; VteTerminal *vte_terminal = VTE_TERMINAL(terminal); vte_terminal_spawn_async( vte_terminal, VTE_PTY_DEFAULT, current_dir, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, -1, NULL, NULL, NULL); g_free(command); } static void make_file_not_executable(const char *path) { if (path == NULL) return; gchar *command = g_strdup_printf("chmod -x \"%s\"", path); gchar *argv[] = {"bash", "-c", command, NULL}; VteTerminal *vte_terminal = VTE_TERMINAL(terminal); vte_terminal_spawn_async( vte_terminal, VTE_PTY_DEFAULT, current_dir, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, -1, NULL, NULL, NULL); g_free(command); } static void create_new_file(const char *file_name) { if (file_name == NULL) return; gchar *full_path = g_strdup_printf("%s/%s", current_dir, file_name); FILE *file = fopen(full_path, "w"); if (file) { fclose(file); } else { g_print("Error creating file: %s\n", strerror(errno)); } g_free(full_path); } static void show_new_file_dialog() { GtkWidget *dialog, *content_area, *entry; dialog = gtk_dialog_new_with_buttons("New File", GTK_WINDOW(window), GTK_DIALOG_MODAL, "_OK", GTK_RESPONSE_OK, "_Cancel", GTK_RESPONSE_CANCEL, NULL); content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); entry = gtk_entry_new(); gtk_container_add(GTK_CONTAINER(content_area), entry); gtk_widget_show_all(dialog); gint response = gtk_dialog_run(GTK_DIALOG(dialog)); if (response == GTK_RESPONSE_OK) { const char *file_name = gtk_entry_get_text(GTK_ENTRY(entry)); create_new_file(file_name); display_directory(current_dir); } gtk_widget_destroy(dialog); } static void show_new_directory_dialog() { GtkWidget *dialog, *content_area, *entry; dialog = gtk_dialog_new_with_buttons("New Directory", GTK_WINDOW(window), GTK_DIALOG_MODAL, "_OK", GTK_RESPONSE_OK, "_Cancel", GTK_RESPONSE_CANCEL, NULL); content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); entry = gtk_entry_new(); gtk_container_add(GTK_CONTAINER(content_area), entry); gtk_widget_show_all(dialog); gint response = gtk_dialog_run(GTK_DIALOG(dialog)); if (response == GTK_RESPONSE_OK) { const char *dir_name = gtk_entry_get_text(GTK_ENTRY(entry)); create_new_directory(dir_name); display_directory(current_dir); } gtk_widget_destroy(dialog); } static void create_new_directory(const char *dir_name) { if (dir_name == NULL) return; gchar *full_path = g_strdup_printf("%s/%s", current_dir, dir_name); if (mkdir(full_path, 0755) == -1) { g_print("Error creating directory: %s\n", strerror(errno)); } else { display_directory(current_dir); } g_free(full_path); } static int get_directory_depth(const char *dir) { const char *base = base_dir; const char *tmp = dir; while (*base != '\0' && *tmp == *base) { base++; tmp++; } int depth = 0; while (*tmp != '\0') { if (*tmp == '/') { depth++; } tmp++; } return depth; } static gboolean recursive_delete(const char *path) { DIR *d = opendir(path); if (d == NULL) { g_printerr("Failed to open directory for deletion: %s\n", strerror(errno)); return FALSE; } struct dirent *entry; gboolean result = TRUE; while ((entry = readdir(d)) != NULL) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0 || strcmp(entry->d_name, "codeWS") == 0) { continue; } char *full_path = g_strdup_printf("%s/%s", path, entry->d_name); if (entry->d_type == DT_DIR) { if (!recursive_delete(full_path)) { result = FALSE; g_free(full_path); break; } } else { if (remove(full_path) != 0) { g_printerr("Failed to delete file: %s\n", strerror(errno)); result = FALSE; g_free(full_path); break; } } g_free(full_path); } closedir(d); if (result) { if (rmdir(path) != 0) { g_printerr("Failed to delete directory: %s\n", strerror(errno)); result = FALSE; } } return result; } static void show_deletion_dialog() { GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK_CANCEL, "Are you sure you want to delete the selected item?"); gint response = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); if (response == GTK_RESPONSE_OK) { delete_selected_item(); } } static void delete_selected_item() { GtkTreeModel *model; GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); GtkTreeIter iter; gchar *actual_name; if (gtk_tree_selection_get_selected(selection, &model, &iter)) { gtk_tree_model_get(model, &iter, 1, &actual_name, -1); char *full_path = g_strdup_printf("%s/%s", current_dir, actual_name); if (g_file_test(full_path, G_FILE_TEST_IS_DIR)) { if (!recursive_delete(full_path)) { g_printerr("Failed to delete directory: %s\n", strerror(errno)); } } else { if (remove(full_path) != 0) { g_printerr("Failed to delete file: %s\n", strerror(errno)); } } g_free(full_path); g_free(actual_name); display_directory(current_dir); } } int main(int argc, char *argv[]) { gtk_init(&argc, &argv); char *home_dir = getenv("HOME"); if (home_dir == NULL) { fprintf(stderr, "Environment variable HOME is not set.\n"); return EXIT_FAILURE; } base_dir = g_strdup_printf("%s/codeWS", home_dir); current_dir = strdup(base_dir); inotify_fd = inotify_init(); if (inotify_fd < 0) { perror("inotify_init"); return EXIT_FAILURE; } inotify_add_watch(inotify_fd, current_dir, IN_CREATE | IN_DELETE | IN_MODIFY); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); g_signal_connect(window, "destroy", G_CALLBACK(on_window_destroy), NULL); gtk_window_set_title(GTK_WINDOW(window), "Code Workshop"); gtk_window_set_default_size(GTK_WINDOW(window), 1200, 600); GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); gtk_container_add(GTK_CONTAINER(window), hbox); GtkWidget *view = create_tree_view(); gtk_box_pack_start(GTK_BOX(hbox), view, FALSE, FALSE, 5); terminal = VTE_TERMINAL(vte_terminal_new()); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(terminal), TRUE, TRUE, 5); display_directory(current_dir); gtk_widget_show_all(window); gtk_main(); return 0; }