Compare commits
45
Commits
cc6039b974
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5021f61c80
|
||
|
|
0ee52bc2ea
|
||
|
|
d681f11bef
|
||
|
|
274442105e
|
||
|
|
e8d95f14b9
|
||
|
|
3d9b6ccac9
|
||
|
|
247419fc93
|
||
|
|
58d23fbf61
|
||
|
|
c563c71b5f
|
||
|
|
6b20f2f179
|
||
|
|
01b078c8d9
|
||
|
|
769aab6d1f
|
||
|
|
43a1da856f
|
||
|
|
55994e9cb3
|
||
|
|
a1e1a0a0d3
|
||
|
|
fc33d521a2
|
||
|
|
7711749d03
|
||
|
|
5b42ea6a73
|
||
|
|
92115c0dd4
|
||
|
|
62626aaea9
|
||
|
|
a2568443e0 | ||
|
|
a35e0ad748
|
||
|
|
4535f85d21
|
||
|
|
da9990e969
|
||
|
|
21bcf119e3
|
||
|
|
01303ffccc
|
||
|
|
7cc0d865fa
|
||
|
|
23eaae7848
|
||
|
|
daded00171
|
||
|
|
269bd0aa72
|
||
|
|
35c666126e
|
||
|
|
84ab8a3777
|
||
|
|
bee3921c62
|
||
|
|
2bc3ee438e
|
||
|
|
63d56de4c8
|
||
|
|
0744a2cbaa
|
||
|
|
4be4bb926a
|
||
|
|
ae81b806eb
|
||
|
|
1ce2cccf59
|
||
|
|
4aca8fb5a4
|
||
|
|
10442f3101
|
||
|
|
cc179e1b72
|
||
|
|
8c87c46c46
|
||
|
|
ba9631a513
|
||
|
|
cd83bc4b16
|
@@ -0,0 +1,6 @@
|
||||
# [submodule "external/Unity"]
|
||||
# path = external/Unity
|
||||
# url = https://github.com/ThrowTheSwitch/Unity.git
|
||||
[submodule "external/cmocka"]
|
||||
path = external/cmocka
|
||||
url = https://git.cryptomilk.org/projects/cmocka.git
|
||||
@@ -1,5 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.23)
|
||||
|
||||
set(CACHE DEV_WIDGET BOOL OFF "Enable development widget mode")
|
||||
|
||||
project(ZBBSClientTUI
|
||||
VERSION 1.0.0
|
||||
)
|
||||
@@ -21,9 +23,16 @@ add_subdirectory(src/modals)
|
||||
add_subdirectory(src/lib)
|
||||
add_subdirectory(src/utils)
|
||||
add_subdirectory(src/windows)
|
||||
add_subdirectory(src/core)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
# Testing
|
||||
include(CTest)
|
||||
include(cmake/OptionsCMocka.cmake)
|
||||
add_subdirectory(external/cmocka)
|
||||
add_subdirectory(test)
|
||||
|
||||
install(
|
||||
TARGETS zbbs-client-tui
|
||||
)
|
||||
@@ -10,7 +10,9 @@ It will create all necessary file structure in order to run the program correctl
|
||||
2. Build the binary running `$ make` or `$ make bbs-client`
|
||||
3. Run it by `$ make run`
|
||||
|
||||
## Develop
|
||||
## Development
|
||||
### Widget development
|
||||
In order to switch to widget/screen development, configure cmake with `-DDEV_WIDGET=ON`
|
||||
|
||||
### Name conventions:
|
||||
|
||||
|
||||
@@ -1,36 +1,20 @@
|
||||
# TODO:
|
||||
|
||||
## Current:
|
||||
- [ ] File upload multiline description (window)
|
||||
- [ ] Separate common structures in the common.h
|
||||
- [ ] Sorting in the file list
|
||||
- [ ] Cancel upload/download
|
||||
- [x] Input: fix non-Latin letters
|
||||
- [x] Get rid of unsigned integers
|
||||
- [x] Check registration / enter server name again
|
||||
- [x] PageUp/PageDown key functionality in lists
|
||||
- [x] Permission checkboxes for file upload/edit
|
||||
- [x] Upload menu disappears after alert (when opening link to non-existing directory for example).
|
||||
|
||||
## Future:
|
||||
- [ ] File catalogs (or tags)
|
||||
- [ ] Store only unique files (check MD5 sums)
|
||||
|
||||
## Global tasks:
|
||||
- [ ] Testing
|
||||
- [ ] Documentation
|
||||
|
||||
## Bugs:
|
||||
- [x] Sometimes segfaults while navigating through modal menu
|
||||
- [ ] Resume download
|
||||
- [ ] Separate common structures in the common.h
|
||||
|
||||
## Features:
|
||||
- [x] Search files
|
||||
- [x] Correct spacing in the search bar
|
||||
- [x] Finish registration
|
||||
- [x] Privilege settings for files
|
||||
- [ ] Files removal
|
||||
- [ ] Encryption
|
||||
- [x] UTF-8 support
|
||||
- [ ] Check MD5 sum after download/upload
|
||||
- [ ] Hotkeys for buttons
|
||||
- [ ] Manipulation by UI-cursor
|
||||
|
||||
## Fixes:
|
||||
- [x] Check and fix registration and login
|
||||
- [x] CMAKE
|
||||
- [ ] Manipulation by UI-cursor
|
||||
@@ -0,0 +1,7 @@
|
||||
# Disable things we don't need from CMocka
|
||||
set(WITH_EXAMPLES OFF CACHE BOOL "Build examples" FORCE)
|
||||
set(UNIT_TESTING OFF CACHE BOOL "Build CMocka's own tests" FORCE)
|
||||
set(WITH_CMOCKERY_SUPPORT OFF CACHE BOOL "" FORCE)
|
||||
|
||||
# Force static library (easier for most projects)
|
||||
set(WITH_STATIC_LIB ON CACHE BOOL "Build static library" FORCE)
|
||||
+1
Submodule external/cmocka added at 7f736f65dc
+15
-4
@@ -20,11 +20,21 @@ elseif(
|
||||
endif()
|
||||
endif()
|
||||
|
||||
file(GLOB M_SOURCES "*.c")
|
||||
|
||||
target_sources(zbbs-client-tui
|
||||
if(DEV_WIDGET)
|
||||
target_sources(zbbs-client-tui
|
||||
PRIVATE
|
||||
${M_SOURCES}
|
||||
main_dev_widget.c
|
||||
)
|
||||
else()
|
||||
target_sources(zbbs-client-tui
|
||||
PRIVATE
|
||||
main.c
|
||||
)
|
||||
endif()
|
||||
|
||||
add_library(common INTERFACE)
|
||||
target_sources(common
|
||||
PUBLIC FILE_SET HEADERS FILES common.h
|
||||
)
|
||||
|
||||
target_link_libraries(zbbs-client-tui
|
||||
@@ -35,5 +45,6 @@ target_link_libraries(zbbs-client-tui
|
||||
bstring
|
||||
windows
|
||||
utils
|
||||
common
|
||||
${CURSES_LIBRARIES}
|
||||
)
|
||||
+280
-1
@@ -4,8 +4,23 @@
|
||||
#ifndef COMMON_H
|
||||
#define COMMON_H
|
||||
|
||||
#include "dlist.h"
|
||||
#include <d_array.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define LOCAL_PORT 1999
|
||||
#define SERVER_PORT 2000
|
||||
#define LISTEN_QLEN 32
|
||||
#define INBUFSIZE 1024
|
||||
#define DOWNLOADS_DIR "./Downloads"
|
||||
#define DIALOGUE_TITLE 64
|
||||
#define DIALOGUE_TEXT 4096
|
||||
#define INPUT_TEXT DIALOGUE_TITLE
|
||||
#define PACKAGE_SIZE INBUFSIZE * 10
|
||||
|
||||
typedef struct {
|
||||
int32_t cur_y, cur_x;
|
||||
int32_t max_y, max_x;
|
||||
@@ -13,7 +28,7 @@ typedef struct {
|
||||
|
||||
#define MAIN_UI_RESET(app) \
|
||||
app->main_ui.ui = NULL; \
|
||||
app->main_ui.cb_ui_refresh = NULL; \
|
||||
app->main_ui.cb_ui_refresh = NULL; \
|
||||
app->main_ui.cb_b_press = NULL; \
|
||||
app->main_ui.b_keys = NULL; \
|
||||
app->main_ui.b_keys_len = 0;
|
||||
@@ -25,4 +40,268 @@ struct action_key {
|
||||
int (*callback)(void *app, void *d_args);
|
||||
};
|
||||
|
||||
typedef struct fl_item {
|
||||
size_t size;
|
||||
char *name;
|
||||
char *description;
|
||||
char *owner;
|
||||
} fl_item_t;
|
||||
|
||||
enum main_window_type {
|
||||
mw_fl_server, /* file list on the server */
|
||||
mw_fl_local, /* local file list */
|
||||
mw_f_desc, /* file description */
|
||||
};
|
||||
|
||||
typedef void(draw_f_t)(void *fui);
|
||||
|
||||
typedef struct main_window {
|
||||
enum main_window_type type;
|
||||
void *ui; /* structure which describes window UI */
|
||||
int32_t (*cb_b_press)(void *app, void *args); /* callback on button press */
|
||||
void (*cb_ui_refresh)(void *app); /* refresh callback */
|
||||
void (*reset)(void *app); /* function to reset the state of the window */
|
||||
draw_f_t *draw;
|
||||
struct action_key *b_keys;
|
||||
int32_t b_keys_len;
|
||||
} main_window_t;
|
||||
|
||||
typedef struct file_args {
|
||||
int file_d;
|
||||
fl_item_t f_selected;
|
||||
fl_item_t *l_start;
|
||||
fl_item_t *l_current;
|
||||
dlist_t *f_list;
|
||||
} file_args_t;
|
||||
|
||||
typedef struct params {
|
||||
unsigned short port;
|
||||
unsigned addr;
|
||||
char *uname;
|
||||
char *pass;
|
||||
char privileges;
|
||||
int32_t sd;
|
||||
int32_t is_connected : 1;
|
||||
} params_t;
|
||||
|
||||
enum state {
|
||||
/* TUI-based */
|
||||
S_N_D,
|
||||
S_ASK_SEVER_IP,
|
||||
S_ASK_LOGIN_TYPE,
|
||||
S_ASK_LOGIN_USER,
|
||||
S_ASK_REGISTER,
|
||||
S_WAIT_SERVER,
|
||||
S_PREP_SERVER_MESSAGE,
|
||||
S_PRINT_SERVER_MESSAGE,
|
||||
S_FILE_LIST,
|
||||
S_WAIT_PASS,
|
||||
/* S_FILE_SELECT, */
|
||||
S_DOWNLOAD_FILE,
|
||||
S_UPLOAD_PARAMS,
|
||||
S_UPLOAD_FILE,
|
||||
/* S_UPLOAD_FILE_SELECT, */
|
||||
S_UPLOAD_REQUESTED,
|
||||
S_UPLOAD_SERVER_FINISHES,
|
||||
S_ASK_USER_BEFORE_LOGIN,
|
||||
S_ERR,
|
||||
S_NEXT_ACTION,
|
||||
S_WAIT_REGISTER_CONFIRMATION,
|
||||
S_WAIT_USER_DESCRIPTION,
|
||||
/* Legacy */
|
||||
WAIT_SERVER_INIT,
|
||||
WAIT_SERVER,
|
||||
WAIT_REGISTER,
|
||||
WAIT_REGISTER_CONFIRMATION,
|
||||
WAIT_CLIENT,
|
||||
};
|
||||
|
||||
enum package_signal { sig_continue, sig_cancel, sig_finish, sig_pause };
|
||||
|
||||
typedef struct {
|
||||
enum package_signal signal;
|
||||
size_t package_size;
|
||||
} s_file_pd_t; /* file package descriptor */
|
||||
|
||||
typedef struct p_file {
|
||||
char *name;
|
||||
char *path;
|
||||
char *description;
|
||||
size_t size;
|
||||
size_t rest;
|
||||
size_t package_rest;
|
||||
int32_t it_count;
|
||||
size_t it_interval;
|
||||
enum package_signal signal;
|
||||
int fd;
|
||||
} p_file_t;
|
||||
|
||||
typedef struct query_args {
|
||||
int32_t sd;
|
||||
int32_t buf_used;
|
||||
p_file_t *file;
|
||||
enum state state;
|
||||
char buf[INBUFSIZE];
|
||||
bool from_server;
|
||||
params_t *params;
|
||||
struct {
|
||||
char *text;
|
||||
int32_t capacity;
|
||||
int32_t size;
|
||||
} server_message;
|
||||
main_window_t *main_ui;
|
||||
void *progress_bar;
|
||||
void *active_dialogue;
|
||||
void *notification;
|
||||
} query_args_t;
|
||||
|
||||
enum w_dialogue_color_scheme { dc_normal, dc_alert };
|
||||
|
||||
enum w_g_type { g_content, g_action };
|
||||
|
||||
enum w_g_dir {
|
||||
horizontal,
|
||||
vertical,
|
||||
};
|
||||
|
||||
enum w_type {
|
||||
w_end,
|
||||
w_button,
|
||||
w_box,
|
||||
w_group,
|
||||
w_input,
|
||||
w_checkbox,
|
||||
w_progress,
|
||||
};
|
||||
|
||||
enum w_val_type {
|
||||
val_num,
|
||||
val_ptr,
|
||||
val_nul,
|
||||
};
|
||||
|
||||
struct w_val_t {
|
||||
enum w_val_type type;
|
||||
union {
|
||||
void *ptr;
|
||||
int32_t num;
|
||||
} val;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int32_t id;
|
||||
int32_t idx; /* element index in the group */
|
||||
void *element;
|
||||
enum w_type type;
|
||||
enum w_g_type g_type;
|
||||
bool is_default;
|
||||
struct w_val_t val;
|
||||
} w_g_el_t;
|
||||
|
||||
/* widget callback response code */
|
||||
enum w_cbrp_code {
|
||||
cbrc_none, /* default */
|
||||
cbrp_val,
|
||||
cbrp_g_el, /* element of type group_el_t */
|
||||
cbrp_err
|
||||
};
|
||||
|
||||
enum w_pmt_attrs {
|
||||
PMT_POS_CENTER = 01,
|
||||
PMT_ALIGN_CENTER = 03,
|
||||
};
|
||||
|
||||
/* widget callback response data */
|
||||
typedef struct {
|
||||
enum w_cbrp_code code;
|
||||
struct w_val_t val;
|
||||
} w_cbrp_data;
|
||||
|
||||
typedef struct {
|
||||
void *app;
|
||||
void *element;
|
||||
void *data;
|
||||
w_cbrp_data resp_data;
|
||||
void *active_el;
|
||||
} w_cb_args_t;
|
||||
|
||||
typedef int32_t (*w_cb_press_t)(void *app, void *data);
|
||||
|
||||
typedef void (*w_cb_t)(w_cb_args_t *args);
|
||||
|
||||
typedef struct w_t {
|
||||
int32_t id;
|
||||
char title[DIALOGUE_TITLE];
|
||||
struct {
|
||||
int32_t y;
|
||||
int32_t x;
|
||||
} cur; /* cursor position */
|
||||
struct {
|
||||
int32_t y;
|
||||
int32_t x;
|
||||
} sz; /* size */
|
||||
struct {
|
||||
int32_t y;
|
||||
int32_t x;
|
||||
} m; /* margin (relative to the parent) */
|
||||
struct {
|
||||
int32_t y;
|
||||
int32_t x;
|
||||
} ps; /* position (relative to the window) */
|
||||
WINDOW *const *parent_win;
|
||||
struct w_t *w_parent;
|
||||
w_cb_t callback;
|
||||
} w_t;
|
||||
|
||||
typedef struct group_el_init_t {
|
||||
enum w_type type;
|
||||
bool is_default;
|
||||
bool is_hidden_value;
|
||||
char label[DIALOGUE_TITLE];
|
||||
int32_t length;
|
||||
enum w_g_dir direction;
|
||||
union {
|
||||
int32_t num;
|
||||
} val;
|
||||
struct group_el_init_t *children;
|
||||
} w_group_el_init_t;
|
||||
|
||||
typedef struct group_t {
|
||||
w_t w;
|
||||
w_g_el_t *elements;
|
||||
enum w_g_dir direction;
|
||||
int32_t count;
|
||||
int32_t first_id, last_id;
|
||||
struct group_t *parent_group;
|
||||
} w_group_t;
|
||||
|
||||
typedef struct {
|
||||
w_t w;
|
||||
WINDOW *win;
|
||||
w_group_t *g_content;
|
||||
w_group_t *g_action;
|
||||
w_g_el_t *active_el;
|
||||
char text[DIALOGUE_TEXT];
|
||||
bool is_initiated;
|
||||
bool needs_update;
|
||||
bool needs_destroy;
|
||||
coordinates_t *p_coordinates;
|
||||
enum w_dialogue_color_scheme color_scheme;
|
||||
d_array_ptr_t id_map;
|
||||
} w_dialogue_t;
|
||||
|
||||
typedef struct app_t {
|
||||
WINDOW *win;
|
||||
w_dialogue_t modal;
|
||||
params_t *params;
|
||||
coordinates_t coordinates;
|
||||
query_args_t *query_args;
|
||||
file_args_t *file_args;
|
||||
void (*active_callback)(w_cb_args_t *args);
|
||||
void (*callback_after_notification)(struct app_t *);
|
||||
void *active_widget;
|
||||
wchar_t *top_text;
|
||||
main_window_t main_ui;
|
||||
} app_t;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
add_library(core STATIC)
|
||||
|
||||
file(GLOB M_SOURCES "*.c")
|
||||
|
||||
target_sources(core
|
||||
PRIVATE
|
||||
${M_SOURCES}
|
||||
PUBLIC
|
||||
FILE_SET HEADERS
|
||||
FILES
|
||||
core.h
|
||||
)
|
||||
target_link_libraries(core
|
||||
PRIVATE
|
||||
common
|
||||
widget_core
|
||||
widget
|
||||
bstring
|
||||
utils
|
||||
modals
|
||||
windows
|
||||
)
|
||||
|
||||
+26
-22
@@ -1,7 +1,7 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include "../app.h"
|
||||
#include "app.h"
|
||||
#include <locale.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <netinet/in.h>
|
||||
@@ -11,44 +11,46 @@
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <wchar.h>
|
||||
|
||||
void ac_file(WINDOW *win, int is_action_w);
|
||||
|
||||
app_t *app_init(void) {
|
||||
app_t *app_init(app_t *app) {
|
||||
app->top_text = NULL;
|
||||
/* get values from terminal size */
|
||||
int32_t y_max, x_max;
|
||||
getmaxyx(stdscr, y_max, x_max);
|
||||
|
||||
/* allocate app struct memory */
|
||||
app_t *_app = calloc(1, sizeof(app_t));
|
||||
if (app == NULL) {
|
||||
app = calloc(1, sizeof(app_t));
|
||||
}
|
||||
|
||||
/* associate values for screen size */
|
||||
_app->coordinates.max_x = _app->coordinates.cur_x = x_max;
|
||||
_app->coordinates.max_y = _app->coordinates.cur_y = y_max;
|
||||
app->coordinates.max_x = app->coordinates.cur_x = x_max;
|
||||
app->coordinates.max_y = app->coordinates.cur_y = y_max;
|
||||
|
||||
_app->win = newwin(_app->coordinates.cur_y, _app->coordinates.cur_x, 0, 0);
|
||||
app->win = newwin(app->coordinates.cur_y, app->coordinates.cur_x, 0, 0);
|
||||
|
||||
/* dialogue */
|
||||
_app->modal.win = NULL;
|
||||
_app->modal.is_initiated = 0;
|
||||
|
||||
/* print top and bottom bars */
|
||||
_app->main_ui.b_keys_len = 0;
|
||||
app_draw_bars(_app);
|
||||
|
||||
/* here goes box borders */
|
||||
app_draw_borders(_app);
|
||||
app->modal.win = NULL;
|
||||
app->modal.is_initiated = 0;
|
||||
|
||||
/* NULL to main_ui */
|
||||
MAIN_UI_RESET(_app);
|
||||
MAIN_UI_RESET(app);
|
||||
|
||||
keypad(_app->win, TRUE);
|
||||
keypad(app->win, TRUE);
|
||||
|
||||
app->callback_after_notification = NULL;
|
||||
|
||||
app->modal.is_initiated = false;
|
||||
app->modal.needs_destroy = false;
|
||||
|
||||
/* refresh the windows */
|
||||
wnoutrefresh(_app->win);
|
||||
wnoutrefresh(app->win);
|
||||
doupdate();
|
||||
|
||||
return _app;
|
||||
return app;
|
||||
}
|
||||
|
||||
void app_init_nc(void) {
|
||||
@@ -106,15 +108,17 @@ void app_draw_bars(app_t *app) {
|
||||
mvwprintw(app->win, app->coordinates.cur_y - 2, i, " ");
|
||||
}
|
||||
|
||||
/* add content to the top bar */
|
||||
if (app->params && app->params->is_connected) {
|
||||
if (app->top_text != NULL) {
|
||||
mvwaddnwstr(app->win, 1, 2, app->top_text, wcslen(app->top_text));
|
||||
} else if (app->params && app->params->is_connected) {
|
||||
int32_t ip_address = app->params->addr;
|
||||
u_char ip_addr[4];
|
||||
mempcpy(ip_addr, &ip_address, 4);
|
||||
sprintf(top_text, "Connected to %u.%u.%u.%u, user: %s", ip_addr[0],
|
||||
ip_addr[1], ip_addr[2], ip_addr[3], app->params->uname);
|
||||
mvwprintw(app->win, 1, 2, "%s", top_text);
|
||||
}
|
||||
mvwprintw(app->win, 1, 2, "%s", top_text);
|
||||
|
||||
wattroff(app->win, A_REVERSE);
|
||||
|
||||
/* add content to the bottom bar */
|
||||
@@ -4,9 +4,7 @@
|
||||
#ifndef APP_H
|
||||
#define APP_H
|
||||
|
||||
#include "../main.h"
|
||||
#include "../common.h"
|
||||
#include "dialogue.h"
|
||||
#include <common.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <widget_core.h>
|
||||
#include <bstrlib.h>
|
||||
@@ -15,19 +13,7 @@ typedef enum {
|
||||
modal_color_pair = 3,
|
||||
} app_color;
|
||||
|
||||
typedef struct {
|
||||
WINDOW *win;
|
||||
w_dialogue_t modal;
|
||||
params_t *params;
|
||||
coordinates_t coordinates;
|
||||
query_args_t *query_args;
|
||||
file_args_t *file_args;
|
||||
void (*active_callback)(w_cb_args_t *args);
|
||||
void *active_widget;
|
||||
main_window_t main_ui;
|
||||
} app_t;
|
||||
|
||||
app_t *app_init(void);
|
||||
app_t *app_init(app_t *app);
|
||||
void app_init_nc(void);
|
||||
void app_destroy(app_t *app, int32_t exit_code);
|
||||
void app_draw_borders(app_t *app);
|
||||
@@ -1,8 +1,8 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include "alert.h"
|
||||
#include "main.h"
|
||||
#include "app.h"
|
||||
#include <widget.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
@@ -31,7 +31,7 @@ bool connect_to_server(app_t *app) {
|
||||
return false;
|
||||
}
|
||||
app->query_args->state = S_WAIT_SERVER;
|
||||
app->params->is_connected = TRUE;
|
||||
app->params->is_connected = true;
|
||||
app->query_args->sd = app->params->sd;
|
||||
app_draw_bars(app);
|
||||
return true;
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#ifndef CONNECTION_H
|
||||
#define CONNECTION_H
|
||||
#include "main.h"
|
||||
|
||||
#include <widget.h>
|
||||
|
||||
bool connect_to_server(app_t *params);
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef CORE_H
|
||||
#define CORE_H
|
||||
|
||||
#include "app.h"
|
||||
#include "connection.h"
|
||||
#include "draw_modal.h"
|
||||
#include "file_processor.h"
|
||||
#include "fs.h"
|
||||
#include "query.h"
|
||||
#include "server.h"
|
||||
|
||||
#endif
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include <modals.h>
|
||||
#include <widget.h>
|
||||
#include "main.h"
|
||||
|
||||
static void (*active_callback_backup)(w_cb_args_t *) = NULL;
|
||||
|
||||
@@ -28,7 +27,7 @@ void app_draw_modal(app_t *app) {
|
||||
case S_PRINT_SERVER_MESSAGE:
|
||||
m_server_message_init(app);
|
||||
break;
|
||||
case S_FILE_DOWNLOAD:
|
||||
case S_DOWNLOAD_FILE:
|
||||
m_load_pr_init(app, false);
|
||||
break;
|
||||
case S_UPLOAD_FILE:
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#ifndef DRAW_MODAL_H
|
||||
#define DRAW_MODAL_H
|
||||
|
||||
#include <widget.h>
|
||||
|
||||
void app_draw_modal(app_t *app);
|
||||
#endif
|
||||
@@ -0,0 +1,411 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include "alert.h"
|
||||
#include "dlist.h"
|
||||
#include "server.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <common.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <widget.h>
|
||||
#include <windows/file_list.h>
|
||||
|
||||
#include "file_processor.h"
|
||||
#include "query.h"
|
||||
|
||||
static void fl_add(dlist_t *fl_list, char *q_line);
|
||||
static bool fl_rm_cb(void *_ptr);
|
||||
|
||||
|
||||
void fl_clear(dlist_t *dlist) { dlist_clear_list(dlist, fl_rm_cb); }
|
||||
|
||||
void file_list(file_args_t *f_args, query_args_t *q_args) {
|
||||
int32_t qlen;
|
||||
char *query = NULL;
|
||||
static char q_buf[INBUFSIZE * 2];
|
||||
static int32_t qbuf_used = 0;
|
||||
w_ui_file_list_t *fui = (w_ui_file_list_t *)q_args->main_ui->ui;
|
||||
w_dialogue_t *d = (w_dialogue_t *)q_args->active_dialogue;
|
||||
|
||||
if (qbuf_used == 0)
|
||||
q_buf[0] = 0;
|
||||
|
||||
while ((qlen = query_extract_from_buf(q_args->buf, &(q_args->buf_used),
|
||||
&query))) {
|
||||
if (!strncmp(":END:", query, sizeof(":END:") - 1)) {
|
||||
sscanf(query, ":END: PAGE %d/%d COUNT: %d/%d\n", &fui->current_page,
|
||||
&fui->pages, &fui->current_count, &fui->full_count);
|
||||
if (d != NULL && d->is_initiated) {
|
||||
d->needs_update = true;
|
||||
}
|
||||
w_fl_draw(fui);
|
||||
free(query);
|
||||
q_args->state = WAIT_CLIENT;
|
||||
break;
|
||||
}
|
||||
strcat(q_buf, query);
|
||||
qbuf_used += strlen(q_buf);
|
||||
if (strchr(q_buf, '\n') == NULL)
|
||||
continue;
|
||||
fl_add(f_args->f_list, q_buf);
|
||||
fui->current_count++;
|
||||
free(query);
|
||||
query = NULL;
|
||||
qbuf_used = 0;
|
||||
q_buf[0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static bool fl_add_cb(void *_dst, void *_src);
|
||||
|
||||
int32_t ui_file_select(file_args_t *f_args, query_args_t *q_args,
|
||||
fl_item_t *l_selected) {
|
||||
fl_item_t *f_selected =
|
||||
&(f_args->f_selected); /* new copy of file struct (list be cleared) */
|
||||
struct stat st = {0};
|
||||
|
||||
if (l_selected == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(f_selected, l_selected, sizeof(fl_item_t));
|
||||
|
||||
fl_add_cb(f_selected, l_selected);
|
||||
|
||||
if (stat(DOWNLOADS_DIR, &st) == -1) {
|
||||
mkdir(DOWNLOADS_DIR, 0700);
|
||||
}
|
||||
char *file_path = malloc(sizeof(DOWNLOADS_DIR) + strlen(l_selected->name) +
|
||||
2); // + '/' + '\0'
|
||||
f_selected->name = NULL;
|
||||
sprintf(file_path, "%s/%s", DOWNLOADS_DIR, l_selected->name);
|
||||
f_selected->name = strdup(l_selected->name);
|
||||
fl_clear(f_args->f_list);
|
||||
f_args->file_d = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
||||
if (f_args->file_d == -1) {
|
||||
server_send_string(q_args, "error: %s\n", f_selected->name);
|
||||
perror(f_selected->name);
|
||||
q_args->state = WAIT_CLIENT;
|
||||
free(file_path);
|
||||
return -2;
|
||||
}
|
||||
server_send_string(q_args, "file download [%s]\n", f_selected->name);
|
||||
q_args->state = S_DOWNLOAD_FILE;
|
||||
|
||||
q_args->file = malloc(sizeof(p_file_t));
|
||||
q_args->file->name = f_selected->name;
|
||||
q_args->file->rest = f_selected->size;
|
||||
q_args->file->size = f_selected->size;
|
||||
q_args->file->package_rest = PACKAGE_SIZE;
|
||||
q_args->file->signal = sig_continue;
|
||||
q_args->file->it_count = 0;
|
||||
q_args->file->fd = f_args->file_d;
|
||||
|
||||
q_args->file->it_interval =
|
||||
(f_selected->size / INBUFSIZE / 100); /* every 1% */
|
||||
if (q_args->file->it_interval == 0)
|
||||
q_args->file->it_interval = 1;
|
||||
|
||||
free(file_path);
|
||||
return OK;
|
||||
}
|
||||
|
||||
#define file_download_receive_after(qlen, q_args, f_args, d, pb) \
|
||||
{ \
|
||||
if (qlen) { \
|
||||
q_args->file->it_count++; \
|
||||
q_args->file->package_rest -= qlen; \
|
||||
if (!(q_args->file->it_count % q_args->file->it_interval)) { \
|
||||
pb->percentage = progress; \
|
||||
d->needs_update = true; \
|
||||
} else { \
|
||||
q_args->buf_used = 0; \
|
||||
} \
|
||||
if (q_args->file->rest < qlen) { \
|
||||
q_args->file->rest = 0; \
|
||||
} else { \
|
||||
q_args->file->rest -= qlen; \
|
||||
} \
|
||||
if (q_args->file->rest == 0) { \
|
||||
q_args->file->it_count = 0; \
|
||||
q_args->file->it_interval = 0; \
|
||||
f_selected->size = 0; \
|
||||
close(f_args->file_d); \
|
||||
d->needs_destroy = true; \
|
||||
sprintf(answer, "File %s is downloaded from the server!", \
|
||||
f_selected->name); \
|
||||
q_args->notification = malloc(strlen(answer) + 1); \
|
||||
strcpy(q_args->notification, answer); \
|
||||
free(f_selected->name); \
|
||||
server_send_string(q_args, "file list %d %d\n%n", fui->max_lines, \
|
||||
fui->current_page, &a_len); \
|
||||
q_args->state = S_FILE_LIST; \
|
||||
return; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
/* TODO: revise */
|
||||
void file_download(file_args_t *f_args, query_args_t *q_args) {
|
||||
fl_item_t *f_selected = &(f_args->f_selected);
|
||||
char answer[256];
|
||||
|
||||
int32_t progress =
|
||||
(f_selected->size - q_args->file->rest) * 100 / f_selected->size;
|
||||
w_pgb_ui_t *pb = (w_pgb_ui_t *)q_args->progress_bar;
|
||||
w_dialogue_t *d = (w_dialogue_t *)q_args->active_dialogue;
|
||||
w_ui_file_list_t *fui = (w_ui_file_list_t *)q_args->main_ui->ui;
|
||||
int32_t a_len = 0;
|
||||
int32_t qlen;
|
||||
|
||||
int write_len = q_args->buf_used;
|
||||
|
||||
if (q_args->file->package_rest <= q_args->buf_used) {
|
||||
write_len = q_args->file->package_rest;
|
||||
qlen = write(f_args->file_d, q_args->buf, write_len);
|
||||
file_download_receive_after(qlen, q_args, f_args, d,
|
||||
pb) if (q_args->file->signal == sig_continue) {
|
||||
server_send_string(q_args, "continue %d\n", PACKAGE_SIZE * 10);
|
||||
if (q_args->file->package_rest == 0) {
|
||||
q_args->file->package_rest = PACKAGE_SIZE * 10;
|
||||
return;
|
||||
}
|
||||
int buf_offset = q_args->file->package_rest;
|
||||
write_len = q_args->buf_used - q_args->file->package_rest;
|
||||
qlen = write(f_args->file_d, q_args->buf + buf_offset, write_len);
|
||||
q_args->file->package_rest = PACKAGE_SIZE * 10;
|
||||
file_download_receive_after(qlen, q_args, f_args, d, pb);
|
||||
} else if (q_args->file->signal == sig_cancel) {
|
||||
server_send_string(q_args, "cancel\n");
|
||||
FILE_CLEAN(q_args->file);
|
||||
d->needs_destroy = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
qlen = write(f_args->file_d, q_args->buf, write_len);
|
||||
file_download_receive_after(qlen, q_args, f_args, d, pb)
|
||||
}
|
||||
|
||||
int32_t file_upload_open(char *dpath, char *fname, query_args_t *q_args) {
|
||||
q_args->file = malloc(sizeof(p_file_t));
|
||||
q_args->file->path = malloc(strlen(dpath) + strlen(fname) + 2);
|
||||
q_args->file->signal = sig_continue;
|
||||
sprintf(q_args->file->path, "%s/%s", dpath, fname);
|
||||
int fd = open(q_args->file->path, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
perror(q_args->file->path);
|
||||
return -1;
|
||||
}
|
||||
size_t fsize = lseek(fd, 0, SEEK_END);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
q_args->file->fd = fd;
|
||||
q_args->file->name = malloc(strlen(fname) + 1);
|
||||
strcpy(q_args->file->name, fname);
|
||||
q_args->file->size = fsize;
|
||||
q_args->file->rest = fsize;
|
||||
q_args->file->description = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t file_upload_start(query_args_t *q_args) {
|
||||
char *query;
|
||||
query_extract_from_buf(q_args->buf, &(q_args->buf_used), &query);
|
||||
if (strcmp(query, "accept")) {
|
||||
clear_file_in_query(q_args);
|
||||
w_notification("Server response", dc_alert, query);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define file_upload_send(send_len, package_rest, buf_indent) \
|
||||
{ \
|
||||
if (q_args->buf_used > 0) { \
|
||||
int wlen = write(q_args->sd, q_args->buf + buf_indent, send_len); \
|
||||
if (q_args->buf_used != wlen) { \
|
||||
/* TODO: ошибка */ \
|
||||
/* reset function before exit */ \
|
||||
it_count = 0; \
|
||||
it_interval = 0; \
|
||||
package_rest = 0; \
|
||||
ret = -1; \
|
||||
} else { \
|
||||
it_count++; \
|
||||
if (!(it_count % it_interval)) { \
|
||||
pb->percentage = progress; \
|
||||
d->needs_update = true; \
|
||||
} \
|
||||
q_args->file->rest -= wlen; \
|
||||
package_rest -= wlen; \
|
||||
q_args->buf_used -= send_len; \
|
||||
ret = 0; \
|
||||
} \
|
||||
} else { \
|
||||
/* reset function before exit */ \
|
||||
it_count = 0; \
|
||||
it_interval = 0; \
|
||||
package_rest = 0; \
|
||||
ret = 1; \
|
||||
} \
|
||||
}
|
||||
|
||||
int32_t file_upload(query_args_t *q_args) {
|
||||
static int32_t it_count = 0;
|
||||
static size_t it_interval = 0;
|
||||
static int package_rest = 0;
|
||||
int ret = 0;
|
||||
int buf_indent = 0;
|
||||
int send_len = q_args->buf_used;
|
||||
|
||||
int32_t progress =
|
||||
(q_args->file->size - q_args->file->rest) * 100 / q_args->file->size;
|
||||
w_pgb_ui_t *pb = (w_pgb_ui_t *)q_args->progress_bar;
|
||||
w_dialogue_t *d = (w_dialogue_t *)q_args->active_dialogue;
|
||||
if (it_interval == 0) {
|
||||
it_interval = (q_args->file->size / INBUFSIZE / 100) * 5; /* every 1% */
|
||||
if (it_interval == 0)
|
||||
it_interval = 1;
|
||||
}
|
||||
|
||||
if (package_rest < send_len) {
|
||||
if (package_rest > 0) {
|
||||
send_len -= package_rest;
|
||||
buf_indent = package_rest;
|
||||
file_upload_send(package_rest, package_rest, 0);
|
||||
if (ret) {
|
||||
/* TODO: error case */
|
||||
}
|
||||
}
|
||||
|
||||
package_rest = PACKAGE_SIZE;
|
||||
|
||||
s_file_pd_t fpd = {.signal = q_args->file->signal,
|
||||
.package_size = package_rest};
|
||||
write(q_args->sd, &fpd, sizeof(s_file_pd_t));
|
||||
if (q_args->file->signal == sig_cancel) {
|
||||
/* reset function before exit */
|
||||
it_count = 0;
|
||||
it_interval = 0;
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
file_upload_send(send_len, package_rest, buf_indent);
|
||||
|
||||
q_args->buf[0] = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void clear_file_in_query(query_args_t *q_args) {
|
||||
free(q_args->file->name);
|
||||
free(q_args->file->path);
|
||||
if (q_args->file->description != NULL) {
|
||||
free(q_args->file->description);
|
||||
}
|
||||
free(q_args->file);
|
||||
q_args->file = NULL;
|
||||
}
|
||||
|
||||
void init_file_args(file_args_t *f_args, app_t *app) {
|
||||
w_ui_file_list_t *fui = app->main_ui.ui;
|
||||
f_args->file_d = 0;
|
||||
f_args->f_selected.name = NULL;
|
||||
f_args->f_selected.size = 0;
|
||||
f_args->l_current = NULL;
|
||||
f_args->l_start = NULL;
|
||||
if (app->main_ui.type == mw_fl_server) {
|
||||
fui = app->main_ui.ui;
|
||||
if (fui != NULL) {
|
||||
f_args->f_list = fui->f_list;
|
||||
}
|
||||
}
|
||||
f_args->f_list = fui->f_list;
|
||||
app->file_args = f_args;
|
||||
}
|
||||
|
||||
static void fl_q_extract(fl_item_t *f, char *line) {
|
||||
char fowner[32];
|
||||
char *descr_begin = NULL;
|
||||
int h_len;
|
||||
char *name_begin = strchr(line, '[') + 1;
|
||||
char *name_end = strchr(line, ']');
|
||||
int name_len = name_end - name_begin;
|
||||
sscanf(name_end, "] %zu %s%n", &(f->size), fowner, &h_len);
|
||||
strncpy(f->name, name_begin, name_len);
|
||||
f->name[name_len] = 0;
|
||||
strcpy(f->owner, fowner);
|
||||
descr_begin = name_end + h_len + 1;
|
||||
if (strcmp(descr_begin, "\n")) {
|
||||
int d_len = strlen(line) - h_len;
|
||||
strcpy(f->description, descr_begin); // +1 to escape the dividing \32
|
||||
for (int i = 0; i < d_len; i++) {
|
||||
if (f->description[i] == '\a') {
|
||||
f->description[i] = '\n';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
f->description = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool fl_add_cb(void *_dst, void *_src) {
|
||||
fl_item_t *dst = _dst;
|
||||
fl_item_t *src = _src;
|
||||
|
||||
dst->size = src->size;
|
||||
|
||||
if (src->description != NULL) {
|
||||
dst->description = malloc(sizeof(char) * (strlen(src->description) + 1));
|
||||
strcpy(dst->description, src->description);
|
||||
} else {
|
||||
dst->description = NULL;
|
||||
}
|
||||
|
||||
dst->name = malloc(sizeof(char) * (strlen(src->name) + 1));
|
||||
strcpy(dst->name, src->name);
|
||||
|
||||
dst->owner = malloc(sizeof(char) * (strlen(src->owner) + 1));
|
||||
strcpy(dst->owner, src->owner);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fl_rm_cb(void *_ptr) {
|
||||
fl_item_t *ptr = _ptr;
|
||||
if (ptr->description != NULL)
|
||||
free(ptr->description);
|
||||
if (ptr->name != NULL)
|
||||
free(ptr->name);
|
||||
if (ptr->owner != NULL)
|
||||
free(ptr->owner);
|
||||
free(ptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* work with file list */
|
||||
static void fl_add(dlist_t *fl_list, char *q_line) {
|
||||
fl_item_t f_item;
|
||||
char f_description[INBUFSIZE * 100];
|
||||
char f_owner[256];
|
||||
char f_name[256];
|
||||
f_item.description = f_description;
|
||||
f_item.owner = f_owner;
|
||||
f_item.name = f_name;
|
||||
f_item.size = 0;
|
||||
|
||||
fl_q_extract(&f_item, q_line);
|
||||
|
||||
dlist_add(fl_list, &f_item, fl_item_t, fl_add_cb, false);
|
||||
}
|
||||
@@ -4,16 +4,17 @@
|
||||
#ifndef FILE_PROCESSOR_H
|
||||
#define FILE_PROCESSOR_H
|
||||
|
||||
#include "main.h"
|
||||
#include "common.h"
|
||||
|
||||
void file_list(file_args_t *f_args, query_args_t *q_args);
|
||||
void file_download(file_args_t *f_args, query_args_t *q_args);
|
||||
void init_file_args(file_args_t *fargs);
|
||||
void init_file_args(file_args_t *fargs, app_t *app);
|
||||
int file_upload_request(char *, query_args_t *q_args);
|
||||
int file_upload_start(query_args_t *q_args);
|
||||
int file_upload(query_args_t *q_args);
|
||||
int32_t file_upload_open(char *dpath, char *fname, query_args_t *q_args);
|
||||
void fl_clear(fl_item_t **start, fl_item_t **arg_current);
|
||||
int32_t ui_file_select(file_args_t *f_args, query_args_t *q_args, int32_t idx);
|
||||
void fl_clear(dlist_t *dlist);
|
||||
int32_t ui_file_select(file_args_t *f_args, query_args_t *q_args, fl_item_t *ptr);
|
||||
void clear_file_in_query(query_args_t *q_args);
|
||||
|
||||
#endif
|
||||
@@ -1,7 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include "query.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
@@ -23,13 +22,13 @@
|
||||
#include <modals.h>
|
||||
#include <widget.h>
|
||||
#include <widget_core.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "query.h"
|
||||
#include "alert.h"
|
||||
#include "connection.h"
|
||||
#include "dialogue.h"
|
||||
#include "file_processor.h"
|
||||
#include "main.h"
|
||||
#include "main_window.h"
|
||||
#include "server.h"
|
||||
#include "types.h"
|
||||
|
||||
@@ -45,26 +44,16 @@ int32_t process_user_input(app_t *app, w_cb_args_t *d_args);
|
||||
app_destroy(app, exit_code); \
|
||||
}
|
||||
|
||||
#define FILE_CLEAN(app) \
|
||||
if (app->query_args->file != NULL) { \
|
||||
FREE_MLC(app->query_args->file->name); \
|
||||
FREE_MLC(app->query_args->file->path); \
|
||||
FREE_MLC(app->query_args->file); \
|
||||
}
|
||||
|
||||
void query_loop(app_t *app) {
|
||||
query_args_t *query_args = app->query_args;
|
||||
fd_set readfds;
|
||||
int32_t sd = app->params->sd;
|
||||
size_t qlen;
|
||||
int sr;
|
||||
static file_args_t file_args;
|
||||
w_cb_args_t d_args = {
|
||||
.app = app, .element = NULL, .data = NULL, .resp_data.code = cbrc_none};
|
||||
|
||||
init_file_args(&file_args);
|
||||
query_args->sd = sd;
|
||||
app->file_args = &file_args;
|
||||
|
||||
for (;;) {
|
||||
/* update screen */
|
||||
@@ -84,12 +73,18 @@ void query_loop(app_t *app) {
|
||||
}
|
||||
}
|
||||
if (query_args->state == S_NEXT_ACTION) {
|
||||
query_args->from_server = TRUE;
|
||||
if (ERR == process_query(app)) {
|
||||
EXIT_APP("*** error while processing query ***", app, 1)
|
||||
if (app->callback_after_notification != NULL) {
|
||||
app->callback_after_notification(app);
|
||||
app->callback_after_notification = NULL;
|
||||
continue;
|
||||
} else {
|
||||
query_args->from_server = TRUE;
|
||||
if (ERR == process_query(app)) {
|
||||
EXIT_APP("*** error while processing query ***", app, 1)
|
||||
}
|
||||
query_args->state = S_WAIT_SERVER;
|
||||
continue;
|
||||
}
|
||||
query_args->state = S_WAIT_SERVER;
|
||||
continue;
|
||||
} else {
|
||||
sr = select(maxfd + 1, &readfds, NULL, NULL, NULL);
|
||||
if (sr == -1) {
|
||||
@@ -150,9 +145,19 @@ int upload_confirm_cb(app_t *app, char *query, int q_len) {
|
||||
w_notification("File upload", dc_normal,
|
||||
"File %s is uploaded to the server!",
|
||||
app->query_args->file->name);
|
||||
clear_file_in_query(app->query_args);
|
||||
app->query_args->state = S_FILE_LIST;
|
||||
app->modal.needs_destroy = true;
|
||||
int desc_answer =
|
||||
w_bool_ask("Write file description", "Yes", "No", true,
|
||||
"Would you like to write description for the file %s?",
|
||||
app->query_args->file->name);
|
||||
if (desc_answer) {
|
||||
main_window_set(app, mw_f_desc);
|
||||
app->query_args->state = S_WAIT_USER_DESCRIPTION;
|
||||
} else {
|
||||
server_send_string(app->query_args, "[Empty description]\n:END:\n");
|
||||
app->query_args->state = WAIT_CLIENT;
|
||||
main_window_set(app, mw_fl_server);
|
||||
}
|
||||
/* clear_file_in_query(app->query_args); */
|
||||
return 0;
|
||||
}
|
||||
return 1; /* TODO: Error case */
|
||||
@@ -183,14 +188,14 @@ int process_query(app_t *app) {
|
||||
case S_FILE_LIST:
|
||||
file_list(file_args, query_args);
|
||||
break;
|
||||
case S_FILE_DOWNLOAD:
|
||||
case S_DOWNLOAD_FILE:
|
||||
file_download(file_args, query_args);
|
||||
break;
|
||||
case S_UPLOAD_REQUESTED:
|
||||
if (!file_upload_start(query_args)) {
|
||||
query_args->state = S_UPLOAD_FILE;
|
||||
} else {
|
||||
FILE_CLEAN(app);
|
||||
FILE_CLEAN(app->query_args->file);
|
||||
query_args->state = WAIT_CLIENT;
|
||||
}
|
||||
break;
|
||||
@@ -205,7 +210,7 @@ int process_query(app_t *app) {
|
||||
/* cancel case */
|
||||
w_notification("Alert", dc_alert, "File %s didn't upload!",
|
||||
query_args->file->name);
|
||||
FILE_CLEAN(app);
|
||||
FILE_CLEAN(app->query_args->file);
|
||||
w_dialogue_t *d = &(app->modal);
|
||||
d->needs_destroy = true;
|
||||
query_args->state = WAIT_CLIENT;
|
||||
@@ -284,7 +289,7 @@ static int process_command(app_t *app, wait_server_cb *callback) {
|
||||
while ((qlen = query_extract_from_buf(q_args->buf, &(q_args->buf_used),
|
||||
&query))) {
|
||||
if (callback == NULL) {
|
||||
process_server_command(query, qlen, app);
|
||||
res = process_server_command(query, qlen, app);
|
||||
} else {
|
||||
res = callback(app, query, qlen);
|
||||
}
|
||||
@@ -298,7 +303,6 @@ static int process_command(app_t *app, wait_server_cb *callback) {
|
||||
}
|
||||
|
||||
void init_query_args(query_args_t *q_args, params_t *params) {
|
||||
q_args->buf = NULL;
|
||||
q_args->sd = -1;
|
||||
q_args->state = S_N_D;
|
||||
q_args->buf_used = 0;
|
||||
@@ -4,8 +4,6 @@
|
||||
#ifndef QUERY_H
|
||||
#define QUERY_H
|
||||
|
||||
#include "../app.h"
|
||||
#include "main.h"
|
||||
#include <widget.h>
|
||||
|
||||
void query_loop(app_t *);
|
||||
@@ -1,9 +1,8 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include "../app.h"
|
||||
#include "app.h"
|
||||
#include "dialogue.h"
|
||||
#include "main.h"
|
||||
#include "query.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
@@ -18,7 +17,7 @@
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <widget.h>
|
||||
#include <windows.h>
|
||||
#include <windows/file_list.h>
|
||||
|
||||
void ask_uname_and_password(params_t *params);
|
||||
void ask_register(params_t *params, char *email);
|
||||
@@ -146,10 +145,10 @@ int process_server_command(char *line, int l_len, app_t *app) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
q_args->state = S_PREP_SERVER_MESSAGE;
|
||||
server_print_message_cb(app, line, l_len);
|
||||
if (q_args->state == S_NEXT_ACTION) return 1;
|
||||
|
||||
return -1;
|
||||
q_args->state = S_PREP_SERVER_MESSAGE;
|
||||
return server_print_message_cb(app, line, l_len);
|
||||
}
|
||||
|
||||
void ask_uname_and_password(params_t *params) {
|
||||
@@ -4,8 +4,8 @@
|
||||
#ifndef SERVER_H
|
||||
#define SERVER_H
|
||||
|
||||
#include "../app.h"
|
||||
#include "main.h"
|
||||
#include <common.h>
|
||||
|
||||
int process_server_command(char *line, int l_len, app_t *app);
|
||||
void ask_register(params_t *params, char *email);
|
||||
void server_send_string(query_args_t *q, const char *fmt, ...);
|
||||
@@ -1,367 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include "alert.h"
|
||||
#include "dialogue.h"
|
||||
#include "server.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <widget.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "file_processor.h"
|
||||
#include "main.h"
|
||||
#include "query.h"
|
||||
|
||||
static void fl_add(fl_item_t **cur, fl_item_t **start, char *fname);
|
||||
fl_item_t *fl_select(fl_item_t *start, int num);
|
||||
void fl_clear(fl_item_t **start, fl_item_t **current);
|
||||
|
||||
void file_list(file_args_t *f_args, query_args_t *q_args) {
|
||||
int32_t qlen;
|
||||
char *query = NULL;
|
||||
static char qbuf[INBUFSIZE * 2];
|
||||
static int32_t qbuf_used = 0;
|
||||
w_ui_file_list_t *fui = (w_ui_file_list_t *)q_args->main_ui->ui;
|
||||
w_dialogue_t *d = (w_dialogue_t *)q_args->active_dialogue;
|
||||
fui->start = &(f_args->l_start);
|
||||
fui->current = &(f_args->l_current);
|
||||
|
||||
if (qbuf_used == 0)
|
||||
qbuf[0] = 0;
|
||||
|
||||
while ((qlen = query_extract_from_buf(q_args->buf, &(q_args->buf_used),
|
||||
&query))) {
|
||||
if (!strncmp(":END:", query, sizeof(":END:") - 1)) {
|
||||
sscanf(query, ":END: PAGE %d/%d COUNT: %d/%d\n", &fui->current_page,
|
||||
&fui->pages, &fui->current_count, &fui->full_count);
|
||||
if (d != NULL && d->is_initiated) {
|
||||
d->needs_update = true;
|
||||
}
|
||||
w_fl_draw(fui);
|
||||
free(query);
|
||||
q_args->state = WAIT_CLIENT;
|
||||
break;
|
||||
}
|
||||
strcat(qbuf, query);
|
||||
qbuf_used += strlen(qbuf);
|
||||
if (strchr(qbuf, '\n') == NULL)
|
||||
continue;
|
||||
fl_add(&(f_args->l_current), &(f_args->l_start), qbuf);
|
||||
fui->current_count++;
|
||||
free(query);
|
||||
query = NULL;
|
||||
qbuf_used = 0;
|
||||
qbuf[0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t ui_file_select(file_args_t *f_args, query_args_t *q_args, int32_t idx) {
|
||||
fl_item_t *l_selected; /* from the list */
|
||||
fl_item_t *f_selected =
|
||||
&(f_args->f_selected); /* new copy of file struct (list be cleared) */
|
||||
struct stat st = {0};
|
||||
|
||||
l_selected = fl_select(f_args->l_start, idx);
|
||||
|
||||
if (l_selected == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(f_selected, l_selected, sizeof(fl_item_t));
|
||||
|
||||
if (stat(DOWNLOADS_DIR, &st) == -1) {
|
||||
mkdir(DOWNLOADS_DIR, 0700);
|
||||
}
|
||||
char *file_path = malloc(sizeof(DOWNLOADS_DIR) + strlen(l_selected->name) +
|
||||
2); // + '/' + '\0'
|
||||
f_selected->name = NULL;
|
||||
sprintf(file_path, "%s/%s", DOWNLOADS_DIR, l_selected->name);
|
||||
f_selected->name = strdup(l_selected->name);
|
||||
fl_clear(&f_args->l_start, &f_args->l_current);
|
||||
f_args->file_d = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
||||
if (f_args->file_d == -1) {
|
||||
server_send_string(q_args, "error: %s\n", f_selected->name);
|
||||
perror(f_selected->name);
|
||||
q_args->state = WAIT_CLIENT;
|
||||
free(file_path);
|
||||
return -2;
|
||||
}
|
||||
server_send_string(q_args, "file download [%s]\n", f_selected->name);
|
||||
q_args->state = S_FILE_DOWNLOAD;
|
||||
free(file_path);
|
||||
return OK;
|
||||
}
|
||||
|
||||
void file_download(file_args_t *f_args, query_args_t *q_args) {
|
||||
fl_item_t *f_selected = &(f_args->f_selected);
|
||||
static int32_t it_count = 0;
|
||||
static size_t it_interval = 0;
|
||||
|
||||
if (it_interval == 0) {
|
||||
it_interval = (f_selected->size / INBUFSIZE / 100) * 5; /* every 1% */
|
||||
if (it_interval == 0)
|
||||
it_interval = 1;
|
||||
}
|
||||
char answer[256];
|
||||
|
||||
static size_t size_rest = 0;
|
||||
int32_t progress = (f_selected->size - size_rest) * 100 / f_selected->size;
|
||||
w_pgb_ui_t *pb = (w_pgb_ui_t *)q_args->progress_bar;
|
||||
w_dialogue_t *d = (w_dialogue_t *)q_args->active_dialogue;
|
||||
w_ui_file_list_t *fui = (w_ui_file_list_t *)q_args->main_ui->ui;
|
||||
int32_t a_len = 0;
|
||||
|
||||
if (size_rest == 0)
|
||||
size_rest = f_selected->size;
|
||||
int32_t qlen = write(f_args->file_d, q_args->buf, q_args->buf_used);
|
||||
if (qlen) {
|
||||
it_count++;
|
||||
if (!(it_count % it_interval)) {
|
||||
pb->procent = progress;
|
||||
d->needs_update = true;
|
||||
} else {
|
||||
q_args->buf_used = 0;
|
||||
}
|
||||
if (size_rest < qlen) {
|
||||
size_rest = 0;
|
||||
} else {
|
||||
size_rest -= qlen;
|
||||
}
|
||||
if (size_rest == 0) {
|
||||
it_count = 0;
|
||||
it_interval = 0;
|
||||
f_selected->size = 0;
|
||||
close(f_args->file_d);
|
||||
d->needs_destroy = true;
|
||||
sprintf(answer, "File %s is downloaded from the server!",
|
||||
f_selected->name);
|
||||
q_args->notification = malloc(strlen(answer) + 1);
|
||||
strcpy(q_args->notification, answer);
|
||||
free(f_selected->name);
|
||||
server_send_string(q_args, "file list %d %d\n%n", fui->max_lines,
|
||||
fui->current_page, &a_len);
|
||||
q_args->state = S_FILE_LIST;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t file_upload_open(char *dpath, char *fname, query_args_t *q_args) {
|
||||
q_args->file = malloc(sizeof(p_file_t));
|
||||
q_args->file->path = malloc(strlen(dpath) + strlen(fname) + 2);
|
||||
q_args->file->signal = sig_continue;
|
||||
sprintf(q_args->file->path, "%s/%s", dpath, fname);
|
||||
int fd = open(q_args->file->path, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
perror(q_args->file->path);
|
||||
return -1;
|
||||
}
|
||||
size_t fsize = lseek(fd, 0, SEEK_END);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
q_args->file->fd = fd;
|
||||
q_args->file->name = malloc(strlen(fname) + 1);
|
||||
strcpy(q_args->file->name, fname);
|
||||
q_args->file->size = fsize;
|
||||
q_args->file->rest = fsize;
|
||||
q_args->file->description = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t file_upload_start(query_args_t *q_args) {
|
||||
char *query;
|
||||
query_extract_from_buf(q_args->buf, &(q_args->buf_used), &query);
|
||||
if (strcmp(query, "accept")) {
|
||||
clear_file_in_query(q_args);
|
||||
w_notification("Server response", dc_alert, query);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t file_upload(query_args_t *q_args) {
|
||||
static int32_t it_count = 0;
|
||||
static size_t it_interval = 0;
|
||||
static int package_rest = 0;
|
||||
int send_len = q_args->buf_used;
|
||||
if (it_interval == 0) {
|
||||
it_interval = (q_args->file->size / INBUFSIZE / 100) * 5; /* every 1% */
|
||||
if (it_interval == 0)
|
||||
it_interval = 1;
|
||||
}
|
||||
int32_t progress =
|
||||
(q_args->file->size - q_args->file->rest) * 100 / q_args->file->size;
|
||||
w_pgb_ui_t *pb = (w_pgb_ui_t *)q_args->progress_bar;
|
||||
w_dialogue_t *d = (w_dialogue_t *)q_args->active_dialogue;
|
||||
|
||||
if (package_rest == 0) {
|
||||
if (q_args->file->signal == sig_cancel) {
|
||||
/* reset function before exit */
|
||||
it_count = 0;
|
||||
it_interval = 0;
|
||||
return -2;
|
||||
}
|
||||
package_rest = PACKAGE_SIZE;
|
||||
s_file_pd_t fpd = {.signal = q_args->file->signal,
|
||||
.package_size = package_rest};
|
||||
write(q_args->sd, &fpd, sizeof(s_file_pd_t));
|
||||
} else if (package_rest < send_len) {
|
||||
send_len = package_rest;
|
||||
}
|
||||
|
||||
if (q_args->buf_used > 0) {
|
||||
int wlen = write(q_args->sd, q_args->buf, send_len);
|
||||
if (q_args->buf_used != wlen) {
|
||||
/* TODO: ошибка */
|
||||
/* reset function before exit */
|
||||
it_count = 0;
|
||||
it_interval = 0;
|
||||
package_rest = 0;
|
||||
return -1;
|
||||
} else {
|
||||
it_count++;
|
||||
if (!(it_count % it_interval)) {
|
||||
pb->procent = progress;
|
||||
d->needs_update = true;
|
||||
}
|
||||
q_args->file->rest -= wlen;
|
||||
package_rest -= wlen;
|
||||
if (package_rest == 0) {
|
||||
if (q_args->file->signal == sig_cancel) {
|
||||
/* reset function before exit */
|
||||
it_count = 0;
|
||||
it_interval = 0;
|
||||
return -2;
|
||||
}
|
||||
package_rest = PACKAGE_SIZE;
|
||||
s_file_pd_t fpd = {.signal = q_args->file->signal,
|
||||
.package_size = package_rest};
|
||||
write(q_args->sd, &fpd, sizeof(s_file_pd_t));
|
||||
/* send the rest */
|
||||
if (q_args->buf_used > send_len) {
|
||||
wlen = write(q_args->sd, q_args->buf + send_len,
|
||||
q_args->buf_used - send_len);
|
||||
if (q_args->buf_used != wlen) {
|
||||
/* TODO: ошибка */
|
||||
/* reset function before exit */
|
||||
it_count = 0;
|
||||
it_interval = 0;
|
||||
package_rest = 0;
|
||||
return -1;
|
||||
}
|
||||
q_args->file->rest -= wlen;
|
||||
package_rest -= wlen;
|
||||
}
|
||||
}
|
||||
q_args->buf[0] = 0;
|
||||
q_args->buf_used = 0;
|
||||
}
|
||||
} else {
|
||||
/* reset function before exit */
|
||||
it_count = 0;
|
||||
it_interval = 0;
|
||||
package_rest = 0;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void clear_file_in_query(query_args_t *q_args) {
|
||||
free(q_args->file->name);
|
||||
free(q_args->file->path);
|
||||
if (q_args->file->description != NULL) {
|
||||
free(q_args->file->description);
|
||||
}
|
||||
free(q_args->file);
|
||||
q_args->file = NULL;
|
||||
}
|
||||
|
||||
void init_file_args(file_args_t *f_args) {
|
||||
f_args->file_d = 0;
|
||||
f_args->f_selected.name = NULL;
|
||||
f_args->f_selected.next = NULL;
|
||||
f_args->f_selected.size = 0;
|
||||
f_args->l_current = NULL;
|
||||
f_args->l_start = NULL;
|
||||
}
|
||||
|
||||
/* work with file list */
|
||||
static void fl_add(fl_item_t **cur, fl_item_t **start, char *line) {
|
||||
char fowner[32];
|
||||
char *descr_begin = NULL;
|
||||
int h_len;
|
||||
char *name_begin = strchr(line, '[') + 1;
|
||||
char *name_end = strrchr(line, ']');
|
||||
int name_len = name_end - name_begin;
|
||||
fl_item_t *fitem = malloc(sizeof(fl_item_t));
|
||||
sscanf(name_end, "] %zu %s%n", &(fitem->size), fowner, &h_len);
|
||||
/* file name */
|
||||
fitem->name = malloc((sizeof(char)) * (name_len + 1));
|
||||
strncpy(fitem->name, name_begin, name_len);
|
||||
fitem->name[name_len] = 0;
|
||||
/* file owner */
|
||||
fitem->owner = malloc((sizeof(char)) * (strlen(fowner) + 1));
|
||||
strcpy(fitem->owner, fowner);
|
||||
/* file description */
|
||||
descr_begin = name_end + h_len + 1;
|
||||
if (strcmp(descr_begin, "\n")) {
|
||||
int d_len = strlen(line) - h_len;
|
||||
fitem->description = malloc((sizeof(char)) * (d_len));
|
||||
strcpy(fitem->description, descr_begin); // +1 to escape the dividing \32
|
||||
for (int i = 0; i < d_len; i++) {
|
||||
if (fitem->description[i] == '\a') {
|
||||
fitem->description[i] = '\n';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fitem->description = NULL;
|
||||
}
|
||||
/* fitem->description[strlen(line + line_pos)] = 0; */
|
||||
fitem->next = NULL;
|
||||
if (*cur != NULL) {
|
||||
(*cur)->next = fitem;
|
||||
} else {
|
||||
*start = fitem;
|
||||
}
|
||||
*cur = fitem;
|
||||
}
|
||||
|
||||
fl_item_t *fl_select(fl_item_t *start, int num) {
|
||||
fl_item_t *current = start;
|
||||
for (; num != 1; num--) {
|
||||
if (current->next != NULL) {
|
||||
current = current->next;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
void fl_clear(fl_item_t **start, fl_item_t **arg_current) {
|
||||
if (*start == NULL)
|
||||
return;
|
||||
fl_item_t *next, *current;
|
||||
current = *start;
|
||||
|
||||
do {
|
||||
next = current->next;
|
||||
free(current->description);
|
||||
free(current->name);
|
||||
free(current);
|
||||
} while ((current = next) != NULL);
|
||||
*start = NULL;
|
||||
*arg_current = NULL;
|
||||
}
|
||||
+21
-24
@@ -6,7 +6,6 @@
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
@@ -15,53 +14,51 @@
|
||||
#include <widget.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "main.h"
|
||||
#include "../app.h"
|
||||
#include "connection.h"
|
||||
#include "file_list.h"
|
||||
#include "query.h"
|
||||
#include "alert.h"
|
||||
#include "core/file_processor.h"
|
||||
#include <core/app.h>
|
||||
#include <core/connection.h>
|
||||
#include <core/query.h>
|
||||
|
||||
int32_t m_id = 0;
|
||||
|
||||
int32_t process_user_input(app_t *app, w_cb_args_t *d_args);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
app_t *app;
|
||||
app_t app;
|
||||
params_t params;
|
||||
query_args_t *q_args = malloc(sizeof(query_args_t));
|
||||
|
||||
app = calloc(1, sizeof(app_t));
|
||||
query_args_t q_args;
|
||||
file_args_t file_args;
|
||||
|
||||
app_init_nc();
|
||||
|
||||
app = app_init();
|
||||
app.params = ¶ms;
|
||||
app_init(&app);
|
||||
init_params(¶ms);
|
||||
app->params = ¶ms;
|
||||
analyze_args(argc, argv, ¶ms);
|
||||
|
||||
/* init alert */
|
||||
w_alert_init(app);
|
||||
w_alert_init(&app);
|
||||
|
||||
init_query_args(q_args, app->params);
|
||||
q_args->buf = malloc(INBUFSIZE);
|
||||
app->query_args = q_args;
|
||||
|
||||
main_window_set(app, mw_fl_server);
|
||||
init_query_args(&q_args, app.params);
|
||||
app.query_args = &q_args;
|
||||
|
||||
main_window_set(&app, mw_fl_server);
|
||||
init_file_args(&file_args, &app);
|
||||
/* init client to connect to the server */
|
||||
app->params->sd = init_client();
|
||||
app.params->sd = init_client();
|
||||
|
||||
if (params.addr == 0) {
|
||||
app->query_args->state = S_ASK_SEVER_IP;
|
||||
app.query_args->state = S_ASK_SEVER_IP;
|
||||
} else {
|
||||
connect_to_server(app);
|
||||
connect_to_server(&app);
|
||||
}
|
||||
|
||||
app->query_args->main_ui = &(app->main_ui);
|
||||
app.query_args->main_ui = &(app.main_ui);
|
||||
|
||||
query_loop(app);
|
||||
query_loop(&app);
|
||||
clear_params(¶ms);
|
||||
app_destroy(app, 0);
|
||||
app_destroy(&app, 0);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
-121
@@ -9,125 +9,4 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define LOCAL_PORT 1999
|
||||
#define SERVER_PORT 2000
|
||||
#define LISTEN_QLEN 32
|
||||
#define INBUFSIZE 1024
|
||||
#define DOWNLOADS_DIR "./Downloads"
|
||||
#define DIALOGUE_TITLE 64
|
||||
#define DIALOGUE_TEXT 4096
|
||||
#define INPUT_TEXT DIALOGUE_TITLE
|
||||
#define PACKAGE_SIZE INBUFSIZE * 10
|
||||
|
||||
typedef struct params {
|
||||
unsigned short port;
|
||||
unsigned addr;
|
||||
char *uname;
|
||||
char *pass;
|
||||
char privileges;
|
||||
int32_t sd;
|
||||
int32_t is_connected : 1;
|
||||
} params_t;
|
||||
|
||||
enum state {
|
||||
/* TUI-based */
|
||||
S_N_D,
|
||||
S_ASK_SEVER_IP,
|
||||
S_ASK_LOGIN_TYPE,
|
||||
S_ASK_LOGIN_USER,
|
||||
S_ASK_REGISTER,
|
||||
S_WAIT_SERVER,
|
||||
S_PREP_SERVER_MESSAGE,
|
||||
S_PRINT_SERVER_MESSAGE,
|
||||
S_FILE_LIST,
|
||||
S_WAIT_PASS,
|
||||
/* S_FILE_SELECT, */
|
||||
S_FILE_DOWNLOAD,
|
||||
S_UPLOAD_PARAMS,
|
||||
S_UPLOAD_FILE,
|
||||
/* S_UPLOAD_FILE_SELECT, */
|
||||
S_UPLOAD_REQUESTED,
|
||||
S_UPLOAD_SERVER_FINISHES,
|
||||
S_ASK_USER_BEFORE_LOGIN,
|
||||
S_ERR,
|
||||
S_NEXT_ACTION,
|
||||
S_WAIT_REGISTER_CONFIRMATION,
|
||||
/* Legacy */
|
||||
WAIT_SERVER_INIT,
|
||||
WAIT_SERVER,
|
||||
WAIT_REGISTER,
|
||||
WAIT_REGISTER_CONFIRMATION,
|
||||
WAIT_CLIENT,
|
||||
};
|
||||
|
||||
enum package_signal { sig_continue, sig_cancel, sig_finish, sig_pause };
|
||||
|
||||
typedef struct {
|
||||
enum package_signal signal;
|
||||
size_t package_size;
|
||||
} s_file_pd_t; /* file package descriptor */
|
||||
|
||||
typedef struct p_file {
|
||||
char *name;
|
||||
char *path;
|
||||
char *description;
|
||||
size_t size;
|
||||
size_t rest;
|
||||
enum package_signal signal;
|
||||
int fd;
|
||||
} p_file_t;
|
||||
|
||||
typedef struct fl_item {
|
||||
size_t size;
|
||||
char *name;
|
||||
char *description;
|
||||
char *owner;
|
||||
struct fl_item *next;
|
||||
} fl_item_t;
|
||||
|
||||
typedef struct file_args {
|
||||
int file_d;
|
||||
fl_item_t f_selected;
|
||||
fl_item_t *l_start;
|
||||
fl_item_t *l_current;
|
||||
} file_args_t;
|
||||
|
||||
enum main_window_type {
|
||||
mw_fl_server, /* file list on the server */
|
||||
mw_fl_local, /* local file list */
|
||||
mw_f_desc, /* file description */
|
||||
};
|
||||
|
||||
typedef void(draw_f_t)(void *fui);
|
||||
|
||||
typedef struct main_window {
|
||||
enum main_window_type type;
|
||||
void *ui; /* structure which describes window UI */
|
||||
int32_t (*cb_b_press)(void *app, void *args); /* callback on button press */
|
||||
void (*cb_ui_refresh)(void *app); /* refresh callback */
|
||||
void (*reset)(void *app); /* function to reset the state of the window */
|
||||
draw_f_t *draw;
|
||||
struct action_key *b_keys;
|
||||
int32_t b_keys_len;
|
||||
} main_window_t;
|
||||
|
||||
typedef struct query_args {
|
||||
int32_t sd;
|
||||
int32_t buf_used;
|
||||
p_file_t *file;
|
||||
enum state state;
|
||||
char *buf;
|
||||
bool from_server;
|
||||
params_t *params;
|
||||
struct {
|
||||
char *text;
|
||||
int32_t capacity;
|
||||
int32_t size;
|
||||
} server_message;
|
||||
main_window_t *main_ui;
|
||||
void *progress_bar;
|
||||
void *active_dialogue;
|
||||
void *notification;
|
||||
} query_args_t;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,116 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <modals.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <widget.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "../app.h"
|
||||
#include "alert.h"
|
||||
#include "connection.h"
|
||||
#include "main.h"
|
||||
#include "query.h"
|
||||
|
||||
int32_t m_id = 0;
|
||||
|
||||
void mini_loop(app_t *app) {
|
||||
w_cb_args_t d_args = {
|
||||
.app = app, .element = NULL, .data = NULL, .resp_data.code = cbrc_none};
|
||||
|
||||
fd_set readfds;
|
||||
|
||||
while (true) {
|
||||
app_draw_modal(app);
|
||||
main_window_draw(app);
|
||||
app_refresh(app);
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(STDIN_FILENO, &readfds);
|
||||
int maxfd = STDIN_FILENO;
|
||||
|
||||
int sr = select(maxfd + 1, &readfds, NULL, NULL, NULL);
|
||||
|
||||
if (sr == -1) {
|
||||
/* perror("select"); */
|
||||
exit(3);
|
||||
}
|
||||
|
||||
if (app->main_ui.cb_b_press != NULL && app->modal.is_initiated == false) {
|
||||
if (ERR == app->main_ui.cb_b_press(app, &d_args)) {
|
||||
w_alert("error while processing user input");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
app_t app;
|
||||
file_args_t fargs;
|
||||
query_args_t q_args;
|
||||
params_t params;
|
||||
p_file_t file;
|
||||
|
||||
init_query_args(&q_args, app.params);
|
||||
|
||||
app.params = ¶ms;
|
||||
app.query_args = &q_args;
|
||||
app.file_args = &fargs;
|
||||
|
||||
app_init_nc();
|
||||
init_params(¶ms);
|
||||
w_alert_init(&app);
|
||||
app_init(&app);
|
||||
|
||||
q_args.file = &file;
|
||||
app.query_args->main_ui = &(app.main_ui);
|
||||
file.name = "Test file #1";
|
||||
params.is_connected = false;
|
||||
app.modal.is_initiated = false;
|
||||
|
||||
main_window_set(&app, mw_f_desc);
|
||||
mini_loop(&app);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int32_t process_user_input(app_t *app, w_cb_args_t *d_args) {
|
||||
int32_t c;
|
||||
w_ui_file_list_t *fui = (w_ui_file_list_t *)app->query_args->main_ui->ui;
|
||||
c = wgetch(app->win);
|
||||
switch (c) {
|
||||
case KEY_F(9):
|
||||
app_destroy(app, 0);
|
||||
return OK;
|
||||
case 'U':
|
||||
case 'u':
|
||||
if (!app->modal.is_initiated && !fui->active_search &&
|
||||
app->main_ui.type != mw_fl_local) {
|
||||
/* app->query_args->state = S_UPLOAD_FILE_SELECT; */
|
||||
main_window_set(app, mw_fl_local);
|
||||
main_window_draw(app);
|
||||
d_args->element = app->main_ui.ui;
|
||||
break;
|
||||
}
|
||||
case 's':
|
||||
case 'S':
|
||||
if (!app->modal.is_initiated && !fui->active_search) {
|
||||
fui->active_search = true;
|
||||
w_fl_draw(fui);
|
||||
return OK;
|
||||
}
|
||||
default:
|
||||
d_args->data = (void *)&c;
|
||||
app->active_callback(d_args);
|
||||
break;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
@@ -2,13 +2,13 @@ add_library(modals STATIC)
|
||||
|
||||
file(GLOB M_SOURCES "*.c")
|
||||
|
||||
target_link_directories(modals
|
||||
PRIVATE
|
||||
"../widget/"
|
||||
"../widget_core/"
|
||||
"../lib/"
|
||||
"../utils/"
|
||||
)
|
||||
# target_link_directories(modals
|
||||
# PRIVATE
|
||||
# "../widget/"
|
||||
# "../widget_core/"
|
||||
# "../lib/"
|
||||
# "../utils/"
|
||||
# )
|
||||
|
||||
target_link_libraries(modals
|
||||
PRIVATE
|
||||
@@ -16,6 +16,8 @@ target_link_libraries(modals
|
||||
widget_core
|
||||
bstring
|
||||
utils
|
||||
common
|
||||
windows
|
||||
)
|
||||
|
||||
target_sources(modals
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <widget.h>
|
||||
#include <widget_core.h>
|
||||
#include "../connection.h"
|
||||
#include <core/connection.h>
|
||||
#include <core/app.h>
|
||||
#include "group.h"
|
||||
|
||||
typedef struct {
|
||||
|
||||
+26
-2
@@ -1,17 +1,41 @@
|
||||
#include "group.h"
|
||||
#include <core/server.h>
|
||||
#include <windows/file_list.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <widget.h>
|
||||
|
||||
#include "alert.h"
|
||||
#include "dialogue.h"
|
||||
#include "group.h"
|
||||
|
||||
void dwn_pr_modal_cb(w_cb_args_t *args) {
|
||||
/* only cancel action */
|
||||
int32_t key = *((int32_t *)args->data);
|
||||
|
||||
if (key != '\n') {
|
||||
return;
|
||||
}
|
||||
|
||||
app_t *app = args->app;
|
||||
app->query_args->file->signal = sig_cancel;
|
||||
w_dialogue_t *d = &(app->modal);
|
||||
/* TODO: forward actual file name */
|
||||
if (app->query_args->state == S_UPLOAD_FILE) {
|
||||
bool response = w_bool_ask("Cancel file upload", "Yes", "No", false,
|
||||
"You're going to cancel upload\n Are you sure?");
|
||||
if (response) {
|
||||
app->query_args->file->signal = sig_cancel;
|
||||
}
|
||||
} else if (app->query_args->state == S_DOWNLOAD_FILE) {
|
||||
w_ui_file_list_t *fui = (w_ui_file_list_t *)app->query_args->main_ui->ui;
|
||||
bool response =
|
||||
w_bool_ask("Cancel file download", "Yes", "No", false,
|
||||
"You're going to cancel download\n Are you sure?");
|
||||
if (response) {
|
||||
app->query_args->file->signal = sig_cancel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w_dialogue_t *m_load_pr_init(app_t *app, bool is_upload) {
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include <widget.h>
|
||||
#include "../server.h"
|
||||
#include <core/server.h>
|
||||
#include <core/app.h>
|
||||
|
||||
typedef struct {
|
||||
w_button_t *element;
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
#include <unistd.h>
|
||||
#include <widget.h>
|
||||
#include "../server.h"
|
||||
#include <core/app.h>
|
||||
#include <core/server.h>
|
||||
|
||||
typedef struct {
|
||||
w_button_t *element;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "alert.h"
|
||||
#include "group.h"
|
||||
#include "../server.h"
|
||||
#include <core/server.h>
|
||||
|
||||
typedef struct {
|
||||
w_button_t *element;
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
#include "checkbox.h"
|
||||
#include "group.h"
|
||||
#include "widget_core.h"
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <utils.h>
|
||||
#include <widget.h>
|
||||
|
||||
#include "../server.h"
|
||||
#include <core/server.h>
|
||||
#include "checkbox.h"
|
||||
#include "group.h"
|
||||
|
||||
void upload_props_dialogue_modal_cb(w_cb_args_t *args) {
|
||||
w_cb_args_t d_args;
|
||||
@@ -40,10 +39,6 @@ void upload_props_dialogue_modal_cb(w_cb_args_t *args) {
|
||||
app->query_args->file->size, privileges);
|
||||
/* app->query_args->file->description =
|
||||
malloc(desc_input->value_len + 9); reserve space for \n:END:\n */
|
||||
app->query_args->file->description =
|
||||
malloc(sizeof("[Empty description]\n:END:\n"));
|
||||
strcpy(app->query_args->file->description,
|
||||
"[Empty description]\n:END:\n");
|
||||
d->needs_destroy = true;
|
||||
app->query_args->state = S_UPLOAD_REQUESTED;
|
||||
break;
|
||||
|
||||
+49
-3
@@ -13,17 +13,51 @@ void u_d_arr_ptr_init(d_array_ptr_t *arr, int32_t init_capacity) {
|
||||
memset(arr->arr, 0, sizeof(void *) * init_capacity);
|
||||
}
|
||||
|
||||
void u_d_array_append(d_array_ptr_t *arr, void *el_ptr, size_t el_len) {
|
||||
u_d_arr_ptr_add(arr, el_ptr, arr->length);
|
||||
}
|
||||
|
||||
void u_d_arr_ptr_add(d_array_ptr_t *arr, void *ptr, int32_t idx) {
|
||||
if (idx > arr->capacity - 1) {
|
||||
int32_t new_capacity = idx * 2;
|
||||
arr->arr = realloc(arr->arr, new_capacity);
|
||||
memset(arr->arr+arr->capacity, 0, arr->capacity - new_capacity);
|
||||
memset(arr->arr + arr->capacity, 0, arr->capacity - new_capacity);
|
||||
arr->capacity = new_capacity;
|
||||
}
|
||||
if (arr->fist_el == NULL) arr->fist_el = ptr;
|
||||
if (arr->fist_el == NULL)
|
||||
arr->fist_el = ptr;
|
||||
|
||||
if (arr->arr[idx] != NULL) {
|
||||
memmove(arr->arr + idx + 1, arr->arr + idx, sizeof (void *) * (arr->length - idx));
|
||||
}
|
||||
arr->last_el = ptr;
|
||||
arr->arr[idx] = ptr;
|
||||
arr->length = idx+1;
|
||||
arr->length = idx + 1;
|
||||
}
|
||||
|
||||
void u_d_arr_ptr_remove_cb(d_array_ptr_t *arr, void *ptr, int32_t idx,
|
||||
u_d_arr_free_callback *callback) {
|
||||
void *_ptr = NULL;
|
||||
int _idx = idx;
|
||||
|
||||
if (idx > 0) {
|
||||
_ptr = arr->arr[idx];
|
||||
} else if (ptr != NULL) {
|
||||
for (int i = 0; i < arr->length; i++) {
|
||||
if (arr->arr[i] == ptr) {
|
||||
_ptr = arr->arr[i];
|
||||
_idx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callback(_ptr);
|
||||
arr->arr[_idx] = NULL;
|
||||
arr->length--;
|
||||
memmove(arr->arr + _idx, arr->arr + _idx + 1,
|
||||
(arr->length - _idx) * sizeof(void *));
|
||||
arr->arr[arr->length] = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
void u_d_arr_ptr_free(d_array_ptr_t *arr) {
|
||||
@@ -31,4 +65,16 @@ void u_d_arr_ptr_free(d_array_ptr_t *arr) {
|
||||
arr->length = 0;
|
||||
free(arr->arr);
|
||||
arr->arr = NULL;
|
||||
}
|
||||
|
||||
void u_d_arr_free_cb(d_array_ptr_t *d_arr, u_d_arr_free_callback *callback) {
|
||||
for (int i = 0; i < d_arr->length; i++) {
|
||||
if (d_arr->arr[i] != NULL) {
|
||||
callback(d_arr->arr[i]);
|
||||
}
|
||||
}
|
||||
d_arr->capacity = 0;
|
||||
d_arr->length = 0;
|
||||
free(d_arr->arr);
|
||||
d_arr->arr = NULL;
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
#ifndef D_ARRAY_H
|
||||
#define D_ARRAY_H
|
||||
/* Dynamic array of numbers */
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct d_array_ptr_t {
|
||||
int32_t capacity;
|
||||
int32_t length;
|
||||
@@ -10,8 +13,12 @@ typedef struct d_array_ptr_t {
|
||||
void *last_el;
|
||||
} d_array_ptr_t;
|
||||
|
||||
typedef void u_d_arr_free_callback(void *arg);
|
||||
|
||||
void u_d_arr_ptr_init(d_array_ptr_t *arr, int32_t init_capacity);
|
||||
void u_d_arr_ptr_add(d_array_ptr_t *arr, void *ptr, int32_t idx);
|
||||
void u_d_arr_ptr_free(d_array_ptr_t *arr);
|
||||
void u_d_array_append(d_array_ptr_t *arr, void *el_ptr, size_t el_len);
|
||||
void u_d_arr_free_cb(d_array_ptr_t *arr, u_d_arr_free_callback *callback);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,202 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "dlist.h"
|
||||
|
||||
/*
|
||||
Uses *add* when needs to make a full copy of the element.
|
||||
Uses *insert* when operates with nodes.
|
||||
*/
|
||||
|
||||
dlist_t *_dlist_init(void *el_ptr, int el_siz, dblist_add_cb_t add_cb) {
|
||||
dlist_t *dlist = malloc(sizeof(dlist_t));
|
||||
dlist->len = 0;
|
||||
dlist->current = NULL;
|
||||
dlist->start = NULL;
|
||||
|
||||
if (el_ptr != NULL) {
|
||||
_dlist_add(dlist, el_ptr, el_siz, add_cb, false);
|
||||
}
|
||||
|
||||
return dlist;
|
||||
}
|
||||
|
||||
void _dlist_insert(dlist_t *dlist, dlist_node_t *new_el,
|
||||
dlist_node_t *prev_node) {
|
||||
if (prev_node != NULL) {
|
||||
new_el->next = prev_node->next;
|
||||
prev_node->next->previous = new_el;
|
||||
new_el->previous = prev_node;
|
||||
prev_node->next = new_el;
|
||||
if (new_el->next == NULL) {
|
||||
dlist->current = new_el;
|
||||
dlist->work_pointer = new_el;
|
||||
}
|
||||
} else {
|
||||
new_el->next = dlist->start;
|
||||
new_el->previous = NULL;
|
||||
dlist->start->previous = new_el;
|
||||
dlist->start = new_el;
|
||||
}
|
||||
dlist->len++;
|
||||
}
|
||||
|
||||
void _dlist_insert_end(dlist_t *dlist, dlist_node_t *new_node) {
|
||||
dlist->current->next = new_node;
|
||||
new_node->previous = dlist->current;
|
||||
new_node->next = NULL;
|
||||
dlist->current = new_node;
|
||||
dlist->work_pointer = new_node;
|
||||
dlist->len++;
|
||||
}
|
||||
|
||||
void _dlist_add_sort(dlist_t *dlist, void *el_ptr, int el_siz,
|
||||
dblist_sort_cb_t cb, dblist_add_cb_t add_cb) {
|
||||
dlist_node_t *node = malloc(sizeof(dlist_node_t));
|
||||
node->el_ptr = malloc(el_siz);
|
||||
add_cb(node->el_ptr, el_ptr);
|
||||
|
||||
dlist_node_t *dlist_item = dlist->start;
|
||||
bool res = true;
|
||||
if (dlist->start == NULL) {
|
||||
dlist->start = node;
|
||||
dlist->current = node;
|
||||
node->next = NULL;
|
||||
node->previous = NULL;
|
||||
dlist->work_pointer = node;
|
||||
dlist->len++;
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < dlist->len; i++) {
|
||||
res = cb(el_ptr, dlist_item->el_ptr);
|
||||
if (!res)
|
||||
break;
|
||||
dlist_item = dlist_item->next;
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
dlist_item = dlist_item->previous;
|
||||
_dlist_insert(dlist, node, dlist_item);
|
||||
} else {
|
||||
_dlist_insert_end(dlist, node);
|
||||
}
|
||||
}
|
||||
|
||||
void _dlist_add(dlist_t *dlist, void *el_ptr, int el_siz,
|
||||
dblist_add_cb_t *add_cb, bool prepend) {
|
||||
dlist_node_t *node = malloc(sizeof(dlist_node_t));
|
||||
node->el_ptr = malloc(el_siz);
|
||||
add_cb(node->el_ptr, el_ptr);
|
||||
|
||||
if (dlist->start == NULL) {
|
||||
node->previous = NULL;
|
||||
node->next = NULL;
|
||||
dlist->start = node;
|
||||
dlist->current = node;
|
||||
dlist->work_pointer = node;
|
||||
} else if (prepend) {
|
||||
node->previous = NULL;
|
||||
node->next = dlist->start;
|
||||
dlist->start->previous = node;
|
||||
dlist->start = node;
|
||||
} else {
|
||||
node->previous = dlist->current;
|
||||
node->next = NULL;
|
||||
dlist->current->next = node;
|
||||
dlist->current = node;
|
||||
dlist->work_pointer = node;
|
||||
}
|
||||
dlist->len++;
|
||||
}
|
||||
|
||||
dlist_node_t *find_node_by_ptr(dlist_t *dlist, void *el_ptr) {
|
||||
dlist_node_t *node = dlist->start;
|
||||
do {
|
||||
if (node->el_ptr == el_ptr)
|
||||
break;
|
||||
} while ((node = node->next) != NULL);
|
||||
if (node->el_ptr == el_ptr)
|
||||
return node;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int dlist_remove_by_ptr(dlist_t *dlist, void *el_ptr, dblist_rm_cb_t cb) {
|
||||
dlist_node_t *node = find_node_by_ptr(dlist, el_ptr);
|
||||
if (node == NULL)
|
||||
return -1;
|
||||
node->next->previous = node->previous;
|
||||
node->previous->next = node->next;
|
||||
dlist->len--;
|
||||
if (node->next == NULL)
|
||||
dlist->current = node->previous;
|
||||
if (node->previous == NULL)
|
||||
dlist->start = node->next;
|
||||
cb(node->el_ptr);
|
||||
free(node);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int dlist_clear_list(dlist_t *dlist, dblist_rm_cb_t cb) {
|
||||
dlist_node_t *current = dlist->start;
|
||||
dlist_node_t *next = NULL;
|
||||
|
||||
if (current == NULL) return 1;
|
||||
|
||||
do {
|
||||
cb(current->el_ptr);
|
||||
next = current->next;
|
||||
free(current);
|
||||
current = next;
|
||||
} while (current != NULL);
|
||||
|
||||
dlist->len = 0;
|
||||
dlist->current = NULL;
|
||||
dlist->start = NULL;
|
||||
dlist->work_pointer = NULL;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Iterators */
|
||||
|
||||
void *dlist_it_prev(dlist_t *dlist) {
|
||||
dlist_node_t *cur_ptr = dlist->work_pointer;
|
||||
if (cur_ptr != NULL) {
|
||||
dlist->work_pointer = dlist->work_pointer->previous;
|
||||
return cur_ptr->el_ptr;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *dlist_it_next(dlist_t *dlist) {
|
||||
dlist_node_t *cur_ptr = dlist->work_pointer;
|
||||
if (cur_ptr != NULL) {
|
||||
dlist->work_pointer = dlist->work_pointer->next;
|
||||
return cur_ptr->el_ptr;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dlist_node_t *dlist_wind_fwd(dlist_t *dlist, dlist_node_t *start, int count) {
|
||||
dlist_node_t *new_ptr = start;
|
||||
|
||||
for (int i = 0; i <= count && new_ptr != NULL; i++) {
|
||||
new_ptr = new_ptr->next;
|
||||
}
|
||||
dlist->work_pointer = new_ptr;
|
||||
return new_ptr;
|
||||
}
|
||||
|
||||
dlist_node_t *dlist_wind_bwd(dlist_t *dlist, dlist_node_t *start, int count) {
|
||||
dlist_node_t *new_ptr = start;
|
||||
|
||||
for (int i = 0; i <= count && new_ptr != NULL; i++) {
|
||||
new_ptr = new_ptr->previous;
|
||||
}
|
||||
dlist->work_pointer = new_ptr;
|
||||
return new_ptr;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef DLIST_H
|
||||
#define DLIST_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct dlist_node_t {
|
||||
struct dlist_node_t *next;
|
||||
struct dlist_node_t *previous;
|
||||
void *el_ptr;
|
||||
} dlist_node_t;
|
||||
|
||||
typedef struct dlist_t {
|
||||
dlist_node_t *start;
|
||||
dlist_node_t *current;
|
||||
dlist_node_t *work_pointer;
|
||||
size_t len;
|
||||
} dlist_t;
|
||||
|
||||
/* Returns if a > b */
|
||||
typedef bool dblist_sort_cb_t(void *a, void *b);
|
||||
typedef bool dblist_add_cb_t(void *dst, void *src);
|
||||
typedef bool dblist_rm_cb_t(void *el_ptr);
|
||||
|
||||
dlist_t *_dlist_init(void *el_ptr, int el_siz, dblist_add_cb_t);
|
||||
void *dlist_get_current(dlist_t *dlist);
|
||||
void *dlist_it_prev(dlist_t *dlist);
|
||||
void _dlist_add(dlist_t *dlist, void *el_ptr, int el_siz,
|
||||
dblist_add_cb_t add_cb, bool prepend);
|
||||
void *dlist_it_prev(dlist_t *dlist);
|
||||
void *dlist_it_next(dlist_t *dlist);
|
||||
void _dlist_add_sort(dlist_t *dlist, void *el_ptr, int el_siz,
|
||||
dblist_sort_cb_t cb, dblist_add_cb_t add_cb);
|
||||
int dlist_remove_by_ptr(dlist_t *dlist, void *el_ptr, dblist_rm_cb_t cb);
|
||||
int dlist_clear_list(dlist_t *dlist, dblist_rm_cb_t cb);
|
||||
dlist_node_t *dlist_wind_fwd(dlist_t *dlist, dlist_node_t *start, int count);
|
||||
dlist_node_t *dlist_wind_bwd(dlist_t *dlist, dlist_node_t *start, int count);
|
||||
|
||||
#define dlist_init(el_ptr, el_def, add_cb) \
|
||||
_dlist_init(el_ptr, sizeof(el_def), add_cb)
|
||||
#define dlist_add(dlist, el_ptr, el_def, add_cb, prepend) \
|
||||
_dlist_add(dlist, el_ptr, sizeof(el_def), add_cb, prepend)
|
||||
#define dlist_get_current(dlist) dlist->current->el_ptr
|
||||
#define dlist_get_working_point(dlist) dlist->current->el_ptr
|
||||
#define dlist_add_sort(dlist, el_ptr, el_typ, sort_cb, add_cb) \
|
||||
_dlist_add_sort(dlist, el_ptr, sizeof(el_typ), sort_cb, add_cb)
|
||||
#define dlist_get_ptr(node_ptr) node_ptr == NULL ? NULL : node_ptr->el_ptr
|
||||
#define dlist_set_wp(dlist, node_ptr) dlist->work_pointer = node_ptr
|
||||
#define dlist_get_wp(dlist) dlist->work_pointer
|
||||
|
||||
#endif
|
||||
+22
-5
@@ -28,19 +28,19 @@ const char *u_utf8_wind_n(const char *s, int n) {
|
||||
|
||||
int32_t u_utf8_convert_to_wide(const char *utf8_str,
|
||||
wchar_t **restrict wide_str,
|
||||
size_t *restrict len) {
|
||||
size_t *restrict o_len) {
|
||||
// Step 1: Convert UTF-8 (MBCS) to wchar_t (wide)
|
||||
size_t wide_len =
|
||||
u_utf8_code_points_count(utf8_str); // Get required length (-1 on error)
|
||||
if (wide_len == (size_t)-1) {
|
||||
*len = 0;
|
||||
*o_len = 0;
|
||||
perror("mbstowcs failed");
|
||||
return 1;
|
||||
}
|
||||
*wide_str =
|
||||
malloc((wide_len + 1) * sizeof(wchar_t)); // +1 for null terminator
|
||||
mbstowcs(*wide_str, utf8_str, wide_len + 1); // Perform conversion
|
||||
*len = wide_len;
|
||||
*o_len = wide_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -55,9 +55,9 @@ int32_t u_utf8_curs_printw(WINDOW *win, int32_t *y, int32_t *x,
|
||||
|
||||
if (multiline) {
|
||||
_y++;
|
||||
for (int i = 1; max_len*i < nsize; i++) {
|
||||
for (int i = 1; max_len * i < nsize; i++) {
|
||||
_x = *x;
|
||||
mvwaddnwstr(win, _y++, _x, wline + max_len*i, max_len);
|
||||
mvwaddnwstr(win, _y++, _x, wline + max_len * i, max_len);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,3 +67,20 @@ int32_t u_utf8_curs_printw(WINDOW *win, int32_t *y, int32_t *x,
|
||||
free(wline);
|
||||
return nsize;
|
||||
}
|
||||
|
||||
wchar_t u_utf8_get_full_letter(int key, WINDOW *win) {
|
||||
char long_key[3];
|
||||
int short_val_len = 1;
|
||||
wchar_t wkey;
|
||||
long_key[0] = key;
|
||||
if ((key & 0xC0) == 0xC0) {
|
||||
long_key[1] = wgetch(win);
|
||||
long_key[2] = '\0';
|
||||
short_val_len++;
|
||||
} else {
|
||||
long_key[1] = '\0';
|
||||
}
|
||||
mbstowcs(&wkey, (const char *)&long_key, 2);
|
||||
|
||||
return wkey;
|
||||
}
|
||||
+2
-1
@@ -8,7 +8,8 @@
|
||||
const char *u_utf8_wind_n(const char *s, int n);
|
||||
size_t u_utf8_code_points_count(const char *s);
|
||||
int32_t u_utf8_convert_to_wide(const char *utf8_str, wchar_t **restrict wide_str,
|
||||
size_t *restrict len);
|
||||
size_t *restrict o_len);
|
||||
int32_t u_utf8_curs_printw(WINDOW *win, int32_t *y, int32_t *x, const char* utf8_str, int max_len, bool multiline);
|
||||
wchar_t u_utf8_get_full_letter(int key, WINDOW *win);
|
||||
|
||||
#endif
|
||||
@@ -9,4 +9,12 @@
|
||||
free(arg); \
|
||||
arg = NULL; \
|
||||
}
|
||||
|
||||
#define FILE_CLEAN(file) \
|
||||
if (file != NULL) { \
|
||||
FREE_MLC(file->name); \
|
||||
FREE_MLC(file->path); \
|
||||
FREE_MLC(file); \
|
||||
file = NULL; \
|
||||
}
|
||||
#endif
|
||||
@@ -7,6 +7,8 @@ target_link_libraries(widget
|
||||
bstring
|
||||
widget_core
|
||||
utils
|
||||
common
|
||||
core
|
||||
)
|
||||
|
||||
target_sources(widget
|
||||
|
||||
+95
-3
@@ -1,4 +1,6 @@
|
||||
#include "alert.h"
|
||||
#include "dialogue.h"
|
||||
#include <core/app.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
@@ -15,10 +17,12 @@ void alert_cb(w_cb_args_t *args) {
|
||||
|
||||
void w_alert_init(app_t *_app) { app = _app; }
|
||||
|
||||
void w_alert(const char *message) { w_notification("Alert", dc_alert, message); }
|
||||
void w_alert(const char *message) {
|
||||
w_notification("Alert", dc_alert, message);
|
||||
}
|
||||
|
||||
void w_notification(const char *title, enum w_dialogue_color_scheme color,
|
||||
const char *f_message, ...) {
|
||||
const char *f_message, ...) {
|
||||
if (app == NULL)
|
||||
return;
|
||||
|
||||
@@ -36,7 +40,7 @@ void w_notification(const char *title, enum w_dialogue_color_scheme color,
|
||||
d->w.callback = alert_cb;
|
||||
d->g_content = NULL;
|
||||
d->g_action = w_group_init(&(d->win), &(d->w), actions, &(d->id_map),
|
||||
horizontal, g_action);
|
||||
horizontal, g_action);
|
||||
|
||||
w_dialogue_init_active_id(d);
|
||||
d->color_scheme = color;
|
||||
@@ -58,4 +62,92 @@ void w_notification(const char *title, enum w_dialogue_color_scheme color,
|
||||
if (app->modal.win != NULL) {
|
||||
app->modal.needs_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
int w_bool_callback(app_t *app, w_dialogue_t *dialogue, int32_t key) {
|
||||
w_cb_args_t args;
|
||||
args.app = app;
|
||||
args.active_el = dialogue;
|
||||
args.element = dialogue;
|
||||
int _key = key;
|
||||
args.data = (void *)&_key;
|
||||
w_dialogue_callback_default(&args);
|
||||
if (args.resp_data.code == cbrp_val) {
|
||||
switch (args.resp_data.val.val.num) {
|
||||
case 1:
|
||||
return 1;
|
||||
case 2:
|
||||
return 2;
|
||||
default:
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int w_bool_ask(const char *title, const char *text_y_btn,
|
||||
const char *text_n_btn, bool yes_default, const char *f_message,
|
||||
...) {
|
||||
|
||||
va_list args;
|
||||
va_start(args, f_message);
|
||||
|
||||
if (app == NULL)
|
||||
return -1;
|
||||
|
||||
void *callback = app->active_callback;
|
||||
|
||||
w_group_el_init_t actions[] = {{.type = w_button, .val.num = 1}, /* YES */
|
||||
{.type = w_button, .val.num = 2}, /* NO */
|
||||
{.type = w_end}};
|
||||
|
||||
strcpy(actions[0].label, text_y_btn);
|
||||
strcpy(actions[1].label, text_n_btn);
|
||||
|
||||
w_dialogue_t *d = malloc(sizeof(w_dialogue_t));
|
||||
|
||||
w_dialogue_vinit(d, title, &(app->coordinates), f_message, &args);
|
||||
|
||||
d->w.callback = alert_cb;
|
||||
d->g_content = NULL;
|
||||
d->g_action = w_group_init(&(d->win), &(d->w), actions, &(d->id_map),
|
||||
horizontal, g_action);
|
||||
|
||||
if (yes_default) {
|
||||
d->active_el = d->g_action->elements;
|
||||
} else {
|
||||
d->active_el = d->g_action->elements + 1;
|
||||
}
|
||||
|
||||
w_dialogue_draw(d);
|
||||
wrefresh(d->win);
|
||||
int c = 0;
|
||||
|
||||
bool response = false;
|
||||
|
||||
while ((c = wgetch(app->win))) {
|
||||
int ret = w_bool_callback(app, d, c);
|
||||
switch (ret) {
|
||||
case 1:
|
||||
response = true;
|
||||
goto break_while;
|
||||
case 2:
|
||||
goto break_while;
|
||||
}
|
||||
w_dialogue_draw(d);
|
||||
wrefresh(d->win);
|
||||
}
|
||||
|
||||
break_while:
|
||||
|
||||
w_group_destroy(d->g_action);
|
||||
delwin(d->win);
|
||||
free(d);
|
||||
app->active_callback = callback;
|
||||
app_refresh(app);
|
||||
if (app->modal.win != NULL) {
|
||||
app->modal.needs_update = true;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
+6
-3
@@ -1,12 +1,15 @@
|
||||
#ifndef W_ALERT_H
|
||||
#define W_ALERT_H
|
||||
|
||||
#include "../app.h"
|
||||
#include <common.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
void w_alert_init(app_t *app);
|
||||
void w_alert(const char *message);
|
||||
void w_notification(const char *title, enum w_dialogue_color_scheme color,
|
||||
const char *f_message, ...);
|
||||
|
||||
const char *f_message, ...);
|
||||
int w_bool_ask(const char *title, const char *yes_button,
|
||||
const char *no_button, bool yes_default, const char *f_message, ...);
|
||||
|
||||
#endif
|
||||
+2
-1
@@ -8,7 +8,8 @@
|
||||
|
||||
#include <widget_core.h>
|
||||
#include "button.h"
|
||||
#include "../app.h"
|
||||
|
||||
#include <core/app.h>
|
||||
|
||||
w_button_t *init_button(WINDOW **win, w_t *w_parent, char *label) {
|
||||
w_button_t *btn = malloc(sizeof(w_button_t));
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../app.h"
|
||||
#include <core/app.h>
|
||||
#include "checkbox.h"
|
||||
#include <widget_core.h>
|
||||
|
||||
|
||||
+16
-6
@@ -1,13 +1,17 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <wchar.h>
|
||||
#include <widget.h>
|
||||
#include <widget_core.h>
|
||||
#include <core/app.h>
|
||||
|
||||
static void (*active_callback_backup)(w_cb_args_t *) = NULL;
|
||||
|
||||
@@ -60,7 +64,7 @@ static void decr_active_id(w_dialogue_t *d) {
|
||||
}
|
||||
|
||||
static void case_callback(w_cb_args_t *args, w_cb_args_t *new_args) {
|
||||
w_dialogue_t *d = (void *)args->element;
|
||||
w_dialogue_t *d = args->element;
|
||||
/* run callback function */
|
||||
if (d->active_el->g_type == g_content) {
|
||||
new_args->element = d->g_content;
|
||||
@@ -74,7 +78,7 @@ static void case_callback(w_cb_args_t *args, w_cb_args_t *new_args) {
|
||||
}
|
||||
|
||||
void w_dialogue_callback_default(w_cb_args_t *args) {
|
||||
w_dialogue_t *d = (void *)args->element;
|
||||
w_dialogue_t *d = args->element;
|
||||
int32_t key = *((int32_t *)args->data);
|
||||
w_cb_args_t new_args;
|
||||
memcpy(&new_args, args, sizeof(w_cb_args_t));
|
||||
@@ -237,7 +241,11 @@ int32_t w_dialogue_draw(w_dialogue_t *d) {
|
||||
/* analyze text content */
|
||||
int32_t line_count = 0;
|
||||
int32_t line_max_len = 0;
|
||||
line_max_len = w_get_max_line_len(d->text, &line_count);
|
||||
wchar_t *main_text_wide = malloc(sizeof(wchar_t) * strlen(d->text));
|
||||
size_t mwt_len;
|
||||
u_utf8_convert_to_wide(d->text, &main_text_wide, &mwt_len);
|
||||
line_count = w_text_divide_by_lines(
|
||||
main_text_wide, mwt_len, d->p_coordinates->max_x * 0.77, &line_max_len);
|
||||
y += line_count;
|
||||
y += 2; /* margin for text */
|
||||
|
||||
@@ -246,8 +254,8 @@ int32_t w_dialogue_draw(w_dialogue_t *d) {
|
||||
DETECT_GROUP_SIZE(d->g_action, line_max_len, y, x);
|
||||
x += line_max_len;
|
||||
|
||||
x += 1; /* when uses box */
|
||||
y += 1; /* when uses box */
|
||||
x += 2; /* when uses box */
|
||||
y += 2; /* when uses box */
|
||||
|
||||
d->w.sz.x = x;
|
||||
d->w.sz.y = y;
|
||||
@@ -283,7 +291,8 @@ int32_t w_dialogue_draw(w_dialogue_t *d) {
|
||||
/* text */
|
||||
wattroff(d->win, A_BOLD);
|
||||
/* mvwhline(d->win, d->w.sz.y - 3, 1, 0, d->w.sz.x - 2); */
|
||||
w_print_multiline_text(d->win, d->text, d->w.sz.x, 2, 1, PMT_ALIGN_CENTER);
|
||||
w_print_multiline_text_wide(d->win, main_text_wide, mwt_len, line_max_len,
|
||||
d->w.sz.x, 2, 0, PMT_ALIGN_CENTER);
|
||||
wattroff(d->win, A_REVERSE);
|
||||
|
||||
if (d->g_content != NULL) {
|
||||
@@ -325,6 +334,7 @@ void w_dialogue_destroy(w_dialogue_t *d, void *_app) {
|
||||
d->win = NULL;
|
||||
d->is_initiated = false;
|
||||
d->needs_destroy = false;
|
||||
d->needs_update = false;
|
||||
app->active_callback = (w_cb_t)app->main_ui.cb_b_press;
|
||||
app->query_args->active_dialogue = NULL;
|
||||
u_d_arr_ptr_free(&(d->id_map));
|
||||
|
||||
+2
-19
@@ -4,31 +4,14 @@
|
||||
#ifndef W_DIALOGUE_H
|
||||
#define W_DIALOGUE_H
|
||||
|
||||
#include "../common.h"
|
||||
#include "group.h"
|
||||
#include <common.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stdint.h>
|
||||
#include <utils.h>
|
||||
#include <widget_core.h>
|
||||
|
||||
#define MAX_IDS 64
|
||||
|
||||
enum w_dialogue_color_scheme { dc_normal, dc_alert };
|
||||
|
||||
typedef struct {
|
||||
w_t w;
|
||||
WINDOW *win;
|
||||
w_group_t *g_content;
|
||||
w_group_t *g_action;
|
||||
w_g_el_t *active_el;
|
||||
char text[DIALOGUE_TEXT];
|
||||
bool is_initiated;
|
||||
bool needs_update;
|
||||
bool needs_destroy;
|
||||
coordinates_t *p_coordinates;
|
||||
enum w_dialogue_color_scheme color_scheme;
|
||||
d_array_ptr_t id_map;
|
||||
} w_dialogue_t;
|
||||
|
||||
void w_dialogue_init(w_dialogue_t *d, const char title[], const char text[],
|
||||
coordinates_t *p_coordinates);
|
||||
void w_dialogue_vinit(w_dialogue_t *dialogue, const char title[],
|
||||
|
||||
+1
-22
@@ -7,28 +7,7 @@
|
||||
#include <stdint.h>
|
||||
#include <utils.h>
|
||||
#include <widget_core.h>
|
||||
|
||||
typedef struct group_el_init_t {
|
||||
enum w_type type;
|
||||
bool is_default;
|
||||
bool is_hidden_value;
|
||||
char label[DIALOGUE_TITLE];
|
||||
int32_t length;
|
||||
enum w_g_dir direction;
|
||||
union {
|
||||
int32_t num;
|
||||
} val;
|
||||
struct group_el_init_t *children;
|
||||
} w_group_el_init_t;
|
||||
|
||||
typedef struct group_t {
|
||||
w_t w;
|
||||
w_g_el_t *elements;
|
||||
enum w_g_dir direction;
|
||||
int32_t count;
|
||||
int32_t first_id, last_id;
|
||||
struct group_t *parent_group;
|
||||
} w_group_t;
|
||||
#include <common.h>
|
||||
|
||||
w_group_t *w_group_init(WINDOW **win, w_t *w_parent, w_group_el_init_t *children,
|
||||
d_array_ptr_t *id_map, enum w_g_dir dir,
|
||||
|
||||
+4
-15
@@ -6,7 +6,8 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../app.h"
|
||||
#include <core/app.h>
|
||||
#include <utils.h>
|
||||
#include "input.h"
|
||||
#include <wchar.h>
|
||||
#include <widget_core.h>
|
||||
@@ -16,10 +17,7 @@ bool w_input_default_key_action(w_cb_args_t *args) {
|
||||
w_g_el_t *g_el = args->active_el;
|
||||
w_input_t *input = g_el->element;
|
||||
int32_t start_pos = 0;
|
||||
wchar_t w_key[2];
|
||||
app_t *app = args->app;
|
||||
char long_key[3];
|
||||
int short_val_len = 1;
|
||||
|
||||
switch (key) {
|
||||
case KEY_BACKSPACE:
|
||||
@@ -47,23 +45,14 @@ bool w_input_default_key_action(w_cb_args_t *args) {
|
||||
default:
|
||||
if (input->value_len == input->max_len)
|
||||
break;
|
||||
long_key[0] = key;
|
||||
input->value_len++;
|
||||
if ((key & 0xC0) == 0xC0) {
|
||||
long_key[1] = wgetch(app->win);
|
||||
long_key[2] = '\0';
|
||||
short_val_len++;
|
||||
} else {
|
||||
long_key[1] = '\0';
|
||||
}
|
||||
mbstowcs(w_key, (const char *)&long_key, 2);
|
||||
key = u_utf8_get_full_letter(key, app->win);
|
||||
if (input->cur_pos > 0) {
|
||||
start_pos = input->value_len - input->cur_pos - 1;
|
||||
memmove(input->w_value + start_pos + 1, input->w_value + start_pos,
|
||||
sizeof(wchar_t) * (input->value_len - start_pos));
|
||||
input->w_value[start_pos] = key;
|
||||
} else {
|
||||
input->w_value[input->value_len - 1] = w_key[0];
|
||||
input->w_value[input->value_len - 1] = key;
|
||||
input->w_value[input->value_len] = '\0';
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#ifndef W_INPUT_H
|
||||
#define W_INPUT_H
|
||||
|
||||
#include "../main.h"
|
||||
#include <stdint.h>
|
||||
#include <widget_core.h>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include "progress_bar.h"
|
||||
#include "../app.h"
|
||||
#include <core/app.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
@@ -11,7 +11,7 @@ w_pgb_ui_t *w_pgb_ui_init(WINDOW **win, w_t *w_parent) {
|
||||
WINDOW *win_par = *(w_parent->w_parent->parent_win);
|
||||
w_pgb_ui_t *pb = malloc(sizeof(w_pgb_ui_t));
|
||||
w_init(&pb->w, w_parent, win, "");
|
||||
pb->procent = 0;
|
||||
pb->percentage = 0;
|
||||
pb->text[0] = '\0';
|
||||
pb->w.sz.y = 4;
|
||||
pb->w.sz.x = getmaxx(win_par) / 10 * 9;
|
||||
@@ -25,17 +25,23 @@ void w_pgb_ui_draw(w_pgb_ui_t *pb) {
|
||||
|
||||
wattrset(win, COLOR_PAIR(modal_color_pair));
|
||||
|
||||
/* top line */
|
||||
mvwhline(win, margin_y, margin_x, 0, pb->w.sz.x - 1);
|
||||
|
||||
float procent = pb->procent;
|
||||
procent /= 100;
|
||||
int32_t filled_num = procent * pb->w.sz.x;
|
||||
int center = pb->w.sz.x / 2 - 3;
|
||||
|
||||
mvwprintw(win, margin_y, margin_x + center, "| %d%% |", pb->percentage);
|
||||
|
||||
float percentage = pb->percentage;
|
||||
percentage /= 100;
|
||||
int32_t filled_num = percentage * pb->w.sz.x;
|
||||
if (filled_num > 2) {
|
||||
filled_num -= 2;
|
||||
}
|
||||
|
||||
for (int i = 1; i < 3; i++) {
|
||||
wattrset(win, COLOR_PAIR(1) | A_REVERSE);
|
||||
wattrset(win, COLOR_PAIR(1) | A_REVERSE);
|
||||
|
||||
for (int i = 1; i < 3; i++) { /* two lines */
|
||||
mvwprintw(win, margin_y + i, margin_x + 1, "%*s", filled_num, "");
|
||||
}
|
||||
|
||||
@@ -43,12 +49,12 @@ void w_pgb_ui_draw(w_pgb_ui_t *pb) {
|
||||
|
||||
// bottom line
|
||||
mvwhline(win, margin_y + 3, margin_x, 0, pb->w.sz.x - 1);
|
||||
// left
|
||||
// left corners
|
||||
mvwvline(win, margin_y, margin_x, ACS_ULCORNER, 1);
|
||||
mvwvline(win, margin_y + 1, margin_x, 0, 1);
|
||||
mvwvline(win, margin_y + 2, margin_x, 0, 1);
|
||||
mvwvline(win, margin_y + 3, margin_x, ACS_LLCORNER, 1);
|
||||
// right
|
||||
// right corners
|
||||
mvwvline(win, margin_y, margin_x + pb->w.sz.x - 2, ACS_URCORNER, 1);
|
||||
mvwvline(win, margin_y + 1, margin_x + pb->w.sz.x - 2, 0, 1);
|
||||
mvwvline(win, margin_y + 2, margin_x + pb->w.sz.x - 2, 0, 1);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
typedef struct {
|
||||
w_t w;
|
||||
int32_t procent;
|
||||
int32_t percentage;
|
||||
char text[24];
|
||||
} w_pgb_ui_t;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ file(GLOB M_SOURCES "*.c")
|
||||
target_link_libraries(widget_core
|
||||
PRIVATE
|
||||
utils
|
||||
common
|
||||
)
|
||||
|
||||
target_sources(widget_core
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
so it is separated from the widget directory */
|
||||
|
||||
#include "widget_core.h"
|
||||
#include "../main.h"
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <utils.h>
|
||||
#include <wchar.h>
|
||||
|
||||
extern int32_t m_id;
|
||||
|
||||
@@ -59,6 +60,49 @@ int32_t w_get_max_line_len(const char *text, int32_t *line_count) {
|
||||
}
|
||||
}
|
||||
|
||||
/* returns number of lines */
|
||||
int32_t w_text_divide_by_lines(wchar_t *restrict text, size_t src_len,
|
||||
int max_x, int *max_len) {
|
||||
int cl_pos = 0;
|
||||
int last_space = -1; /* space position in the current line */
|
||||
int lines_num = 1;
|
||||
|
||||
for (int i = 0; text[i] != '\0'; i++, cl_pos++) {
|
||||
switch (text[i]) {
|
||||
case ' ': /* space symbol */
|
||||
last_space = i;
|
||||
break;
|
||||
case '\n':
|
||||
if (cl_pos > *max_len) {
|
||||
*max_len = cl_pos;
|
||||
}
|
||||
last_space = -1;
|
||||
cl_pos = 0;
|
||||
lines_num++;
|
||||
break;
|
||||
default:
|
||||
if (cl_pos > max_x) {
|
||||
if (last_space > 0) {
|
||||
int line_size = last_space - (i - cl_pos);
|
||||
if (line_size > *max_len) {
|
||||
*max_len = line_size;
|
||||
}
|
||||
text[last_space] = '\n';
|
||||
i = last_space;
|
||||
last_space = -1;
|
||||
cl_pos = 0;
|
||||
lines_num++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cl_pos--;
|
||||
if (cl_pos > *max_len) { /* for the last line */
|
||||
*max_len = cl_pos;
|
||||
}
|
||||
return lines_num;
|
||||
}
|
||||
|
||||
/* #define PRINT_TEXT(win, l_buf, text, i, c_line_len, win_width, line_v_pos, \
|
||||
attrs) \
|
||||
strncpy(l_buf, text + i - c_line_len, c_line_len); \
|
||||
@@ -72,7 +116,7 @@ int32_t w_get_max_line_len(const char *text, int32_t *line_count) {
|
||||
}
|
||||
*/
|
||||
|
||||
void PRINT_TEXT(WINDOW *win, char *l_buf, char *text, int32_t i,
|
||||
void line_print(WINDOW *win, char *l_buf, char *text, int32_t i,
|
||||
int32_t c_line_len, int32_t win_width, int32_t line_v_pos,
|
||||
int32_t attrs, int32_t x, int32_t max_line_len) {
|
||||
int32_t utf8len = 0;
|
||||
@@ -90,9 +134,27 @@ void PRINT_TEXT(WINDOW *win, char *l_buf, char *text, int32_t i,
|
||||
}
|
||||
}
|
||||
|
||||
void wline_print(WINDOW *win, wchar_t *w_text, int32_t i, int32_t c_line_len,
|
||||
int32_t win_width, int32_t y, int32_t attrs, int32_t x,
|
||||
int32_t max_line_len) {
|
||||
wchar_t l_buf[DIALOGUE_TEXT];
|
||||
int32_t b_len = c_line_len * sizeof(wchar_t);
|
||||
memcpy(l_buf, w_text + i - c_line_len, b_len);
|
||||
l_buf[c_line_len] = '\0';
|
||||
if ((attrs & PMT_ALIGN_CENTER) == PMT_ALIGN_CENTER) {
|
||||
int32_t _x = x + (win_width - c_line_len) / 2;
|
||||
mvwaddnwstr(win, y, _x, l_buf, c_line_len);
|
||||
} else if (attrs & PMT_POS_CENTER) {
|
||||
int32_t _x = x + (win_width - max_line_len) / 2;
|
||||
mvwaddnwstr(win, y, _x, l_buf, c_line_len);
|
||||
} else {
|
||||
mvwaddnwstr(win, y, x, l_buf, c_line_len);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t w_print_multiline_text(WINDOW *win, const char *_text,
|
||||
const int32_t win_width, const int32_t y,
|
||||
const int32_t x, const int16_t attrs) {
|
||||
const int32_t win_width, const int32_t y,
|
||||
const int32_t x, const int16_t attrs) {
|
||||
|
||||
int16_t line_v_pos = y;
|
||||
int32_t c_line_len = 0;
|
||||
@@ -126,17 +188,64 @@ int32_t w_print_multiline_text(WINDOW *win, const char *_text,
|
||||
|
||||
for (; text[i]; i++, c_line_len++) {
|
||||
if (text[i] == '\n') {
|
||||
PRINT_TEXT(win, l_buf, text, i, c_line_len, win_width, line_v_pos, attrs,
|
||||
line_print(win, l_buf, text, i, c_line_len, win_width, line_v_pos, attrs,
|
||||
x, m_line_len);
|
||||
c_line_len = -1;
|
||||
line_v_pos++;
|
||||
}
|
||||
}
|
||||
|
||||
PRINT_TEXT(win, l_buf, text, i, c_line_len, win_width, line_v_pos, attrs, x,
|
||||
line_print(win, l_buf, text, i, c_line_len, win_width, line_v_pos, attrs, x,
|
||||
m_line_len);
|
||||
|
||||
free(text);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int32_t w_print_multiline_text_wide(WINDOW *win, const wchar_t *_text,
|
||||
size_t text_len, int32_t max_line_size,
|
||||
const int32_t win_width, const int32_t y,
|
||||
const int32_t x, const int16_t attrs) {
|
||||
|
||||
int16_t line_v_pos = y;
|
||||
int32_t c_line_len = 0;
|
||||
int32_t i = 0;
|
||||
int text_bytes_size = sizeof(wchar_t) * (text_len + 1);
|
||||
wchar_t *text = malloc(text_bytes_size);
|
||||
mempcpy(text, _text, text_bytes_size);
|
||||
|
||||
int l_size = 0;
|
||||
for (int m = 0; text[m] != '\0'; m++) {
|
||||
if (text[m] != '\n') {
|
||||
if (l_size >= win_width) {
|
||||
int j = m;
|
||||
while (text[j] != ' ') {
|
||||
j--;
|
||||
}
|
||||
text[j] = '\n';
|
||||
l_size = 0;
|
||||
} else {
|
||||
l_size++;
|
||||
}
|
||||
} else {
|
||||
l_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (; text[i]; i++, c_line_len++) {
|
||||
if (text[i] == '\n') {
|
||||
wline_print(win, text, i, c_line_len, win_width, line_v_pos, attrs, x,
|
||||
max_line_size);
|
||||
c_line_len = -1;
|
||||
line_v_pos++;
|
||||
}
|
||||
}
|
||||
|
||||
wline_print(win, text, i, c_line_len, win_width, line_v_pos, attrs, x,
|
||||
max_line_size);
|
||||
|
||||
free(text);
|
||||
|
||||
return 1;
|
||||
}
|
||||
+12
-101
@@ -4,109 +4,20 @@
|
||||
#ifndef WIDGET_CORE_H
|
||||
#define WIDGET_CORE_H
|
||||
|
||||
#include "../main.h"
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stdint.h>
|
||||
#include <common.h>
|
||||
|
||||
enum w_g_type { g_content, g_action };
|
||||
|
||||
enum w_g_dir {
|
||||
horizontal,
|
||||
vertical,
|
||||
};
|
||||
|
||||
enum w_type {
|
||||
w_end,
|
||||
w_button,
|
||||
w_box,
|
||||
w_group,
|
||||
w_input,
|
||||
w_checkbox,
|
||||
w_progress,
|
||||
};
|
||||
|
||||
enum w_val_type {
|
||||
val_num,
|
||||
val_ptr,
|
||||
val_nul,
|
||||
};
|
||||
|
||||
struct w_val_t {
|
||||
enum w_val_type type;
|
||||
union {
|
||||
void *ptr;
|
||||
int32_t num;
|
||||
} val;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int32_t id;
|
||||
int32_t idx; /* element index in the group */
|
||||
void *element;
|
||||
enum w_type type;
|
||||
enum w_g_type g_type;
|
||||
bool is_default;
|
||||
struct w_val_t val;
|
||||
} w_g_el_t;
|
||||
|
||||
/* widget callback response code */
|
||||
enum w_cbrp_code {
|
||||
cbrc_none, /* default */
|
||||
cbrp_val,
|
||||
cbrp_g_el, /* element of type group_el_t */
|
||||
cbrp_err
|
||||
};
|
||||
|
||||
/* widget callback response data */
|
||||
typedef struct {
|
||||
enum w_cbrp_code code;
|
||||
struct w_val_t val;
|
||||
} w_cbrp_data;
|
||||
|
||||
typedef struct {
|
||||
void *app;
|
||||
void *element;
|
||||
void *data;
|
||||
w_cbrp_data resp_data;
|
||||
void *active_el;
|
||||
} w_cb_args_t;
|
||||
|
||||
typedef void (*w_cb_t)(w_cb_args_t *args);
|
||||
typedef int32_t (*w_cb_press_t)(void *app, void *data);
|
||||
|
||||
typedef struct w_t {
|
||||
int32_t id;
|
||||
char title[DIALOGUE_TITLE];
|
||||
struct {
|
||||
int32_t y;
|
||||
int32_t x;
|
||||
} cur; /* cursor position */
|
||||
struct {
|
||||
int32_t y;
|
||||
int32_t x;
|
||||
} sz; /* size */
|
||||
struct {
|
||||
int32_t y;
|
||||
int32_t x;
|
||||
} m; /* margin (relative to the parent) */
|
||||
struct {
|
||||
int32_t y;
|
||||
int32_t x;
|
||||
} ps; /* position (relative to the window) */
|
||||
WINDOW *const *parent_win;
|
||||
struct w_t *w_parent;
|
||||
w_cb_t callback;
|
||||
} w_t;
|
||||
|
||||
enum w_pmt_attrs {
|
||||
PMT_POS_CENTER = 01,
|
||||
PMT_ALIGN_CENTER = 03,
|
||||
};
|
||||
|
||||
void w_init(w_t *w, w_t *w_parent, WINDOW **win, char *title);
|
||||
int32_t w_get_max_line_len(const char *text, int32_t *line_count);
|
||||
int32_t w_print_multiline_text(WINDOW *win, const char *text,
|
||||
const int32_t win_width, const int32_t y,
|
||||
const int32_t x, const int16_t attrs);
|
||||
void w_init(w_t *w, w_t *w_parent, WINDOW **win, char *title);
|
||||
int32_t w_get_max_line_len(const char *text, int32_t *line_count);
|
||||
int32_t w_print_multiline_text(WINDOW *win, const char *text,
|
||||
const int32_t win_width, const int32_t y,
|
||||
const int32_t x, const int16_t attrs);
|
||||
int32_t w_print_multiline_text_wide(WINDOW *win, const wchar_t *_text,
|
||||
size_t text_len, int32_t max_line_size, const int32_t win_width,
|
||||
const int32_t y, const int32_t x,
|
||||
const int16_t attrs);
|
||||
int32_t w_text_divide_by_lines(wchar_t *restrict text, size_t src_len,
|
||||
int max_x, int *max_len);
|
||||
|
||||
#endif
|
||||
@@ -2,13 +2,13 @@ add_library(windows STATIC)
|
||||
|
||||
file(GLOB M_SOURCES "*.c")
|
||||
|
||||
target_link_directories(windows
|
||||
PRIVATE
|
||||
"../widget/"
|
||||
"../widget_core/"
|
||||
"../lib/"
|
||||
"../utils/"
|
||||
)
|
||||
# target_link_directories(windows
|
||||
# PRIVATE
|
||||
# "../widget/"
|
||||
# "../widget_core/"
|
||||
# "../lib/"
|
||||
# "../utils/"
|
||||
# )
|
||||
|
||||
target_link_libraries(windows
|
||||
PRIVATE
|
||||
@@ -16,6 +16,8 @@ target_link_libraries(windows
|
||||
widget_core
|
||||
bstring
|
||||
utils
|
||||
core
|
||||
common
|
||||
)
|
||||
|
||||
target_sources(windows
|
||||
|
||||
+31
-24
@@ -2,11 +2,10 @@
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include "file_list.h"
|
||||
#include "../app.h"
|
||||
#include "../file_processor.h"
|
||||
#include "../fs.h"
|
||||
#include "../server.h"
|
||||
#include "main_window.h"
|
||||
#include "dlist.h"
|
||||
#include <common.h>
|
||||
#include <core.h>
|
||||
#include <bstrlib.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stdbool.h>
|
||||
@@ -46,7 +45,7 @@ static void w_fl_cb(w_cb_args_t *args) {
|
||||
}
|
||||
server_send_string(app->query_args, "%s %d %d\n", q_prefix,
|
||||
fui->max_lines, 1);
|
||||
w_fl_reset(fui);
|
||||
fl_clear(fui->f_list);
|
||||
} else {
|
||||
bconchar(fui->search_key, key);
|
||||
w_fl_draw(fui);
|
||||
@@ -58,6 +57,8 @@ static void w_fl_cb(w_cb_args_t *args) {
|
||||
case KEY_DOWN:
|
||||
if (fui->current_idx < fui->current_count - 1) {
|
||||
fui->current_idx++;
|
||||
if (fui->f_selected == NULL) fui->f_selected = fui->f_list->start;
|
||||
fui->f_selected = fui->f_selected->next;
|
||||
} else if (fui->current_page < fui->pages) {
|
||||
app->query_args->state = S_FILE_LIST;
|
||||
server_send_string(app->query_args, "%s %d %d\n", q_prefix,
|
||||
@@ -70,6 +71,7 @@ static void w_fl_cb(w_cb_args_t *args) {
|
||||
case KEY_UP:
|
||||
if (fui->current_idx > 0) {
|
||||
fui->current_idx--;
|
||||
fui->f_selected = fui->f_selected->previous;
|
||||
} else if (fui->current_page > 1) {
|
||||
app->query_args->state = S_FILE_LIST;
|
||||
server_send_string(app->query_args, "%s %d %d\n", q_prefix,
|
||||
@@ -111,7 +113,7 @@ static void w_fl_cb(w_cb_args_t *args) {
|
||||
1);
|
||||
w_fl_reset(fui);
|
||||
case '\n':
|
||||
ui_file_select(app->file_args, app->query_args, fui->current_idx + 1);
|
||||
ui_file_select(app->file_args, app->query_args, dlist_get_ptr(fui->f_selected));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -175,7 +177,7 @@ void *w_fl_init(app_t *app) {
|
||||
app->main_ui.b_keys_len = 4;
|
||||
app->main_ui.cb_b_press = (w_cb_press_t)process_user_input;
|
||||
app->main_ui.reset = w_fl_reset_app;
|
||||
app->main_ui.draw = (draw_f_t *) w_fl_draw;
|
||||
app->main_ui.draw = (draw_f_t *)w_fl_draw;
|
||||
app->active_callback = w_fl_cb;
|
||||
fui->w.callback = w_fl_cb;
|
||||
w_init(&(fui->w), NULL, &(app->win), "");
|
||||
@@ -188,11 +190,15 @@ void *w_fl_init(app_t *app) {
|
||||
fui->activate_last = false;
|
||||
fui->active_search = false;
|
||||
fui->search_key = bfromcstrrangealloc(12, 64, "");
|
||||
fui->start = NULL;
|
||||
fui->current = NULL;
|
||||
fui->w.sz.x = getmaxx(app->win);
|
||||
fui->w.sz.y = getmaxy(app->win);
|
||||
|
||||
fui->f_list = dlist_init(NULL, fl_item_t, NULL);
|
||||
fui->f_selected = NULL;
|
||||
if (app->file_args != NULL) {
|
||||
app->file_args->f_list = fui->f_list;
|
||||
}
|
||||
|
||||
/* * INIT UI * */
|
||||
|
||||
/* define the width for each sub window */
|
||||
@@ -210,13 +216,13 @@ void *w_fl_init(app_t *app) {
|
||||
}
|
||||
|
||||
void w_fl_destroy(w_ui_file_list_t **fui) {
|
||||
fl_clear((*fui)->start, (*fui)->current);
|
||||
fl_clear((*fui)->f_list);
|
||||
free(*fui);
|
||||
*fui = NULL;
|
||||
}
|
||||
|
||||
void w_fl_reset(w_ui_file_list_t *fl_ui) {
|
||||
fl_clear(fl_ui->start, fl_ui->current);
|
||||
fl_clear(fl_ui->f_list);
|
||||
fl_ui->current_idx = 0;
|
||||
fl_ui->current_page = 0;
|
||||
fl_ui->pages = 0;
|
||||
@@ -227,8 +233,8 @@ void w_fl_reset(w_ui_file_list_t *fl_ui) {
|
||||
}
|
||||
|
||||
void w_fl_reset_app(void *app) {
|
||||
app_t *_app = (app_t *) app;
|
||||
w_ui_file_list_t *fl_ui = (w_ui_file_list_t *) _app->main_ui.ui;
|
||||
app_t *_app = (app_t *)app;
|
||||
w_ui_file_list_t *fl_ui = (w_ui_file_list_t *)_app->main_ui.ui;
|
||||
w_fl_reset(fl_ui);
|
||||
}
|
||||
|
||||
@@ -242,17 +248,14 @@ void w_fl_draw(w_ui_file_list_t *fui) {
|
||||
sz_x -= 1; /* do not count the borders */
|
||||
p_y = 1;
|
||||
p_x = 1;
|
||||
fl_item_t *el = NULL;
|
||||
fl_item_t *active_el = NULL;
|
||||
|
||||
dlist_node_t *wp_bac = dlist_get_wp(fui->f_list);
|
||||
dlist_set_wp(fui->f_list, fui->f_list->start);
|
||||
fl_item_t *el = dlist_it_next(fui->f_list);
|
||||
fl_item_t *active_el = dlist_get_ptr(fui->f_selected);
|
||||
int32_t cur_el_idx = 0;
|
||||
|
||||
box(fui->win_list, 0, 0);
|
||||
|
||||
if (fui->start != NULL) {
|
||||
el = *(fui->start);
|
||||
active_el = el;
|
||||
}
|
||||
|
||||
if (fui->activate_last) {
|
||||
fui->current_idx = fui->max_lines - 1;
|
||||
fui->activate_last = false;
|
||||
@@ -281,13 +284,15 @@ void w_fl_draw(w_ui_file_list_t *fui) {
|
||||
}
|
||||
p_y++;
|
||||
cur_el_idx++;
|
||||
} while ((el = el->next) != NULL && p_y < sz_y_f);
|
||||
} while ((el = dlist_it_next(fui->f_list)) != NULL && p_y < sz_y_f);
|
||||
|
||||
dlist_set_wp(fui->f_list, wp_bac);
|
||||
|
||||
p_x = 1;
|
||||
int32_t p_len;
|
||||
|
||||
if (!fui->active_search && fui->search_key->slen > 0 &&
|
||||
*(fui->start) == NULL) {
|
||||
fui->f_list->start == NULL) {
|
||||
bstring text = bfromStatic("[No data to show]");
|
||||
for (; p_y < (sz_y_f / 2); p_y++) {
|
||||
mvwprintw(win, p_y, p_x, "%*s", sz_x - 1, "");
|
||||
@@ -314,6 +319,7 @@ void w_fl_draw(w_ui_file_list_t *fui) {
|
||||
curs_set(true);
|
||||
wmove(win, p_y, p_x + p_len);
|
||||
} else {
|
||||
curs_set(false);
|
||||
int32_t l_pad = 0;
|
||||
sprintf(p_info, "P: %d/%d F: %d L: %d%n", fui->current_page, fui->pages,
|
||||
fui->current_count, fui->full_count, &p_len);
|
||||
@@ -350,7 +356,8 @@ void w_fl_draw(w_ui_file_list_t *fui) {
|
||||
mvwprintw(i_win, p_y, p_x, "Name: ");
|
||||
|
||||
p_x = 8; /* 7 (size of "Name: ") + 1 */
|
||||
u_utf8_curs_printw(i_win, &p_y, &p_x, active_el->name, sz_x - p_x, true);
|
||||
u_utf8_curs_printw(i_win, &p_y, &p_x, active_el->name, sz_x - p_x - 2,
|
||||
true);
|
||||
p_x = 1;
|
||||
|
||||
size_to_text(active_el->size, size_text);
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
#ifndef W_FILE_LIST_H
|
||||
#define W_FILE_LIST_H
|
||||
|
||||
#include "../app.h"
|
||||
#include "dlist.h"
|
||||
#include <bstrlib.h>
|
||||
#include <stdint.h>
|
||||
#include <widget_core.h>
|
||||
|
||||
typedef struct {
|
||||
w_t w;
|
||||
fl_item_t **current;
|
||||
fl_item_t **start;
|
||||
dlist_t *f_list;
|
||||
dlist_node_t *f_selected;
|
||||
int32_t current_idx;
|
||||
int32_t current_count;
|
||||
int32_t full_count;
|
||||
|
||||
+190
-134
@@ -15,14 +15,24 @@
|
||||
#include <unistd.h>
|
||||
#include <utils.h>
|
||||
|
||||
#include "../file_processor.h"
|
||||
#include "../fs.h"
|
||||
#include "../server.h"
|
||||
#include <core/app.h>
|
||||
#include <core/file_processor.h>
|
||||
#include <core/fs.h>
|
||||
#include <core/server.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "alert.h"
|
||||
#include "dlist.h"
|
||||
#include "fs_file_list.h"
|
||||
#include "main_window.h"
|
||||
#include "widget_core.h"
|
||||
|
||||
#define fl_add(dlist, el_ptr, prepend) \
|
||||
dlist_add(dlist, el_ptr, w_lfl_item_t, fl_add_cb, prepend)
|
||||
#define fl_add_sort(dlist, el_ptr) \
|
||||
dlist_add_sort(dlist, el_ptr, w_lfl_item_t, fl_add_sort_cb, fl_add_cb)
|
||||
#define fl_clear(f_list) dlist_clear_list(f_list, fl_clear_cb)
|
||||
|
||||
// void w_fl_reset(w_lfl_ui_t *fl_ui);
|
||||
|
||||
static size_t get_file_size(char *path) {
|
||||
@@ -44,124 +54,163 @@ struct fl_args {
|
||||
u_char d_type;
|
||||
};
|
||||
|
||||
static void fl_add(w_lfl_item_t **f_start, w_lfl_item_t **f_current,
|
||||
struct fl_args *fargs) {
|
||||
w_lfl_item_t *c_item = malloc(sizeof(w_lfl_item_t));
|
||||
c_item->name = malloc(strlen(fargs->name) + 1);
|
||||
strcpy(c_item->name, fargs->name);
|
||||
c_item->size = fargs->size;
|
||||
c_item->next = NULL;
|
||||
c_item->d_type = fargs->d_type;
|
||||
if (fargs->path != NULL) {
|
||||
c_item->path = malloc(strlen(fargs->path) + 1);
|
||||
strcpy(c_item->path, fargs->path);
|
||||
static bool fl_add_cb(void *_dst, void *_src) {
|
||||
w_lfl_item_t *dst = _dst;
|
||||
w_lfl_item_t *src = _src;
|
||||
memcpy(dst, src, sizeof(w_lfl_item_t));
|
||||
if (src->name != NULL) {
|
||||
dst->name = malloc(strlen(src->name) + 1);
|
||||
strcpy(dst->name, src->name);
|
||||
}
|
||||
if (*f_start == NULL) {
|
||||
c_item->prev = NULL;
|
||||
*f_start = c_item;
|
||||
*f_current = c_item;
|
||||
|
||||
if (src->w_name != NULL) {
|
||||
dst->w_name = malloc((wcslen(src->w_name) + 1) * sizeof(wchar_t));
|
||||
wcscpy(dst->w_name, src->w_name);
|
||||
}
|
||||
|
||||
if (src->path != NULL) {
|
||||
dst->path = malloc(strlen(src->path) + 1);
|
||||
strcpy(dst->path, src->path);
|
||||
} else {
|
||||
c_item->prev = *f_current;
|
||||
(*f_current)->next = c_item;
|
||||
*f_current = c_item;
|
||||
dst->path = NULL;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void lfl_clear(w_lfl_item_t **start, w_lfl_item_t **arg_current) {
|
||||
if (*start == NULL)
|
||||
return;
|
||||
w_lfl_item_t *next, *current;
|
||||
current = *start;
|
||||
static int _strcmp(wchar_t *a, wchar_t *b) {
|
||||
wchar_t abuf[256];
|
||||
wchar_t bbuf[256];
|
||||
|
||||
do {
|
||||
next = current->next;
|
||||
free(current->name);
|
||||
free(current);
|
||||
} while ((current = next) != NULL);
|
||||
*start = NULL;
|
||||
*arg_current = NULL;
|
||||
int c = 0;
|
||||
|
||||
for (int i = 0; a[i] != '\0'; i++) {
|
||||
abuf[i] = a[i] & ~32; /* case non-sensitive for Latin characters */
|
||||
}
|
||||
|
||||
for (int i = 0; b[i] != '\0'; i++) {
|
||||
bbuf[i] = b[i] & ~32;
|
||||
}
|
||||
return wcscmp(abuf, bbuf);
|
||||
}
|
||||
|
||||
static bool fl_add_sort_cb(void *_a, void *_b) {
|
||||
w_lfl_item_t *a = _a;
|
||||
w_lfl_item_t *b = _b;
|
||||
wchar_t *a_w_name = a->w_name;
|
||||
wchar_t *b_w_name = b->w_name;
|
||||
int a_m = 0, b_m = 0;
|
||||
int res = 0;
|
||||
if (a->d_type == DT_DIR || (a->d_type == DT_LNK && a->w_name[2] == '/'))
|
||||
a_m |= 1;
|
||||
if (b->d_type == DT_DIR || (b->d_type == DT_LNK && b->w_name[2] == '/'))
|
||||
b_m |= 1;
|
||||
|
||||
if (a_m != b_m)
|
||||
return b_m > a_m;
|
||||
|
||||
if (a->d_type == DT_LNK)
|
||||
a_w_name += 2;
|
||||
if (b->d_type == DT_LNK)
|
||||
b_w_name += 2;
|
||||
res = _strcmp(a_w_name, b_w_name);
|
||||
return res > 0;
|
||||
}
|
||||
|
||||
static bool fl_clear_cb(void *el_ptr) {
|
||||
w_lfl_item_t *el = el_ptr;
|
||||
free(el->path);
|
||||
free(el->name);
|
||||
free(el->w_name);
|
||||
free(el);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int get_files_from_fs(w_lfl_ui_t *fui, char *path) {
|
||||
DIR *dir;
|
||||
struct dirent *dent;
|
||||
char name[257];
|
||||
char d_name[257];
|
||||
wchar_t w_d_name[257];
|
||||
char l_path[INBUFSIZE];
|
||||
struct fl_args f_args;
|
||||
w_lfl_item_t f_args;
|
||||
ssize_t r = 0;
|
||||
fui->files_num = 0;
|
||||
|
||||
w_lfl_item_t *d_start = NULL, *d_current = NULL; /* directories */
|
||||
w_lfl_item_t *f_start = NULL, *f_current = NULL; /* files */
|
||||
|
||||
dir = opendir(path);
|
||||
if (!dir)
|
||||
return 1;
|
||||
if (fui->d_path != NULL) {
|
||||
free(fui->d_path);
|
||||
lfl_clear(&(fui->start), &(fui->current));
|
||||
fl_clear(fui->f_list);
|
||||
}
|
||||
fui->d_path = malloc(strlen(path) + 1);
|
||||
strcpy(fui->d_path, path);
|
||||
while ((dent = readdir(dir)) != NULL) {
|
||||
f_args.path = NULL;
|
||||
if ((f_args.d_type = dent->d_type) == DT_DIR) {
|
||||
/* -> DIRECTORIES */
|
||||
if (!strcmp(dent->d_name, "..")) {
|
||||
w_lfl_item_t *t_st = NULL, *t_cur = NULL;
|
||||
sprintf(name, "/%s", dent->d_name);
|
||||
f_args.name = name;
|
||||
wcscpy(w_d_name, L"/..");
|
||||
f_args.name = dent->d_name;
|
||||
f_args.w_name = w_d_name;
|
||||
f_args.size = 0;
|
||||
fl_add(&t_st, &t_cur, &f_args);
|
||||
f_args.d_type = dent->d_type;
|
||||
fl_add(fui->f_list, &f_args, true);
|
||||
fui->files_num++;
|
||||
/* Always put .. at the beginning */
|
||||
t_st->next = d_start;
|
||||
d_start = t_st;
|
||||
if (d_current == NULL)
|
||||
d_current = t_cur;
|
||||
continue;
|
||||
}
|
||||
/* Hide directories with dot-beginnings */
|
||||
if (!strncmp(dent->d_name, ".", 1))
|
||||
continue;
|
||||
sprintf(name, "/%s", dent->d_name);
|
||||
f_args.name = name;
|
||||
fl_add(&d_start, &d_current, &f_args);
|
||||
mbstowcs(w_d_name + 1, dent->d_name, 255);
|
||||
w_d_name[0] = '/';
|
||||
f_args.name = dent->d_name;
|
||||
f_args.w_name = w_d_name;
|
||||
f_args.size = 0;
|
||||
f_args.d_type = dent->d_type;
|
||||
fl_add_sort(fui->f_list, &f_args);
|
||||
fui->files_num++;
|
||||
/* <- DIRECTORIES */
|
||||
} else if (f_args.d_type == DT_LNK) {
|
||||
/* -> LINKS */
|
||||
sprintf(name, "%s/%s", fui->d_path, dent->d_name);
|
||||
r = readlink(name, l_path, INBUFSIZE);
|
||||
sprintf(name, "->/%s", dent->d_name);
|
||||
f_args.name = name;
|
||||
for (int i = 1; l_path[r - 1] == '/'; i++) {
|
||||
l_path[r - 1] = '\0';
|
||||
}
|
||||
wcsncpy(w_d_name, L"->/", 3);
|
||||
mbstowcs(w_d_name + 3, dent->d_name, 253);
|
||||
f_args.name = dent->d_name;
|
||||
f_args.w_name = w_d_name;
|
||||
f_args.path = l_path;
|
||||
f_args.size = 0;
|
||||
fl_add(&d_start, &d_current, &f_args);
|
||||
f_args.d_type = dent->d_type;
|
||||
fl_add_sort(fui->f_list, &f_args);
|
||||
fui->files_num++;
|
||||
/* <- LINKS */
|
||||
} else {
|
||||
/* -> FILES */
|
||||
/* Hide files with dot-beginnings */
|
||||
if (!strncmp(dent->d_name, ".", 1))
|
||||
continue;
|
||||
f_args.name = dent->d_name;
|
||||
mbstowcs(w_d_name, dent->d_name, 256);
|
||||
f_args.w_name = w_d_name;
|
||||
f_args.size = get_file_size(dent->d_name);
|
||||
fl_add(&f_start, &f_current, &f_args);
|
||||
f_args.d_type = dent->d_type;
|
||||
fl_add_sort(fui->f_list, &f_args);
|
||||
fui->files_num++;
|
||||
sprintf(name, "%s", dent->d_name);
|
||||
/* <- FILES */
|
||||
}
|
||||
}
|
||||
if (d_current != NULL)
|
||||
d_current->next = f_start;
|
||||
if (f_start != NULL)
|
||||
f_start->prev = d_current;
|
||||
|
||||
fui->start = d_start;
|
||||
fui->page_start = d_start;
|
||||
fui->current = d_start;
|
||||
fui->cur_page = 1;
|
||||
fui->pages_num = fui->files_num / fui->max_lines;
|
||||
if (fui->files_num % fui->max_lines) {
|
||||
fui->pages_num++;
|
||||
}
|
||||
fui->page_start = fui->f_list->start;
|
||||
fui->fl_selected = fui->f_list->start;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -170,74 +219,75 @@ static void open_selected_item(w_lfl_ui_t *fui, w_cbrp_data *resp_data) {
|
||||
char *selection = NULL;
|
||||
size_t dp_current;
|
||||
char n_path[INBUFSIZE];
|
||||
if (fui->current->d_type == DT_DIR) {
|
||||
if (!strcmp("/..", fui->current->name)) {
|
||||
w_lfl_item_t *selected_item = fui->fl_selected->el_ptr;
|
||||
if (selected_item->d_type == DT_DIR) {
|
||||
if (!strcmp("..", selected_item->name)) {
|
||||
strcpy(n_path, fui->d_path);
|
||||
bash_case = strrchr(n_path, '/');
|
||||
if (bash_case != NULL) {
|
||||
*bash_case = '\0';
|
||||
if (get_files_from_fs(fui, n_path)) {
|
||||
if (fui->d_path_previous != NULL) {
|
||||
if (get_files_from_fs(fui, fui->d_path_previous)) {
|
||||
w_alert("Can't open the folder!");
|
||||
return;
|
||||
}
|
||||
free(fui->d_path_previous);
|
||||
fui->d_path_previous = NULL;
|
||||
} else {
|
||||
bash_case = strrchr(n_path, '/');
|
||||
if (bash_case != NULL) {
|
||||
*bash_case = '\0';
|
||||
if (get_files_from_fs(fui, n_path)) {
|
||||
w_alert("Can't open the folder!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dp_current = strlen(fui->d_path);
|
||||
selection = fui->current->name + 1;
|
||||
selection = selected_item->name + 1;
|
||||
sprintf(n_path, "%s/%s", fui->d_path, selection);
|
||||
if (get_files_from_fs(fui, n_path)) {
|
||||
w_alert("Can't open the folder!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
fui->current = fui->start;
|
||||
fui->current_idx = 0;
|
||||
resp_data->code = cbrc_none;
|
||||
w_lfl_draw(fui);
|
||||
} else if (fui->current->d_type == DT_REG) {
|
||||
} else if (selected_item->d_type == DT_REG) {
|
||||
resp_data->code = cbrp_val;
|
||||
resp_data->val.type = val_num;
|
||||
resp_data->val.val.num = 1;
|
||||
} else if (fui->current->d_type == DT_LNK) {
|
||||
strcpy(n_path, fui->current->path);
|
||||
} else if (selected_item->d_type == DT_LNK) {
|
||||
strcpy(n_path, selected_item->path);
|
||||
fui->d_path_previous = malloc(sizeof(char) * (strlen(fui->d_path) + 1));
|
||||
strcpy(fui->d_path_previous, fui->d_path);
|
||||
if (get_files_from_fs(fui, n_path)) {
|
||||
w_alert("Can't open the link!");
|
||||
return;
|
||||
}
|
||||
fui->current = fui->start;
|
||||
fui->current_idx = 0;
|
||||
resp_data->code = cbrc_none;
|
||||
w_lfl_draw(fui);
|
||||
}
|
||||
}
|
||||
|
||||
/* next page */
|
||||
static void page_next(w_lfl_ui_t *fui) {
|
||||
int count = fui->max_lines;
|
||||
w_lfl_item_t *n_page_item = fui->page_start;
|
||||
if (fui->cur_page < fui->pages_num) {
|
||||
for (; count >= 0; count--) {
|
||||
n_page_item = n_page_item->next;
|
||||
}
|
||||
dlist_node_t *n_page_item =
|
||||
dlist_wind_fwd(fui->f_list, fui->page_start, fui->max_lines);
|
||||
fui->cur_page++;
|
||||
fui->page_start = n_page_item;
|
||||
fui->fl_selected = n_page_item;
|
||||
fui->current_idx = 0;
|
||||
fui->current = n_page_item;
|
||||
}
|
||||
}
|
||||
|
||||
/* previous page */
|
||||
static void page_previous(w_lfl_ui_t *fui) {
|
||||
int count = fui->max_lines;
|
||||
w_lfl_item_t *n_page_item = fui->page_start;
|
||||
dlist_node_t *last_node = fui->page_start->previous;
|
||||
if (fui->cur_page > 1) {
|
||||
for (; count >= 0; count--) {
|
||||
n_page_item = n_page_item->prev;
|
||||
}
|
||||
dlist_node_t *n_page_item =
|
||||
dlist_wind_bwd(fui->f_list, fui->page_start, fui->max_lines);
|
||||
fui->cur_page--;
|
||||
fui->current =
|
||||
fui->page_start->prev; /* the last element of the previous page */
|
||||
fui->page_start = n_page_item;
|
||||
fui->fl_selected = last_node;
|
||||
fui->current_idx = fui->max_lines;
|
||||
}
|
||||
}
|
||||
@@ -247,32 +297,30 @@ static void w_lfl_cb(w_cb_args_t *args) {
|
||||
int32_t key = *((int32_t *)args->data);
|
||||
bool is_cur_p = 0; /* current page */
|
||||
app_t *app = (app_t *)args->app;
|
||||
dlist_node_t *flwp = dlist_get_wp(fui->f_list);
|
||||
dlist_set_wp(fui->f_list, fui->fl_selected);
|
||||
switch (key) {
|
||||
case KEY_DOWN:
|
||||
is_cur_p = (fui->current_idx % fui->max_lines || fui->current_idx == 0);
|
||||
if (fui->current->next != NULL) {
|
||||
if (is_cur_p) {
|
||||
if (fui->current_idx < fui->max_lines) {
|
||||
fui->current = fui->current->next;
|
||||
fui->current_idx++;
|
||||
}
|
||||
} else { /* is_cur_p */
|
||||
page_next(fui);
|
||||
if (is_cur_p) {
|
||||
if (fui->fl_selected->next != NULL) {
|
||||
fui->fl_selected = fui->fl_selected->next;
|
||||
fui->current_idx++;
|
||||
}
|
||||
} else
|
||||
break; /* fui->current->next != NULL */
|
||||
} else { /* is_cur_p */
|
||||
page_next(fui);
|
||||
}
|
||||
w_lfl_draw(fui);
|
||||
break;
|
||||
case KEY_UP:
|
||||
if (fui->current->prev != NULL) {
|
||||
if (fui->current_idx > 0) {
|
||||
fui->current = fui->current->prev;
|
||||
if (fui->current_idx > 0) {
|
||||
if (fui->fl_selected->previous != NULL) {
|
||||
fui->fl_selected = fui->fl_selected->previous;
|
||||
fui->current_idx--;
|
||||
} else {
|
||||
page_previous(fui);
|
||||
}
|
||||
} else
|
||||
break;
|
||||
} else {
|
||||
page_previous(fui);
|
||||
}
|
||||
w_lfl_draw(fui);
|
||||
break;
|
||||
case KEY_NPAGE:
|
||||
@@ -285,24 +333,26 @@ static void w_lfl_cb(w_cb_args_t *args) {
|
||||
open_selected_item(fui, &(args->resp_data));
|
||||
break;
|
||||
}
|
||||
dlist_set_wp(fui->f_list, flwp);
|
||||
}
|
||||
|
||||
w_lfl_ui_t *w_lfl_init(WINDOW **win, w_t *w_parent) {
|
||||
w_lfl_ui_t *fl_ui = malloc(sizeof(w_lfl_ui_t));
|
||||
w_lfl_ui_t *fui = malloc(sizeof(w_lfl_ui_t));
|
||||
WINDOW *win_par = *(w_parent->w_parent->parent_win);
|
||||
w_init(&(fl_ui->w), w_parent, win, "");
|
||||
fl_ui->current_idx = 0;
|
||||
fl_ui->win_info = NULL;
|
||||
fl_ui->d_path = NULL;
|
||||
w_init(&(fui->w), w_parent, win, "");
|
||||
fui->current_idx = 0;
|
||||
fui->win_info = NULL;
|
||||
fui->d_path = NULL;
|
||||
char *path = get_current_dir_name();
|
||||
get_files_from_fs(fl_ui, path);
|
||||
fl_ui->current = fl_ui->start;
|
||||
fl_ui->w.callback = w_lfl_cb;
|
||||
fl_ui->w.sz.x = getmaxx(win_par) / 10 * 8;
|
||||
fl_ui->w.sz.y = getmaxy(win_par) / 10 * 8;
|
||||
fl_ui->max_lines = 0; /* detects on the first draw */
|
||||
fl_ui->cur_page = 1;
|
||||
return fl_ui;
|
||||
get_files_from_fs(fui, path);
|
||||
fui->f_list = dlist_init(NULL, w_lfl_item_t, NULL);
|
||||
fui->w.callback = w_lfl_cb;
|
||||
fui->w.sz.x = getmaxx(win_par) / 10 * 8;
|
||||
fui->w.sz.y = getmaxy(win_par) / 10 * 8;
|
||||
fui->max_lines = 0; /* detects on the first draw */
|
||||
fui->cur_page = 1;
|
||||
fui->d_path_previous = NULL;
|
||||
return fui;
|
||||
}
|
||||
|
||||
static void w_lfl_cb_refresh(void *data) {
|
||||
@@ -347,7 +397,8 @@ static int32_t process_user_input(app_t *app, w_cb_args_t *d_args) {
|
||||
if (d_args->resp_data.code == cbrp_val &&
|
||||
d_args->resp_data.val.type == val_num &&
|
||||
d_args->resp_data.val.val.num == 1) {
|
||||
file_upload_open(fui->d_path, fui->current->name, app->query_args);
|
||||
w_lfl_item_t *file = dlist_get_ptr(fui->fl_selected);
|
||||
file_upload_open(fui->d_path, file->name, app->query_args);
|
||||
app->query_args->state = S_UPLOAD_PARAMS;
|
||||
}
|
||||
return OK;
|
||||
@@ -364,7 +415,7 @@ w_lfl_ui_t *w_lfl_init_win(app_t *app) {
|
||||
app->main_ui.b_keys_len = 2;
|
||||
app->main_ui.cb_b_press = (w_cb_press_t)process_user_input;
|
||||
app->main_ui.reset = w_lfl_reset_app;
|
||||
app->main_ui.draw = (draw_f_t *) w_lfl_draw;
|
||||
app->main_ui.draw = (draw_f_t *)w_lfl_draw;
|
||||
app->active_callback = w_lfl_cb;
|
||||
fui->w.callback = w_lfl_cb;
|
||||
w_init(&(fui->w), NULL, &(app->win), "");
|
||||
@@ -375,6 +426,7 @@ w_lfl_ui_t *w_lfl_init_win(app_t *app) {
|
||||
fui->w.sz.y = getmaxy(app->win);
|
||||
fui->cur_page = 1;
|
||||
fui->w.parent_win = &(app->win);
|
||||
fui->f_list = dlist_init(NULL, w_lfl_item_t, NULL);
|
||||
/* * INIT UI * */
|
||||
|
||||
/* define the width for each sub window */
|
||||
@@ -392,18 +444,18 @@ w_lfl_ui_t *w_lfl_init_win(app_t *app) {
|
||||
/* get files */
|
||||
char *path = get_current_dir_name();
|
||||
get_files_from_fs(fui, path);
|
||||
fui->current = fui->start;
|
||||
fui->d_path_previous = NULL;
|
||||
return fui;
|
||||
}
|
||||
|
||||
void w_lfl_reset(w_lfl_ui_t *fl_ui) {
|
||||
fl_ui->current_idx = 0;
|
||||
lfl_clear(&(fl_ui->start), &(fl_ui->current));
|
||||
void w_lfl_reset(w_lfl_ui_t *fui) {
|
||||
fui->current_idx = 0;
|
||||
fl_clear(fui->f_list);
|
||||
}
|
||||
|
||||
void w_lfl_reset_app(void *app) {
|
||||
app_t *_app = app;
|
||||
w_lfl_ui_t *fl_ui = (w_lfl_ui_t *) _app->main_ui.ui;
|
||||
w_lfl_ui_t *fl_ui = (w_lfl_ui_t *)_app->main_ui.ui;
|
||||
w_lfl_reset(fl_ui);
|
||||
}
|
||||
|
||||
@@ -417,8 +469,9 @@ void w_lfl_draw(w_lfl_ui_t *fui) {
|
||||
|
||||
p_y = 1;
|
||||
p_x = 1;
|
||||
w_lfl_item_t *el = fui->page_start;
|
||||
w_lfl_item_t *active_el = fui->page_start;
|
||||
dlist_set_wp(fui->f_list, fui->page_start);
|
||||
w_lfl_item_t *el = dlist_it_next(fui->f_list);
|
||||
w_lfl_item_t *active_el = dlist_get_ptr(fui->fl_selected);
|
||||
|
||||
box(fui->win_list, 0, 0);
|
||||
box(fui->win_info, 0, 0);
|
||||
@@ -428,21 +481,24 @@ void w_lfl_draw(w_lfl_ui_t *fui) {
|
||||
p_y++;
|
||||
continue;
|
||||
}
|
||||
if (el == fui->current) {
|
||||
if (el == fui->fl_selected->el_ptr) {
|
||||
wattrset(win, A_BOLD | A_REVERSE);
|
||||
active_el = el;
|
||||
}
|
||||
p_x = 1;
|
||||
p_x += u_utf8_curs_printw(win, &p_y, &p_x, el->name, sz_x_f - 1, false);
|
||||
mvwaddnwstr(win, p_y, p_x, el->w_name, sz_x_f - 1);
|
||||
p_x += wcslen(el->w_name);
|
||||
/* p_x += u_utf8_curs_printw(win, &p_y, &p_x, el->name, sz_x_f - 1, false);
|
||||
*/
|
||||
int pad = sz_x_f - p_x;
|
||||
if (pad >= 0) {
|
||||
mvwprintw(win, p_y, p_x, "%*s", pad, "");
|
||||
}
|
||||
if (el == fui->current) {
|
||||
if (el == fui->fl_selected->el_ptr) {
|
||||
wattroff(win, A_BOLD | A_REVERSE);
|
||||
}
|
||||
p_y++;
|
||||
} while ((el = el->next) != NULL && p_y < sz_y_f);
|
||||
} while ((el = dlist_it_next(fui->f_list)) != NULL && p_y < sz_y_f);
|
||||
|
||||
p_x = 1;
|
||||
|
||||
@@ -482,7 +538,7 @@ void w_lfl_draw(w_lfl_ui_t *fui) {
|
||||
|
||||
void w_lfl_destroy(w_lfl_ui_t **fui) {
|
||||
w_lfl_ui_t *f = *fui;
|
||||
lfl_clear(&(f->start), &(f->current));
|
||||
fl_clear(f->f_list);
|
||||
free(*fui);
|
||||
*fui = NULL;
|
||||
}
|
||||
@@ -4,8 +4,9 @@
|
||||
#ifndef W_LFL_H /* LOCAL FILE LIST */
|
||||
#define W_LFL_H
|
||||
|
||||
#include "../app.h"
|
||||
#include "dlist.h"
|
||||
#include <stddef.h>
|
||||
#include <wchar.h>
|
||||
#include <widget_core.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stdint.h>
|
||||
@@ -14,18 +15,13 @@
|
||||
typedef struct fs_fl_item {
|
||||
char *name;
|
||||
char *path;
|
||||
char *d_name;
|
||||
wchar_t *w_name;
|
||||
size_t size;
|
||||
struct fs_fl_item *next;
|
||||
struct fs_fl_item *prev;
|
||||
u_char d_type;
|
||||
} w_lfl_item_t;
|
||||
|
||||
typedef struct {
|
||||
w_t w;
|
||||
w_lfl_item_t *current;
|
||||
w_lfl_item_t *start;
|
||||
w_lfl_item_t *page_start;
|
||||
int32_t current_idx;
|
||||
int32_t max_lines;
|
||||
int32_t cur_page;
|
||||
@@ -34,6 +30,10 @@ typedef struct {
|
||||
WINDOW *win_info;
|
||||
WINDOW *win_list;
|
||||
char *d_path;
|
||||
char *d_path_previous;
|
||||
dlist_t *f_list;
|
||||
dlist_node_t *page_start;
|
||||
dlist_node_t *fl_selected;
|
||||
} w_lfl_ui_t;
|
||||
|
||||
void w_lfl_draw(w_lfl_ui_t *fl_ui);
|
||||
|
||||
@@ -4,11 +4,18 @@
|
||||
#include "main_window.h"
|
||||
#include "file_list.h"
|
||||
#include "fs_file_list.h"
|
||||
#include "text_edit.h"
|
||||
|
||||
#include <core/app.h>
|
||||
#include <widget/dialogue.h>
|
||||
|
||||
void main_window_set(app_t *app, enum main_window_type type) {
|
||||
if (app->main_ui.ui != NULL) {
|
||||
main_window_destroy(app);
|
||||
}
|
||||
if (app->modal.is_initiated) {
|
||||
w_dialogue_destroy(&(app->modal), app);
|
||||
}
|
||||
app->main_ui.type = type;
|
||||
switch (type) {
|
||||
case mw_fl_server:
|
||||
@@ -19,10 +26,12 @@ void main_window_set(app_t *app, enum main_window_type type) {
|
||||
w_lfl_init_win(app);
|
||||
break;
|
||||
case mw_f_desc:
|
||||
w_te_init_win(app);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
app_draw_bars(app);
|
||||
}
|
||||
|
||||
void main_window_draw(app_t *app) {
|
||||
@@ -34,6 +43,7 @@ void main_window_draw(app_t *app) {
|
||||
w_lfl_draw((w_lfl_ui_t *)app->main_ui.ui);
|
||||
break;
|
||||
case mw_f_desc:
|
||||
w_te_draw((w_te_ui_t *)app->main_ui.ui);
|
||||
break;
|
||||
}
|
||||
app_draw_bbar(app);
|
||||
@@ -48,6 +58,7 @@ void main_window_destroy(app_t *app) {
|
||||
w_lfl_destroy((w_lfl_ui_t **)&(app->main_ui.ui));
|
||||
break;
|
||||
case mw_f_desc:
|
||||
w_te_destroy((w_te_ui_t **)&(app->main_ui.ui));
|
||||
break;
|
||||
}
|
||||
MAIN_UI_RESET(app);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#ifndef MAIN_WINDOW_H
|
||||
#define MAIN_WINDOW_H
|
||||
|
||||
#include "../app.h"
|
||||
#include <common.h>
|
||||
|
||||
void main_window_set(app_t *app, enum main_window_type type);
|
||||
void main_window_draw(app_t *app);
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <utils.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include <core/file_processor.h>
|
||||
#include <core/server.h>
|
||||
#include <core/app.h>
|
||||
|
||||
#include "d_array.h"
|
||||
#include "file_list.h"
|
||||
#include "text_edit.h"
|
||||
#include "widget_core.h"
|
||||
#include "main_window.h"
|
||||
|
||||
/* TODO: расширять capacity линии при достижении лимита */
|
||||
|
||||
#define INACTIVE_LINES 5
|
||||
|
||||
static void line_destroy_cb(void *line_arg) {
|
||||
w_te_ui_line_t *line = line_arg;
|
||||
free(line->text);
|
||||
free(line);
|
||||
}
|
||||
|
||||
static void w_te_cb(w_cb_args_t *args) {
|
||||
app_t *app = args->app;
|
||||
int32_t key = *((int32_t *)args->data);
|
||||
w_te_ui_t *fui = app->query_args->main_ui->ui;
|
||||
w_te_ui_line_t *new_line = NULL;
|
||||
|
||||
switch (key) {
|
||||
case KEY_BACKSPACE:
|
||||
case KEY_DL:
|
||||
if (fui->cur_line->len == 0)
|
||||
break;
|
||||
if (fui->cur_line_pos == fui->cur_line->len && fui->cur_line->len > 0) {
|
||||
fui->cur_line->text[--fui->cur_line_pos] = 0;
|
||||
fui->cur_line->len--;
|
||||
} else if (fui->cur_line_idx > 0) {
|
||||
new_line = fui->lines_arr.arr[fui->cur_line_idx - 1];
|
||||
if (fui->cur_line_pos == 0 && fui->cur_line->len > 0) {
|
||||
wcscpy(new_line->text + new_line->len, fui->cur_line->text);
|
||||
fui->cur_line_pos = new_line->len;
|
||||
new_line->len += fui->cur_line->len;
|
||||
} else {
|
||||
fui->cur_line_pos = new_line->len;
|
||||
}
|
||||
|
||||
u_d_arr_ptr_remove_cb(&(fui->lines_arr), fui->cur_line, fui->cur_line_idx,
|
||||
&line_destroy_cb);
|
||||
fui->cur_line = fui->lines_arr.arr[--fui->cur_line_idx];
|
||||
fui->lines_count--;
|
||||
} else {
|
||||
int n = fui->cur_line->len - fui->cur_line_pos + 1;
|
||||
memmove(fui->cur_line->text + fui->cur_line_pos - 1,
|
||||
fui->cur_line->text + fui->cur_line_pos, n * sizeof(wchar_t));
|
||||
fui->cur_line->len--;
|
||||
fui->cur_line_pos--;
|
||||
}
|
||||
fui->sym_count--;
|
||||
break;
|
||||
case KEY_UP:
|
||||
if (fui->cur_line_idx > 0) {
|
||||
fui->cur_line = fui->lines_arr.arr[--fui->cur_line_idx];
|
||||
fui->cur_line_pos = fui->cur_line->len;
|
||||
if ((fui->cur_line_idx > 0) &&
|
||||
(fui->cur_line_idx - fui->lines_top_indent == 0)) {
|
||||
fui->lines_top_indent--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KEY_DOWN:
|
||||
if ((fui->lines_arr.arr[fui->cur_line_idx + 1] != NULL)) {
|
||||
fui->cur_line = fui->lines_arr.arr[++fui->cur_line_idx];
|
||||
fui->cur_line_pos = fui->cur_line->len;
|
||||
if (fui->cur_line_idx >=
|
||||
fui->lines_top_indent + fui->w.sz.y - INACTIVE_LINES) {
|
||||
fui->lines_top_indent++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KEY_LEFT:
|
||||
if (fui->cur_line_pos > 0) {
|
||||
fui->cur_line_pos--;
|
||||
} else if (fui->cur_line_idx > 0) { /* ACT LIKE KEY TOP */
|
||||
fui->cur_line = fui->lines_arr.arr[--fui->cur_line_idx];
|
||||
fui->cur_line_pos = fui->cur_line->len;
|
||||
if ((fui->cur_line_idx > 0) &&
|
||||
(fui->cur_line_idx - fui->lines_top_indent == 0)) {
|
||||
fui->lines_top_indent--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KEY_RIGHT:
|
||||
if (fui->cur_line_pos < fui->cur_line->len) {
|
||||
fui->cur_line_pos++;
|
||||
} else if ((fui->lines_arr.arr[fui->cur_line_idx + 1] !=
|
||||
NULL)) { /* ACT LIKE KEY BOTTOM */
|
||||
fui->cur_line = fui->lines_arr.arr[++fui->cur_line_idx];
|
||||
fui->cur_line_pos = fui->cur_line->len;
|
||||
if (fui->cur_line_idx >=
|
||||
fui->lines_top_indent + fui->w.sz.y - INACTIVE_LINES) {
|
||||
fui->lines_top_indent++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '\n':
|
||||
new_line = malloc(sizeof(w_te_ui_line_t));
|
||||
new_line->text = malloc(sizeof(wchar_t) * INBUFSIZE);
|
||||
new_line->capacity = new_line->len = INBUFSIZE;
|
||||
new_line->text[0] = 0;
|
||||
new_line->len = 0;
|
||||
if (fui->cur_line_pos != 0) {
|
||||
u_d_arr_ptr_add(&(fui->lines_arr), new_line, fui->cur_line_idx + 1);
|
||||
wcscpy(new_line->text, fui->cur_line->text + fui->cur_line_pos);
|
||||
new_line->len = fui->cur_line->len - fui->cur_line_pos;
|
||||
fui->cur_line->text[fui->cur_line_pos] = 0;
|
||||
fui->cur_line->len -= new_line->len;
|
||||
} else {
|
||||
u_d_array_append(&(fui->lines_arr), new_line, sizeof(w_te_ui_line_t));
|
||||
}
|
||||
fui->cur_line_pos = 0;
|
||||
fui->cur_line = new_line;
|
||||
fui->cur_line_idx++;
|
||||
fui->sym_count++;
|
||||
fui->lines_count++;
|
||||
if (fui->cur_line_idx >= fui->w.sz.y - INACTIVE_LINES) {
|
||||
fui->lines_top_indent++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
key = u_utf8_get_full_letter(key, app->win);
|
||||
if (fui->cur_line_pos == fui->cur_line->len) {
|
||||
fui->cur_line->text[fui->cur_line_pos++] = key;
|
||||
fui->cur_line->text[fui->cur_line_pos] = 0;
|
||||
} else {
|
||||
int n = fui->cur_line->len - fui->cur_line_pos + 1;
|
||||
memmove(fui->cur_line->text + fui->cur_line_pos + 1,
|
||||
fui->cur_line->text + fui->cur_line_pos, n * sizeof(wchar_t));
|
||||
fui->cur_line->text[fui->cur_line_pos++] = key;
|
||||
}
|
||||
fui->sym_count++;
|
||||
fui->cur_line->len++;
|
||||
}
|
||||
}
|
||||
|
||||
static void w_te_cb_refresh(void *data) {
|
||||
app_t *app = (app_t *)data;
|
||||
if (app->main_ui.type != mw_f_desc) {
|
||||
return;
|
||||
}
|
||||
w_te_ui_t *fui = app->main_ui.ui;
|
||||
wnoutrefresh(fui->win);
|
||||
}
|
||||
|
||||
static struct action_key action_keys[] = {
|
||||
{.key = "Esc", .title = "Back", .code = '\33'},
|
||||
{.key = "F2", .title = "Save", .code = KEY_F(2)},
|
||||
{.key = "F8", .title = "Cancel", .code = KEY_F(8)},
|
||||
{.key = "F9", .title = "Quit", .code = KEY_F(9)},
|
||||
};
|
||||
|
||||
void callback_after_notification(app_t *app) {
|
||||
clear_file_in_query(app->query_args);
|
||||
int a_len;
|
||||
main_window_set(app, mw_fl_server);
|
||||
w_ui_file_list_t *fui = app->main_ui.ui;
|
||||
server_send_string(app->query_args, "file list %d %d\n%n", fui->max_lines,
|
||||
fui->current_page, &a_len);
|
||||
app->query_args->state = S_FILE_LIST;
|
||||
}
|
||||
|
||||
static int32_t process_user_input(app_t *app, w_cb_args_t *d_args) {
|
||||
int32_t c;
|
||||
w_te_ui_t *fui = (w_te_ui_t *)app->query_args->main_ui->ui;
|
||||
c = wgetch(app->win);
|
||||
int text_pos = 0;
|
||||
w_te_ui_line_t *arr_line = NULL;
|
||||
switch (c) {
|
||||
case KEY_F(9):
|
||||
app_destroy(app, 0);
|
||||
return OK;
|
||||
case KEY_F(2):
|
||||
/* TODO: move to description edit */
|
||||
app->query_args->file->description =
|
||||
malloc(sizeof(wchar_t) * (fui->sym_count + 9));
|
||||
for (int i = 0; i < fui->lines_count; i++) {
|
||||
arr_line = fui->lines_arr.arr[i];
|
||||
text_pos += wcstombs(app->query_args->file->description + text_pos,
|
||||
arr_line->text, arr_line->len);
|
||||
app->query_args->file->description[text_pos++] = '\n';
|
||||
}
|
||||
// app->query_args->file->description[--text_pos] = '\03'; /* ETX symbol */
|
||||
app->query_args->file->description[text_pos] = '\03'; /* ETX symbol */
|
||||
strncpy(app->query_args->file->description + text_pos, ":END:\n",
|
||||
sizeof(":END:\n"));
|
||||
server_send_string(app->query_args, app->query_args->file->description);
|
||||
app->query_args->state = S_WAIT_SERVER;
|
||||
app->callback_after_notification = callback_after_notification;
|
||||
/* Save */
|
||||
break;
|
||||
case KEY_F(8):
|
||||
server_send_string(app->query_args, "[Empty description]\n:END:\n");
|
||||
app->query_args->state = S_WAIT_SERVER;
|
||||
app->callback_after_notification = callback_after_notification;
|
||||
/* Cancel */
|
||||
break;
|
||||
default:
|
||||
d_args->data = (void *)&c;
|
||||
app->active_callback(d_args);
|
||||
break;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
void w_te_reset_app(void *app);
|
||||
|
||||
w_te_ui_t *w_te_init_win(app_t *app) {
|
||||
wchar_t top_text[INBUFSIZE];
|
||||
int top_text_len = 0;
|
||||
w_te_ui_t *fui = malloc(sizeof(w_te_ui_t));
|
||||
app->main_ui.ui = fui;
|
||||
app->main_ui.type = mw_f_desc;
|
||||
app->main_ui.cb_ui_refresh = w_te_cb_refresh;
|
||||
app->main_ui.b_keys = action_keys;
|
||||
app->main_ui.b_keys_len = 4;
|
||||
app->main_ui.cb_b_press = (w_cb_press_t)process_user_input;
|
||||
app->main_ui.reset = w_te_reset_app;
|
||||
app->main_ui.draw = (draw_f_t *)w_te_draw;
|
||||
app->active_callback = w_te_cb;
|
||||
fui->w.callback = w_te_cb;
|
||||
w_init(&(fui->w), NULL, &(app->win), "");
|
||||
fui->file_ptr = app->query_args->file;
|
||||
fui->w.sz.x = getmaxx(app->win);
|
||||
fui->w.sz.y = getmaxy(app->win);
|
||||
fui->w.parent_win = &(app->win);
|
||||
/* * INIT UI * */
|
||||
fui->win = newwin(fui->w.sz.y - 4, fui->w.sz.x - 2, 2, 1);
|
||||
top_text_len =
|
||||
swprintf(top_text, INBUFSIZE, L"Description: %s", fui->file_ptr->name);
|
||||
app->top_text = malloc(sizeof(wchar_t) * (top_text_len + 1));
|
||||
wcsncpy(app->top_text, top_text, top_text_len);
|
||||
app->top_text[top_text_len] = 0;
|
||||
u_d_arr_ptr_init(&(fui->lines_arr), INBUFSIZE);
|
||||
fui->cur_line = malloc(sizeof(w_te_ui_line_t));
|
||||
fui->cur_line->text = malloc(INBUFSIZE * sizeof(wchar_t));
|
||||
fui->cur_line->len = 0;
|
||||
fui->cur_line->capacity = INBUFSIZE;
|
||||
u_d_array_append(&(fui->lines_arr), fui->cur_line, sizeof(w_te_ui_line_t));
|
||||
fui->cur_line_idx = 0;
|
||||
fui->sym_count = 0;
|
||||
fui->lines_count = 1;
|
||||
fui->lines_top_indent = 0;
|
||||
fui->cur_line_pos = 0;
|
||||
return fui;
|
||||
}
|
||||
|
||||
void w_te_reset(w_te_ui_t *fui) {
|
||||
fui->cur_line_pos = 0;
|
||||
fui->sym_count = 0;
|
||||
fui->lines_count = 1;
|
||||
fui->lines_top_indent = 0;
|
||||
u_d_arr_free_cb(&(fui->lines_arr), line_destroy_cb);
|
||||
}
|
||||
|
||||
void w_te_reset_app(void *app) {
|
||||
app_t *_app = app;
|
||||
w_te_ui_t *fl_ui = (w_te_ui_t *)_app->main_ui.ui;
|
||||
w_te_reset(fl_ui);
|
||||
}
|
||||
|
||||
void w_te_draw(w_te_ui_t *fui) {
|
||||
int32_t sz_y, sz_x;
|
||||
int32_t p_y, p_x;
|
||||
WINDOW *win = fui->win;
|
||||
getmaxyx(win, sz_y, sz_x);
|
||||
int32_t sz_y_f = sz_y - 1; // actual size (without box)
|
||||
int32_t sz_x_f = sz_x - 1; // actual size (without box)
|
||||
|
||||
p_y = 0;
|
||||
p_x = 0;
|
||||
|
||||
wattrset(win, COLOR_PAIR(modal_color_pair) | A_BOLD | A_REVERSE);
|
||||
|
||||
int i = 0;
|
||||
for (i = 0; i < fui->w.sz.y - INACTIVE_LINES; i++) {
|
||||
mvwprintw(win, p_y + i, p_x, "%*s", fui->w.sz.x, "");
|
||||
}
|
||||
|
||||
wattrset(win, COLOR_PAIR(modal_color_pair) | A_BOLD);
|
||||
|
||||
int len = 0;
|
||||
mvwprintw(win, p_y + i, p_x, "LC: %d SC: %d L: %d P: %d%n", fui->lines_count,
|
||||
fui->sym_count, fui->cur_line_idx + 1, fui->cur_line_pos + 1, &len);
|
||||
|
||||
mvwprintw(win, p_y + i, p_x + len, "%*s", fui->w.sz.x - len, "");
|
||||
|
||||
wattrset(win, COLOR_PAIR(modal_color_pair) | A_BOLD | A_REVERSE);
|
||||
|
||||
w_te_ui_line_t *line_arr_el = NULL;
|
||||
|
||||
if (fui->lines_arr.length > 0) {
|
||||
for (i = fui->lines_top_indent;
|
||||
i < fui->lines_arr.length &&
|
||||
i < fui->lines_top_indent + fui->w.sz.y - INACTIVE_LINES;
|
||||
i++) {
|
||||
line_arr_el = (w_te_ui_line_t *)fui->lines_arr.arr[i];
|
||||
mvwaddnwstr(win, p_y++, p_x, line_arr_el->text, line_arr_el->len);
|
||||
}
|
||||
}
|
||||
|
||||
p_y--;
|
||||
|
||||
wmove(win, fui->cur_line_idx - fui->lines_top_indent, fui->cur_line_pos);
|
||||
|
||||
p_x = 1;
|
||||
|
||||
wattroff(win, A_BOLD);
|
||||
|
||||
curs_set(true);
|
||||
}
|
||||
|
||||
void w_te_destroy(w_te_ui_t **fui) {
|
||||
w_te_ui_t *_fui = *fui;
|
||||
u_d_arr_free_cb(&(_fui->lines_arr), line_destroy_cb);
|
||||
free(*fui);
|
||||
*fui = NULL;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#ifndef W_TEXT_EDIT_H /* LOCAL FILE LIST */
|
||||
#define W_TEXT_EDIT_H
|
||||
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <wchar.h>
|
||||
#include <widget_core.h>
|
||||
|
||||
typedef struct {
|
||||
int len;
|
||||
wchar_t *text; /* TODO: make dynamic */
|
||||
int capacity;
|
||||
} w_te_ui_line_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
w_t w;
|
||||
WINDOW *win;
|
||||
p_file_t *file_ptr;
|
||||
w_te_ui_line_t *cur_line;
|
||||
int cur_line_idx;
|
||||
int cur_line_pos;
|
||||
d_array_ptr_t lines_arr;
|
||||
int lines_count;
|
||||
int lines_top_indent;
|
||||
int sym_count;
|
||||
} w_te_ui_t;
|
||||
|
||||
void w_te_draw(w_te_ui_t *fl_ui);
|
||||
void w_te_destroy(w_te_ui_t **fui);
|
||||
void w_te_reset(w_te_ui_t *fl_ui);
|
||||
w_te_ui_t *w_te_init(WINDOW **win, w_t *w_parent);
|
||||
w_te_ui_t *w_te_init_win(app_t *app);
|
||||
void u_d_arr_ptr_remove_cb(d_array_ptr_t *arr, void *ptr, int32_t idx, u_d_arr_free_callback *callback);
|
||||
|
||||
#endif
|
||||
+1
-13
@@ -1,13 +1 @@
|
||||
project("My Awesome Tested Project" C)
|
||||
cmake_minimum_required(VERSION 3.0) # Let's tell it a bit about our environment
|
||||
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) # We declare a variable to select what to build
|
||||
set (TARGET_GROUP production CACHE STRING "What Should I Build?") # Modules that exist in both our release and our unit tests, are added now
|
||||
add_subdirectory(src) if(TARGET_GROUP STREQUAL release) # If we had any files that ONLY were compiled into our release, for example startup code,
|
||||
# it's really handy to put it here.
|
||||
elseif(TARGET_GROUP STREQUAL test) # Our tests need to let CTest know we're running tests
|
||||
include(CTest) # It also needs to understand we have code in source and test directories
|
||||
add_subdirectory(unity)
|
||||
add_subdirectory(test)
|
||||
else()
|
||||
message(FATAL_ERROR "I don't know the TARGET_GROUP you gave me!")
|
||||
endif()
|
||||
add_subdirectory(utils)
|
||||
@@ -0,0 +1,13 @@
|
||||
# ---/ process_client_command /--------------------------------------
|
||||
|
||||
add_executable(test_dlist
|
||||
dlist.c
|
||||
)
|
||||
|
||||
target_link_libraries(test_dlist
|
||||
cmocka
|
||||
common
|
||||
utils
|
||||
)
|
||||
|
||||
add_test(NAME utils_dlist COMMAND test_dlist)
|
||||
@@ -0,0 +1,222 @@
|
||||
#include <cmocka.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <utils/dlist.h>
|
||||
|
||||
typedef struct {
|
||||
int num;
|
||||
char *text;
|
||||
char buf[128];
|
||||
} test_t;
|
||||
|
||||
bool dblist_add_cb(void *dst, void *src) {
|
||||
// test_t *dst = _dst;
|
||||
// test_t *src = _src;
|
||||
memcpy(dst, src, sizeof(test_t));
|
||||
return true;
|
||||
}
|
||||
|
||||
#define tlist_add(dlist, item, prepend) \
|
||||
dlist_add(dlist, item, test_t, dblist_add_cb, prepend);
|
||||
|
||||
#define LOREM_IPSUM \
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " \
|
||||
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim " \
|
||||
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " \
|
||||
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate " \
|
||||
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint " \
|
||||
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt " \
|
||||
"mollit anim id est laborum."
|
||||
|
||||
void test__init(void **state) {
|
||||
test_t t = {.buf = "TEST BUF", .num = 123, .text = LOREM_IPSUM};
|
||||
|
||||
dlist_t *dlist = dlist_init(&t, test_t, dblist_add_cb);
|
||||
assert_int_equal(dlist->len, 1);
|
||||
test_t *dt_ptr = dlist->current->el_ptr;
|
||||
assert_int_equal(dt_ptr->num, t.num);
|
||||
assert_string_equal(dt_ptr->buf, t.buf);
|
||||
assert_ptr_equal(dt_ptr->text, LOREM_IPSUM);
|
||||
}
|
||||
|
||||
void test__add_several_times(void **state) {
|
||||
test_t t1 = {.buf = "TEST BUF", .num = 123, .text = LOREM_IPSUM};
|
||||
test_t t2 = {.buf = "TEST 1 BUF", .num = 11, .text = "RAND 1 TEXT"};
|
||||
test_t t3 = {.buf = "TEST 2 BUF", .num = 22, .text = "RAND 2 TEXT"};
|
||||
|
||||
dlist_t *dlist = dlist_init(NULL, test_t, NULL);
|
||||
|
||||
dlist_add(dlist, &t1, test_t, dblist_add_cb, false);
|
||||
dlist_add(dlist, &t2, test_t, dblist_add_cb, false);
|
||||
dlist_add(dlist, &t3, test_t, dblist_add_cb, false);
|
||||
|
||||
assert_int_equal(dlist->len, 3);
|
||||
|
||||
test_t *dl_t1 = dlist->start->el_ptr;
|
||||
test_t *dl_t2 = dlist->start->next->el_ptr;
|
||||
test_t *dl_t3 = dlist->current->el_ptr;
|
||||
|
||||
assert_memory_equal(dl_t1, &t1, sizeof(test_t));
|
||||
assert_memory_equal(dl_t2, &t2, sizeof(test_t));
|
||||
assert_memory_equal(dl_t3, &t3, sizeof(test_t));
|
||||
}
|
||||
|
||||
void test__add_several_times_prepend(void **state) {
|
||||
test_t t1 = {.buf = "TEST BUF", .num = 123, .text = LOREM_IPSUM};
|
||||
test_t t2 = {.buf = "TEST 1 BUF", .num = 11, .text = "RAND 1 TEXT"};
|
||||
test_t t3 = {.buf = "TEST 2 BUF", .num = 22, .text = "RAND 2 TEXT"};
|
||||
|
||||
dlist_t *dlist = dlist_init(NULL, test_t, NULL);
|
||||
|
||||
dlist_add(dlist, &t1, test_t, dblist_add_cb, true);
|
||||
dlist_add(dlist, &t2, test_t, dblist_add_cb, true);
|
||||
dlist_add(dlist, &t3, test_t, dblist_add_cb, true);
|
||||
|
||||
assert_int_equal(dlist->len, 3);
|
||||
|
||||
test_t *dl_t1 = dlist->current->el_ptr;
|
||||
test_t *dl_t2 = dlist->current->previous->el_ptr;
|
||||
test_t *dl_t3 = dlist->start->el_ptr;
|
||||
|
||||
assert_memory_equal(dl_t1, &t1, sizeof(test_t));
|
||||
assert_memory_equal(dl_t2, &t2, sizeof(test_t));
|
||||
assert_memory_equal(dl_t3, &t3, sizeof(test_t));
|
||||
}
|
||||
|
||||
void test__add_and_iterate(void **state) {
|
||||
test_t t1 = {.buf = "TEST BUF", .num = 123, .text = LOREM_IPSUM};
|
||||
test_t t2 = {.buf = "TEST 1 BUF", .num = 11, .text = "RAND 1 TEXT"};
|
||||
test_t t3 = {.buf = "TEST 2 BUF", .num = 22, .text = "RAND 2 TEXT"};
|
||||
|
||||
dlist_t *dlist = dlist_init(NULL, test_t, NULL);
|
||||
|
||||
dlist_add(dlist, &t1, test_t, dblist_add_cb, false);
|
||||
dlist_add(dlist, &t2, test_t, dblist_add_cb, false);
|
||||
dlist_add(dlist, &t3, test_t, dblist_add_cb, false);
|
||||
|
||||
test_t *dl_t3 = dlist_it_prev(dlist);
|
||||
test_t *dl_t2 = dlist_it_prev(dlist);
|
||||
test_t *dl_t1 = dlist_it_prev(dlist);
|
||||
|
||||
assert_memory_equal(dl_t1, &t1, sizeof(test_t));
|
||||
assert_memory_equal(dl_t2, &t2, sizeof(test_t));
|
||||
assert_memory_equal(dl_t3, &t3, sizeof(test_t));
|
||||
}
|
||||
|
||||
bool _sort(void *_a, void *_b) {
|
||||
test_t *a = _a;
|
||||
test_t *b = _b;
|
||||
|
||||
return a->num > b->num;
|
||||
}
|
||||
|
||||
#define T_BUF "TEST BUF "
|
||||
#define T_TEXT "RAND TEXT "
|
||||
#define T_IT_NUM 120
|
||||
|
||||
void test__insert_sort(void **state) {
|
||||
srand(time(NULL));
|
||||
|
||||
test_t t = {.text = T_TEXT};
|
||||
dlist_t *dlist = dlist_init(NULL, test_t, NULL);
|
||||
|
||||
for (int i = 0; i < T_IT_NUM; i++) {
|
||||
sprintf(t.buf, "%s %d", T_BUF, i);
|
||||
t.num = rand();
|
||||
dlist_add_sort(dlist, &t, test_t, _sort, dblist_add_cb);
|
||||
}
|
||||
|
||||
test_t *t1, *t2;
|
||||
|
||||
t2 = dlist_it_prev(dlist);
|
||||
t1 = dlist_it_prev(dlist);
|
||||
|
||||
assert_int_in_range(t2->num, t1->num, INT_MAX);
|
||||
|
||||
for (int i = T_IT_NUM - 2; i > 0; i--) {
|
||||
t2 = t1;
|
||||
t1 = dlist_it_prev(dlist);
|
||||
assert_int_in_range(t2->num, t1->num, INT_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
int delete_times = 0;
|
||||
test_t *b_ptr = NULL;
|
||||
|
||||
bool delete_cb(void *el_ptr) {
|
||||
assert_int_equal(b_ptr, el_ptr);
|
||||
delete_times++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void test__remove_by_ptr(void **state) {
|
||||
test_t ta = {.buf = "TEST A", .num = 123, .text = "TEXT A"};
|
||||
test_t tb = {.buf = "TEST B", .num = 456, .text = "TEXT B"};
|
||||
test_t tc = {.buf = "TEST C", .num = 789, .text = "TEXT C"};
|
||||
dlist_t *dlist = dlist_init(NULL, int, NULL);
|
||||
|
||||
tlist_add(dlist, &ta, false);
|
||||
tlist_add(dlist, &tb, false);
|
||||
tlist_add(dlist, &tc, false);
|
||||
|
||||
b_ptr = dlist->start->next->el_ptr;
|
||||
assert_int_equal(tb.num, 456);
|
||||
|
||||
dlist_remove_by_ptr(dlist, b_ptr, delete_cb);
|
||||
assert_ptr_equal(dlist->start->next, dlist->current);
|
||||
assert_ptr_equal(dlist->current->previous, dlist->start);
|
||||
assert_int_equal(dlist->len, 2);
|
||||
assert_int_equal(delete_times, 1);
|
||||
test_t *a_ptr = dlist->start->el_ptr;
|
||||
test_t *c_ptr = dlist->start->next->el_ptr;
|
||||
assert_int_equal(a_ptr->num, 123);
|
||||
assert_int_equal(c_ptr->num, 789);
|
||||
}
|
||||
|
||||
int clean_db_call_times = 0;
|
||||
|
||||
bool clean_db(void *ptr) {
|
||||
free(ptr);
|
||||
clean_db_call_times++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void test__clear_list(void **state) {
|
||||
test_t ta = {.buf = "TEST A", .num = 123, .text = "TEXT A"};
|
||||
test_t tb = {.buf = "TEST B", .num = 456, .text = "TEXT B"};
|
||||
test_t tc = {.buf = "TEST C", .num = 789, .text = "TEXT C"};
|
||||
|
||||
dlist_t *dlist = dlist_init(NULL, int, NULL);
|
||||
|
||||
tlist_add(dlist, &ta, false);
|
||||
tlist_add(dlist, &tb, false);
|
||||
tlist_add(dlist, &tc, false);
|
||||
|
||||
assert_int_equal(dlist->len, 3);
|
||||
dlist_clear_list(dlist, clean_db);
|
||||
assert_int_equal(dlist->len, 0);
|
||||
assert_ptr_equal(dlist->current, NULL);
|
||||
assert_ptr_equal(dlist->start, NULL);
|
||||
assert_int_equal(clean_db_call_times, 3);
|
||||
clean_db_call_times = 0;
|
||||
}
|
||||
|
||||
int setup(void **state) { return 0; }
|
||||
int tear_down(void **state) { return 0; }
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test__init),
|
||||
cmocka_unit_test(test__add_several_times),
|
||||
cmocka_unit_test(test__add_several_times_prepend),
|
||||
cmocka_unit_test(test__add_and_iterate),
|
||||
cmocka_unit_test(test__insert_sort),
|
||||
cmocka_unit_test(test__remove_by_ptr),
|
||||
cmocka_unit_test(test__clear_list),
|
||||
};
|
||||
|
||||
return cmocka_run_group_tests(tests, setup, tear_down);
|
||||
}
|
||||
Reference in New Issue
Block a user