New version with caching.
Some checks failed
Build / build-macos (push) Waiting to run
Build / build-linux (push) Failing after 23s
Build / build-clang (push) Failing after 1m14s
Tests / test (push) Failing after 1m19s

This commit is contained in:
retoor 2025-12-05 17:08:50 +01:00
parent 1ad8901f3e
commit f5b47eebe4
32 changed files with 986 additions and 2416 deletions

150
Makefile
View File

@ -1,115 +1,103 @@
.PHONY: all clean test build-lib build-tools build-api install help dev coverage memcheck
.PHONY: all clean test install help dev tikker plot process merge index popular
CC := gcc
CFLAGS := -Wall -Wextra -pedantic -std=c11 -O2 -I./src/third_party -I./src/libtikker/include
CFLAGS := -Ofast -Wall -Wextra
DEBUG_FLAGS := -g -O0 -DDEBUG
RELEASE_FLAGS := -O3
COVERAGE_FLAGS := -fprofile-arcs -ftest-coverage
LDFLAGS := -lsqlite3 -lm
LDFLAGS := -lsqlite3 -lpthread
INSTALL_PREFIX ?= /usr/local
BUILD_DIR := ./build
BIN_DIR := $(BUILD_DIR)/bin
LIB_DIR := $(BUILD_DIR)/lib
all: build-lib build-tools build-api
@echo "✓ Complete build finished"
PYTHON := python3
VENV := .venv
VENV_PYTHON := $(VENV)/bin/python
all: tikker
@echo "Build complete"
help:
@echo "Tikker Enterprise Build System"
@echo "Tikker Build System"
@echo ""
@echo "Targets:"
@echo " make all - Build everything (lib, tools, API)"
@echo " make build-lib - Build libtikker static library"
@echo " make build-tools - Build all CLI tools"
@echo " make build-api - Setup Python API environment"
@echo " make test - Run all tests"
@echo " make coverage - Generate code coverage report"
@echo " make memcheck - Run memory leak detection"
@echo " make clean - Remove all build artifacts"
@echo " make install - Install binaries"
@echo " make dev - Build with debug symbols"
@echo "Build Targets:"
@echo " make tikker - Build tikker binary (default)"
@echo " make all - Same as tikker"
@echo " make dev - Build with debug symbols"
@echo " make clean - Remove build artifacts"
@echo " make install - Install binary to $(INSTALL_PREFIX)/bin"
@echo ""
@echo "Environment Variables:"
@echo " CC - C compiler (default: gcc)"
@echo " CFLAGS - Compiler flags"
@echo " INSTALL_PREFIX - Installation directory (default: /usr/local)"
@echo "Python Targets:"
@echo " make ensure_env - Create Python virtual environment"
@echo " make plot - Generate graphs and reports"
@echo " make process - Run processing pipeline"
@echo " make merge - Merge visualization outputs"
@echo " make index - Index words in database"
@echo " make popular - Show popular typed words"
@echo ""
@echo "Tikker Commands:"
@echo " ./tikker - Start keyboard monitoring"
@echo " ./tikker presses_today - Show today's keystrokes"
@echo " ./tikker stats daily - Daily statistics"
@echo " ./tikker stats hourly - Hourly breakdown"
@echo " ./tikker stats weekly - Weekly statistics"
@echo " ./tikker stats weekday - Weekday comparison"
@echo " ./tikker stats top-keys N - Top N keys"
@echo " ./tikker stats top-words N - Top N words"
@echo " ./tikker stats summary - Overall summary"
@echo " ./tikker decode FILE - Decode keystroke log"
@echo " ./tikker help - Show usage"
$(BIN_DIR):
@mkdir -p $(BIN_DIR)
$(LIB_DIR):
@mkdir -p $(LIB_DIR)
tikker: $(BIN_DIR)
@echo "Building tikker..."
@$(CC) $(CFLAGS) tikker.c -o $(BIN_DIR)/tikker $(LDFLAGS)
@cp $(BIN_DIR)/tikker ./tikker
@echo "Built: ./tikker"
build-lib: $(LIB_DIR)
@echo "Building libtikker library..."
@cd src/libtikker && make LIB_DIR=../../$(LIB_DIR) CC="$(CC)" CFLAGS="$(CFLAGS)"
@echo "✓ libtikker built"
dev: CFLAGS := $(DEBUG_FLAGS)
dev: clean tikker
@echo "Debug build complete"
build-tools: build-lib $(BIN_DIR)
@echo "Building CLI tools..."
@cd src/tools/decoder && make BIN_DIR=../../$(BIN_DIR) LIB_DIR=../../$(LIB_DIR) CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)"
@cd src/tools/indexer && make BIN_DIR=../../$(BIN_DIR) LIB_DIR=../../$(LIB_DIR) CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)"
@cd src/tools/aggregator && make BIN_DIR=../../$(BIN_DIR) LIB_DIR=../../$(LIB_DIR) CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)"
@cd src/tools/report_gen && make BIN_DIR=../../$(BIN_DIR) LIB_DIR=../../$(LIB_DIR) CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)"
@echo "✓ All CLI tools built"
build-api:
@echo "Setting up Python API environment..."
@if [ -f src/api/requirements.txt ]; then \
python3 -m pip install --quiet -r src/api/requirements.txt 2>/dev/null || true; \
echo "✓ Python dependencies installed"; \
ensure_env:
@if [ ! -d "$(VENV)" ]; then \
$(PYTHON) -m venv $(VENV); \
$(VENV_PYTHON) -m pip install --upgrade pip; \
$(VENV_PYTHON) -m pip install matplotlib requests fastapi uvicorn openai dataset; \
echo "Virtual environment created at $(VENV)"; \
else \
echo "⚠ No API requirements.txt found"; \
echo "Virtual environment already exists"; \
fi
test: build-lib
@echo "Running tests..."
@cd tests && make CC="$(CC)" CFLAGS="$(CFLAGS)"
@echo "✓ Tests completed"
plot: ensure_env
@PYTHONPATH=/home/retoor/bin $(VENV_PYTHON) plot.py
@$(VENV_PYTHON) merge.py
coverage: clean
@echo "Building with coverage instrumentation..."
@$(MAKE) CFLAGS="$(CFLAGS) $(COVERAGE_FLAGS)" test
@echo "Generating coverage report..."
@gcov src/libtikker/src/*.c 2>/dev/null || true
@lcov --capture --directory . --output-file coverage.info 2>/dev/null || true
@genhtml coverage.info --output-directory coverage_html 2>/dev/null || true
@echo "✓ Coverage report generated in coverage_html/"
process: ensure_env
@PYTHONPATH=/home/retoor/bin $(VENV_PYTHON) process.py
memcheck: build-lib
@echo "Running memory leak detection..."
@valgrind --leak-check=full --show-leak-kinds=all \
./$(BIN_DIR)/tikker-aggregator --help 2>&1 | tail -20
@echo "✓ Memory check completed"
merge: ensure_env
@$(VENV_PYTHON) merge.py
dev: CFLAGS += $(DEBUG_FLAGS)
dev: clean all
@echo "✓ Debug build completed"
index: ensure_env
@$(VENV_PYTHON) tags.py --index
install: all
@echo "Installing to $(INSTALL_PREFIX)..."
popular: ensure_env
@$(VENV_PYTHON) tags.py --popular
install: tikker
@echo "Installing to $(INSTALL_PREFIX)/bin..."
@mkdir -p $(INSTALL_PREFIX)/bin
@mkdir -p $(INSTALL_PREFIX)/lib
@mkdir -p $(INSTALL_PREFIX)/include
@install -m 755 $(BIN_DIR)/tikker-decoder $(INSTALL_PREFIX)/bin/
@install -m 755 $(BIN_DIR)/tikker-indexer $(INSTALL_PREFIX)/bin/
@install -m 755 $(BIN_DIR)/tikker-aggregator $(INSTALL_PREFIX)/bin/
@install -m 755 $(BIN_DIR)/tikker-report $(INSTALL_PREFIX)/bin/
@install -m 644 $(LIB_DIR)/libtikker.a $(INSTALL_PREFIX)/lib/
@install -m 644 src/libtikker/include/*.h $(INSTALL_PREFIX)/include/
@echo "✓ Installation complete"
@install -m 755 $(BIN_DIR)/tikker $(INSTALL_PREFIX)/bin/
@echo "Installed: $(INSTALL_PREFIX)/bin/tikker"
clean:
@echo "Cleaning build artifacts..."
@rm -rf $(BUILD_DIR)
@rm -f tikker
@find . -name "*.o" -delete
@find . -name "*.a" -delete
@find . -name "*.gcov" -delete
@find . -name "*.gcda" -delete
@find . -name "*.gcno" -delete
@find . -name "gmon.out" -delete
@rm -rf coverage.info coverage_html/
@echo "✓ Clean completed"
.PHONY: help $(BIN_DIR) $(LIB_DIR)
@echo "Clean complete"

View File

@ -1,36 +0,0 @@
CC ?= gcc
CFLAGS ?= -Wall -Wextra -pedantic -std=c11 -O2
CFLAGS += -I. -I./include -I../third_party -fPIC
LIB_DIR ?= ../../build/lib
SRC_DIR := src
OBJ_DIR := .obj
LIB_TARGET := $(LIB_DIR)/libtikker.a
SOURCES := $(wildcard $(SRC_DIR)/*.c)
OBJECTS := $(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
.PHONY: all clean
all: $(LIB_TARGET)
$(OBJ_DIR):
@mkdir -p $(OBJ_DIR)
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
@echo "Compiling $<..."
@$(CC) $(CFLAGS) -c $< -o $@
$(LIB_TARGET): $(OBJECTS) | $(LIB_DIR)
@mkdir -p $(LIB_DIR)
@echo "Creating static library $(LIB_TARGET)..."
@ar rcs $@ $(OBJECTS)
@echo "✓ libtikker.a created"
$(LIB_DIR):
@mkdir -p $(LIB_DIR)
clean:
@rm -rf $(OBJ_DIR)
@rm -f $(LIB_TARGET)
@echo "✓ libtikker cleaned"

View File

@ -1,72 +0,0 @@
#ifndef TIKKER_AGGREGATOR_H
#define TIKKER_AGGREGATOR_H
#include <stdint.h>
#include <sqlite3.h>
#include <tikker.h>
typedef struct {
char date[11];
uint64_t total;
} tikker_daily_entry_t;
typedef struct {
char date[11];
uint32_t hour;
uint64_t presses;
} tikker_hourly_entry_t;
typedef struct {
char week[8];
uint64_t total;
} tikker_weekly_entry_t;
typedef enum {
TIKKER_WEEKDAY_SUNDAY = 0,
TIKKER_WEEKDAY_MONDAY = 1,
TIKKER_WEEKDAY_TUESDAY = 2,
TIKKER_WEEKDAY_WEDNESDAY = 3,
TIKKER_WEEKDAY_THURSDAY = 4,
TIKKER_WEEKDAY_FRIDAY = 5,
TIKKER_WEEKDAY_SATURDAY = 6
} tikker_weekday_t;
int tikker_aggregate_daily(sqlite3 *db,
tikker_daily_entry_t **entries,
int *count);
int tikker_aggregate_hourly(sqlite3 *db,
const char *date,
tikker_hourly_entry_t **entries,
int *count);
int tikker_aggregate_weekly(sqlite3 *db,
tikker_weekly_entry_t **entries,
int *count);
int tikker_aggregate_weekday(sqlite3 *db,
tikker_weekday_stat_t **entries,
int *count);
int tikker_get_peak_hour(sqlite3 *db,
const char *date,
uint32_t *hour,
uint64_t *presses);
int tikker_get_peak_day(sqlite3 *db,
char *date,
uint64_t *presses);
int tikker_get_daily_average(sqlite3 *db,
uint64_t *avg_presses,
int *num_days);
uint64_t tikker_calculate_total_presses(sqlite3 *db);
uint64_t tikker_calculate_total_releases(sqlite3 *db);
uint64_t tikker_calculate_total_repeats(sqlite3 *db);
void tikker_free_daily_entries(tikker_daily_entry_t *entries, int count);
void tikker_free_hourly_entries(tikker_hourly_entry_t *entries, int count);
void tikker_free_weekly_entries(tikker_weekly_entry_t *entries, int count);
#endif

View File

@ -1,37 +0,0 @@
#ifndef TIKKER_CONFIG_H
#define TIKKER_CONFIG_H
#define TIKKER_VERSION "2.0.0-enterprise"
#define TIKKER_VERSION_MAJOR 2
#define TIKKER_VERSION_MINOR 0
#define TIKKER_VERSION_PATCH 0
#define TIKKER_DEFAULT_DB_PATH "tikker.db"
#define TIKKER_DEFAULT_LOGS_DIR "logs_plain"
#define TIKKER_DEFAULT_CACHE_DIR "tikker_cache"
#define TIKKER_DEFAULT_TAGS_DB "tags.db"
#define TIKKER_DEFAULT_LOGS_DB "logs.db"
#define TIKKER_TEXT_BUFFER_INITIAL 4096
#define TIKKER_TEXT_BUFFER_MAX (1024 * 1024 * 100)
#define TIKKER_MAX_KEYCODE 256
#define TIKKER_MAX_KEY_NAME 32
#define TIKKER_MAX_DATE_STR 11
#define TIKKER_MAX_PATH 4096
#define TIKKER_WORD_MIN_LENGTH 2
#define TIKKER_WORD_MAX_LENGTH 255
#define TIKKER_TOP_WORDS_DEFAULT 10
#define TIKKER_TOP_KEYS_DEFAULT 10
#define TIKKER_SHIFT_KEYCODE_LSHIFT 42
#define TIKKER_SHIFT_KEYCODE_RSHIFT 54
#define TIKKER_KEY_SPACE 57
#define TIKKER_ENABLE_PROFILING 0
#define TIKKER_ENABLE_DEBUG 0
#endif

View File

@ -1,27 +0,0 @@
#ifndef TIKKER_DATABASE_H
#define TIKKER_DATABASE_H
#include <stddef.h>
#include <sqlite3.h>
typedef struct {
const char *db_path;
sqlite3 *conn;
int flags;
} tikker_db_t;
tikker_db_t* tikker_db_open(const char *path);
void tikker_db_close(tikker_db_t *db);
int tikker_db_init_schema(tikker_db_t *db);
int tikker_db_execute(tikker_db_t *db, const char *sql);
int tikker_db_query(tikker_db_t *db, const char *sql,
int (*callback)(void*, int, char**, char**),
void *arg);
int tikker_db_begin_transaction(tikker_db_t *db);
int tikker_db_commit_transaction(tikker_db_t *db);
int tikker_db_rollback_transaction(tikker_db_t *db);
int tikker_db_vacuum(tikker_db_t *db);
int tikker_db_pragma(tikker_db_t *db, const char *pragma, char *result, size_t result_size);
int tikker_db_integrity_check(tikker_db_t *db);
#endif

View File

@ -1,33 +0,0 @@
#ifndef TIKKER_DECODER_H
#define TIKKER_DECODER_H
#include <stddef.h>
#include <stdint.h>
#define TIKKER_KEY_SPACE 57
#define TIKKER_KEY_ENTER 28
#define TIKKER_KEY_TAB 15
#define TIKKER_KEY_BACKSPACE 14
#define TIKKER_KEY_LSHIFT 42
#define TIKKER_KEY_RSHIFT 54
typedef struct {
char *data;
size_t capacity;
size_t length;
} tikker_text_buffer_t;
tikker_text_buffer_t* tikker_text_buffer_create(size_t initial_capacity);
void tikker_text_buffer_free(tikker_text_buffer_t *buf);
int tikker_text_buffer_append(tikker_text_buffer_t *buf, const char *data, size_t len);
int tikker_text_buffer_append_char(tikker_text_buffer_t *buf, char c);
void tikker_text_buffer_pop(tikker_text_buffer_t *buf);
int tikker_keycode_to_char(uint32_t keycode, int shift_active, char *out_char);
const char* tikker_keycode_to_name(uint32_t keycode);
int tikker_decode_file(const char *input_path, const char *output_path);
int tikker_decode_buffer(const char *input, size_t input_len,
tikker_text_buffer_t *output);
#endif

View File

@ -1,57 +0,0 @@
#ifndef TIKKER_INDEXER_H
#define TIKKER_INDEXER_H
#include <stdint.h>
#include <sqlite3.h>
typedef struct {
const char *word;
uint64_t count;
int rank;
} tikker_word_entry_t;
typedef struct {
sqlite3 *db;
int word_count;
uint64_t total_words;
} tikker_word_index_t;
tikker_word_index_t* tikker_word_index_open(const char *db_path);
void tikker_word_index_close(tikker_word_index_t *index);
int tikker_word_index_reset(tikker_word_index_t *index);
int tikker_index_text_file(const char *file_path,
const char *db_path);
int tikker_index_directory(const char *dir_path,
const char *db_path);
int tikker_word_index_add(tikker_word_index_t *index,
const char *word,
uint64_t count);
int tikker_word_index_commit(tikker_word_index_t *index);
int tikker_word_get_frequency(const char *db_path,
const char *word,
uint64_t *count);
int tikker_word_get_rank(const char *db_path,
const char *word,
int *rank,
uint64_t *count);
int tikker_word_get_top(const char *db_path,
int limit,
tikker_word_entry_t **entries,
int *count);
int tikker_word_get_total_count(const char *db_path,
uint64_t *total);
int tikker_word_get_unique_count(const char *db_path,
int *count);
void tikker_word_entries_free(tikker_word_entry_t *entries, int count);
#endif

View File

@ -1,19 +0,0 @@
#ifndef TIKKER_REPORT_H
#define TIKKER_REPORT_H
#include <stddef.h>
#include <sqlite3.h>
typedef struct {
char *title;
char *data;
size_t data_size;
} tikker_report_t;
int tikker_merge_text_files(const char *input_dir,
const char *pattern,
const char *output_path);
void tikker_report_free(tikker_report_t *report);
#endif

View File

@ -1,138 +0,0 @@
#ifndef TIKKER_H
#define TIKKER_H
#include <stdint.h>
#include <time.h>
#include <sqlite3.h>
#include <stddef.h>
#define TIKKER_SUCCESS 0
#define TIKKER_ERROR_DB -1
#define TIKKER_ERROR_MEMORY -2
#define TIKKER_ERROR_IO -3
#define TIKKER_ERROR_INVALID -4
#define TIKKER_ERROR_NOT_FOUND -5
typedef struct {
sqlite3 *db;
const char *db_path;
uint32_t flags;
} tikker_context_t;
typedef struct {
const char *word;
uint64_t count;
float percentage;
} tikker_word_stat_t;
typedef struct {
uint32_t keycode;
const char *key_name;
uint64_t count;
} tikker_key_stat_t;
typedef struct {
char date[11];
uint64_t total_presses;
uint64_t total_releases;
uint64_t total_repeats;
} tikker_daily_stat_t;
typedef struct {
uint32_t hour;
uint64_t presses;
} tikker_hourly_stat_t;
typedef struct {
char weekday[10];
uint64_t presses;
} tikker_weekday_stat_t;
typedef struct {
double decode_time;
double index_time;
double aggregate_time;
uint64_t records_processed;
} tikker_perf_metrics_t;
tikker_context_t* tikker_open(const char *db_path);
void tikker_close(tikker_context_t *ctx);
int tikker_init_schema(tikker_context_t *ctx);
int tikker_get_version(char *buffer, size_t size);
int tikker_get_daily_stats(tikker_context_t *ctx,
tikker_daily_stat_t **stats,
int *count);
int tikker_get_hourly_stats(tikker_context_t *ctx,
const char *date,
tikker_hourly_stat_t **stats,
int *count);
int tikker_get_weekday_stats(tikker_context_t *ctx,
tikker_weekday_stat_t **stats,
int *count);
int tikker_get_top_words(tikker_context_t *ctx,
int limit,
tikker_word_stat_t **words,
int *count);
int tikker_get_top_keys(tikker_context_t *ctx,
int limit,
tikker_key_stat_t **keys,
int *count);
int tikker_get_date_range(tikker_context_t *ctx,
char *min_date,
char *max_date);
int tikker_get_event_counts(tikker_context_t *ctx,
uint64_t *pressed,
uint64_t *released,
uint64_t *repeated);
int tikker_decode_keylog(const char *input_file,
const char *output_file);
int tikker_decode_keylog_buffer(const char *input,
size_t input_len,
char **output,
size_t *output_len);
int tikker_index_text_file(const char *file_path,
const char *db_path);
int tikker_index_directory(const char *dir_path,
const char *db_path);
int tikker_get_word_frequency(const char *db_path,
const char *word,
uint64_t *count);
int tikker_get_top_words_from_db(const char *db_path,
int limit,
tikker_word_stat_t **words,
int *count);
int tikker_generate_html_report(tikker_context_t *ctx,
const char *output_file,
const char *graph_dir);
int tikker_generate_json_report(tikker_context_t *ctx,
char **json_output);
int tikker_merge_text_files(const char *input_dir,
const char *pattern,
const char *output_path);
int tikker_get_metrics(tikker_perf_metrics_t *metrics);
void tikker_free_words(tikker_word_stat_t *words, int count);
void tikker_free_keys(tikker_key_stat_t *keys, int count);
void tikker_free_daily_stats(tikker_daily_stat_t *stats, int count);
void tikker_free_hourly_stats(tikker_hourly_stat_t *stats, int count);
void tikker_free_weekday_stats(tikker_weekday_stat_t *stats, int count);
void tikker_free_json(char *json);
#endif

View File

@ -1,60 +0,0 @@
#ifndef TIKKER_TYPES_H
#define TIKKER_TYPES_H
#include <stdint.h>
#include <stddef.h>
#include <time.h>
typedef enum {
TIKKER_LOG_DEBUG = 0,
TIKKER_LOG_INFO = 1,
TIKKER_LOG_WARN = 2,
TIKKER_LOG_ERROR = 3,
TIKKER_LOG_FATAL = 4
} tikker_log_level_t;
typedef enum {
TIKKER_EVENT_PRESSED = 0,
TIKKER_EVENT_RELEASED = 1,
TIKKER_EVENT_REPEATED = 2
} tikker_event_type_t;
typedef struct {
uint64_t id;
uint32_t keycode;
tikker_event_type_t event;
const char *name;
time_t timestamp;
char character;
} tikker_kevent_t;
typedef struct {
const char *name;
const char *symbol;
uint32_t code;
} tikker_key_mapping_t;
typedef struct {
int year;
int month;
int day;
int hour;
int minute;
int second;
int weekday;
} tikker_datetime_t;
typedef struct {
char *buffer;
size_t capacity;
size_t length;
} tikker_string_t;
tikker_string_t* tikker_string_create(size_t capacity);
void tikker_string_free(tikker_string_t *str);
int tikker_string_append(tikker_string_t *str, const char *data);
int tikker_string_append_char(tikker_string_t *str, char c);
void tikker_string_clear(tikker_string_t *str);
char* tikker_string_cstr(tikker_string_t *str);
#endif

View File

@ -1,92 +0,0 @@
#include <aggregator.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <sqlite3.h>
int tikker_aggregate_daily(sqlite3 *db,
tikker_daily_entry_t **entries,
int *count) {
if (!db || !entries || !count) return -1;
*entries = NULL;
*count = 0;
return 0;
}
int tikker_aggregate_hourly(sqlite3 *db,
const char *date,
tikker_hourly_entry_t **entries,
int *count) {
if (!db || !date || !entries || !count) return -1;
*entries = NULL;
*count = 0;
return 0;
}
int tikker_aggregate_weekly(sqlite3 *db,
tikker_weekly_entry_t **entries,
int *count) {
if (!db || !entries || !count) return -1;
*entries = NULL;
*count = 0;
return 0;
}
int tikker_aggregate_weekday(sqlite3 *db,
tikker_weekday_stat_t **entries,
int *count) {
if (!db || !entries || !count) return -1;
*entries = NULL;
*count = 0;
return 0;
}
int tikker_get_peak_hour(sqlite3 *db,
const char *date,
uint32_t *hour,
uint64_t *presses) {
if (!db || !date || !hour || !presses) return -1;
return 0;
}
int tikker_get_peak_day(sqlite3 *db,
char *date,
uint64_t *presses) {
if (!db || !date || !presses) return -1;
return 0;
}
int tikker_get_daily_average(sqlite3 *db,
uint64_t *avg_presses,
int *num_days) {
if (!db || !avg_presses || !num_days) return -1;
return 0;
}
uint64_t tikker_calculate_total_presses(sqlite3 *db) {
if (!db) return 0;
return 0;
}
uint64_t tikker_calculate_total_releases(sqlite3 *db) {
if (!db) return 0;
return 0;
}
uint64_t tikker_calculate_total_repeats(sqlite3 *db) {
if (!db) return 0;
return 0;
}
void tikker_free_daily_entries(tikker_daily_entry_t *entries, int count) {
if (entries) free(entries);
}
void tikker_free_hourly_entries(tikker_hourly_entry_t *entries, int count) {
if (entries) free(entries);
}
void tikker_free_weekly_entries(tikker_weekly_entry_t *entries, int count) {
if (entries) free(entries);
}

View File

@ -1,89 +0,0 @@
#include <database.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
tikker_db_t* tikker_db_open(const char *path) {
if (!path) return NULL;
tikker_db_t *db = malloc(sizeof(tikker_db_t));
if (!db) return NULL;
db->db_path = path;
db->flags = 0;
int ret = sqlite3_open(path, &db->conn);
if (ret != SQLITE_OK) { free(db); return NULL; }
return db;
}
void tikker_db_close(tikker_db_t *db) {
if (!db) return;
if (db->conn) sqlite3_close(db->conn);
free(db);
}
int tikker_db_init_schema(tikker_db_t *db) {
if (!db || !db->conn) return -1;
return 0;
}
int tikker_db_execute(tikker_db_t *db, const char *sql) {
if (!db || !db->conn || !sql) return -1;
char *errmsg = NULL;
int ret = sqlite3_exec(db->conn, sql, NULL, NULL, &errmsg);
if (errmsg) sqlite3_free(errmsg);
return ret == SQLITE_OK ? 0 : -1;
}
int tikker_db_query(tikker_db_t *db, const char *sql,
int (*callback)(void*, int, char**, char**),
void *arg) {
if (!db || !db->conn || !sql) return -1;
char *errmsg = NULL;
int ret = sqlite3_exec(db->conn, sql, callback, arg, &errmsg);
if (errmsg) sqlite3_free(errmsg);
return ret == SQLITE_OK ? 0 : -1;
}
int tikker_db_begin_transaction(tikker_db_t *db) {
if (!db || !db->conn) return -1;
return tikker_db_execute(db, "BEGIN TRANSACTION;");
}
int tikker_db_commit_transaction(tikker_db_t *db) {
if (!db || !db->conn) return -1;
return tikker_db_execute(db, "COMMIT;");
}
int tikker_db_rollback_transaction(tikker_db_t *db) {
if (!db || !db->conn) return -1;
return tikker_db_execute(db, "ROLLBACK;");
}
int tikker_db_vacuum(tikker_db_t *db) {
if (!db || !db->conn) return -1;
return tikker_db_execute(db, "VACUUM;");
}
int tikker_db_pragma(tikker_db_t *db, const char *pragma, char *result, size_t result_size) {
if (!db || !db->conn || !pragma || !result) return -1;
char sql[512];
snprintf(sql, sizeof(sql), "PRAGMA %s", pragma);
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db->conn, sql, -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
const unsigned char *text = sqlite3_column_text(stmt, 0);
if (text) {
snprintf(result, result_size, "%s", (const char *)text);
}
}
sqlite3_finalize(stmt);
return 0;
}
return -1;
}
int tikker_db_integrity_check(tikker_db_t *db) {
if (!db || !db->conn) return -1;
return tikker_db_execute(db, "PRAGMA integrity_check;");
}

View File

@ -1,201 +0,0 @@
#include <decoder.h>
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
static const char *keycode_names[] = {
"NONE", "ESC", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=",
"BACKSPACE", "TAB", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P",
"[", "]", "ENTER", "L_CTRL", "A", "S", "D", "F", "G", "H", "J", "K",
"L", ";", "'", "`", "L_SHIFT", "\\", "Z", "X", "C", "V", "B", "N", "M",
",", ".", "/", "R_SHIFT", "*", "L_ALT", "SPACE", "CAPSLOCK"
};
tikker_text_buffer_t* tikker_text_buffer_create(size_t initial_capacity) {
tikker_text_buffer_t *buf = malloc(sizeof(tikker_text_buffer_t));
if (!buf) return NULL;
buf->capacity = initial_capacity ? initial_capacity : 4096;
buf->data = malloc(buf->capacity);
if (!buf->data) { free(buf); return NULL; }
buf->length = 0;
return buf;
}
void tikker_text_buffer_free(tikker_text_buffer_t *buf) {
if (!buf) return;
if (buf->data) free(buf->data);
free(buf);
}
int tikker_text_buffer_append(tikker_text_buffer_t *buf, const char *data, size_t len) {
if (!buf || !data) return -1;
if (buf->length + len >= buf->capacity) {
size_t new_capacity = buf->capacity * 2;
while (new_capacity < buf->length + len + 1) new_capacity *= 2;
char *new_data = realloc(buf->data, new_capacity);
if (!new_data) return -1;
buf->data = new_data;
buf->capacity = new_capacity;
}
memcpy(buf->data + buf->length, data, len);
buf->length += len;
buf->data[buf->length] = '\0';
return 0;
}
int tikker_text_buffer_append_char(tikker_text_buffer_t *buf, char c) {
return tikker_text_buffer_append(buf, &c, 1);
}
void tikker_text_buffer_pop(tikker_text_buffer_t *buf) {
if (!buf || buf->length == 0) return;
buf->length--;
buf->data[buf->length] = '\0';
}
int tikker_keycode_to_char(uint32_t keycode, int shift_active, char *out_char) {
if (!out_char) return -1;
if (keycode >= 2 && keycode <= 11) {
char base = '0' + (keycode - 2);
if (shift_active) {
const char *shifted[] = {"!", "@", "#", "$", "%", "^", "&", "*", "(", ")"};
*out_char = shifted[keycode - 2][0];
} else {
*out_char = base;
}
return 0;
}
if (keycode >= 16 && keycode <= 25) {
*out_char = 'a' + (keycode - 16);
if (shift_active) *out_char = (*out_char) - 32;
return 0;
}
if (keycode == TIKKER_KEY_SPACE) { *out_char = ' '; return 0; }
*out_char = '?';
return 1;
}
const char* tikker_keycode_to_name(uint32_t keycode) {
if (keycode < sizeof(keycode_names) / sizeof(keycode_names[0])) return keycode_names[keycode];
return "UNKNOWN";
}
static const char* shift_number_map[] = {
"!", "@", "#", "$", "%", "^", "&", "*", "(", ")"
};
int tikker_decode_buffer(const char *input, size_t input_len,
tikker_text_buffer_t *output) {
if (!input || !output) return -1;
int shift_active = 0;
size_t i = 0;
while (i < input_len) {
if (input[i] == '[') {
size_t j = i + 1;
while (j < input_len && input[j] != ']') j++;
if (j >= input_len) return -1;
size_t token_len = j - i - 1;
char token[256];
if (token_len >= sizeof(token)) return -1;
memcpy(token, input + i + 1, token_len);
token[token_len] = '\0';
if (strcmp(token, "LEFT_SHIFT") == 0 || strcmp(token, "R_SHIFT") == 0) {
shift_active = 1;
} else if (strcmp(token, "BACKSPACE") == 0) {
tikker_text_buffer_pop(output);
} else if (strcmp(token, "TAB") == 0) {
tikker_text_buffer_append_char(output, '\t');
} else if (strcmp(token, "ENTER") == 0) {
tikker_text_buffer_append_char(output, '\n');
} else if (strcmp(token, "UP") == 0 || strcmp(token, "DOWN") == 0 ||
strcmp(token, "LEFT") == 0 || strcmp(token, "RIGHT") == 0) {
} else if (token_len == 1) {
char c = token[0];
if (shift_active) {
if (c >= 'a' && c <= 'z') {
c = c - 32;
} else if (c >= '0' && c <= '9') {
c = shift_number_map[c - '0'][0];
}
shift_active = 0;
} else {
if (c >= 'A' && c <= 'Z') {
c = c + 32;
}
}
tikker_text_buffer_append_char(output, c);
}
i = j + 1;
} else if (input[i] == ' ' || input[i] == '\t' || input[i] == '\n') {
i++;
} else {
i++;
}
}
return 0;
}
int tikker_decode_file(const char *input_path, const char *output_path) {
if (!input_path || !output_path) return -1;
FILE *input_file = fopen(input_path, "r");
if (!input_file) return -1;
fseek(input_file, 0, SEEK_END);
long file_size = ftell(input_file);
fseek(input_file, 0, SEEK_SET);
if (file_size <= 0) {
fclose(input_file);
return -1;
}
char *buffer = malloc(file_size);
if (!buffer) {
fclose(input_file);
return -1;
}
size_t read_bytes = fread(buffer, 1, file_size, input_file);
fclose(input_file);
if (read_bytes != (size_t)file_size) {
free(buffer);
return -1;
}
tikker_text_buffer_t *output_buf = tikker_text_buffer_create(file_size);
if (!output_buf) {
free(buffer);
return -1;
}
int ret = tikker_decode_buffer(buffer, file_size, output_buf);
free(buffer);
if (ret != 0) {
tikker_text_buffer_free(output_buf);
return -1;
}
FILE *output_file = fopen(output_path, "w");
if (!output_file) {
tikker_text_buffer_free(output_buf);
return -1;
}
fwrite(output_buf->data, 1, output_buf->length, output_file);
fclose(output_file);
tikker_text_buffer_free(output_buf);
return 0;
}

View File

@ -1,329 +0,0 @@
#define _DEFAULT_SOURCE
#include <indexer.h>
#include <database.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <dirent.h>
tikker_word_index_t* tikker_word_index_open(const char *db_path) {
if (!db_path) return NULL;
tikker_word_index_t *index = malloc(sizeof(tikker_word_index_t));
if (!index) return NULL;
int ret = sqlite3_open(db_path, &index->db);
if (ret != SQLITE_OK) { free(index); return NULL; }
index->word_count = 0;
index->total_words = 0;
return index;
}
void tikker_word_index_close(tikker_word_index_t *index) {
if (!index) return;
if (index->db) sqlite3_close(index->db);
free(index);
}
static int is_valid_word_char(char c) {
return isalnum(c) || c == '_';
}
int tikker_word_index_reset(tikker_word_index_t *index) {
if (!index || !index->db) return -1;
sqlite3_exec(index->db, "DROP TABLE IF EXISTS words", NULL, NULL, NULL);
const char *sql = "CREATE TABLE IF NOT EXISTS words ("
"word TEXT NOT NULL PRIMARY KEY,"
"count INTEGER NOT NULL)";
char *errmsg = NULL;
int ret = sqlite3_exec(index->db, sql, NULL, NULL, &errmsg);
if (errmsg) sqlite3_free(errmsg);
if (ret == SQLITE_OK) {
index->word_count = 0;
index->total_words = 0;
return 0;
}
return -1;
}
int tikker_index_text_file(const char *file_path, const char *db_path) {
if (!file_path || !db_path) return -1;
FILE *f = fopen(file_path, "r");
if (!f) return -1;
tikker_word_index_t *index = tikker_word_index_open(db_path);
if (!index) {
fclose(f);
return -1;
}
char word[256];
int word_len = 0;
int c;
while ((c = fgetc(f)) != EOF) {
if (is_valid_word_char(c)) {
if (word_len < (int)sizeof(word) - 1) {
word[word_len++] = tolower(c);
}
} else {
if (word_len > 0) {
word[word_len] = '\0';
tikker_word_index_add(index, word, 1);
word_len = 0;
}
}
}
if (word_len > 0) {
word[word_len] = '\0';
tikker_word_index_add(index, word, 1);
}
fclose(f);
tikker_word_index_commit(index);
tikker_word_index_close(index);
return 0;
}
int tikker_index_directory(const char *dir_path, const char *db_path) {
if (!dir_path || !db_path) return -1;
DIR *dir = opendir(dir_path);
if (!dir) return -1;
tikker_word_index_t *index = tikker_word_index_open(db_path);
if (!index) {
closedir(dir);
return -1;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_REG && strstr(entry->d_name, ".txt")) {
char file_path[1024];
snprintf(file_path, sizeof(file_path), "%s/%s", dir_path, entry->d_name);
FILE *f = fopen(file_path, "r");
if (f) {
char word[256];
int word_len = 0;
int c;
while ((c = fgetc(f)) != EOF) {
if (is_valid_word_char(c)) {
if (word_len < (int)sizeof(word) - 1) {
word[word_len++] = tolower(c);
}
} else {
if (word_len >= 2) {
word[word_len] = '\0';
tikker_word_index_add(index, word, 1);
}
word_len = 0;
}
}
if (word_len >= 2) {
word[word_len] = '\0';
tikker_word_index_add(index, word, 1);
}
fclose(f);
}
}
}
closedir(dir);
tikker_word_index_commit(index);
tikker_word_index_close(index);
return 0;
}
int tikker_word_index_add(tikker_word_index_t *index, const char *word, uint64_t count) {
if (!index || !index->db || !word) return -1;
sqlite3_stmt *stmt;
const char *sql = "INSERT OR IGNORE INTO words (word, count) VALUES (?, 0); "
"UPDATE words SET count = count + ? WHERE word = ?";
if (sqlite3_prepare_v2(index->db, sql, -1, &stmt, NULL) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, word, -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 2, count);
sqlite3_bind_text(stmt, 3, word, -1, SQLITE_STATIC);
if (sqlite3_step(stmt) == SQLITE_DONE) {
index->word_count++;
index->total_words += count;
}
sqlite3_finalize(stmt);
return 0;
}
return -1;
}
int tikker_word_index_commit(tikker_word_index_t *index) {
if (!index || !index->db) return -1;
char *errmsg = NULL;
int ret = sqlite3_exec(index->db, "COMMIT", NULL, NULL, &errmsg);
if (errmsg) sqlite3_free(errmsg);
return ret == SQLITE_OK ? 0 : -1;
}
int tikker_word_get_frequency(const char *db_path, const char *word, uint64_t *count) {
if (!db_path || !word || !count) return -1;
sqlite3 *db;
if (sqlite3_open(db_path, &db) != SQLITE_OK) return -1;
sqlite3_stmt *stmt;
const char *sql = "SELECT count FROM words WHERE word = ?";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, word, -1, SQLITE_STATIC);
if (sqlite3_step(stmt) == SQLITE_ROW) {
*count = sqlite3_column_int64(stmt, 0);
sqlite3_finalize(stmt);
sqlite3_close(db);
return 0;
}
sqlite3_finalize(stmt);
}
*count = 0;
sqlite3_close(db);
return -1;
}
int tikker_word_get_rank(const char *db_path, const char *word, int *rank, uint64_t *count) {
if (!db_path || !word || !rank || !count) return -1;
sqlite3 *db;
if (sqlite3_open(db_path, &db) != SQLITE_OK) return -1;
sqlite3_stmt *stmt;
const char *sql = "SELECT COUNT(*) + 1 FROM words WHERE count > (SELECT count FROM words WHERE word = ?)";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, word, -1, SQLITE_STATIC);
if (sqlite3_step(stmt) == SQLITE_ROW) {
*rank = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
tikker_word_get_frequency(db_path, word, count);
sqlite3_close(db);
return 0;
}
sqlite3_finalize(stmt);
}
sqlite3_close(db);
return -1;
}
int tikker_word_get_top(const char *db_path, int limit, tikker_word_entry_t **entries, int *count) {
if (!db_path || limit <= 0 || !entries || !count) return -1;
sqlite3 *db;
if (sqlite3_open(db_path, &db) != SQLITE_OK) return -1;
sqlite3_stmt *stmt;
const char *sql = "SELECT word, count FROM words ORDER BY count DESC LIMIT ?";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
sqlite3_close(db);
return -1;
}
sqlite3_bind_int(stmt, 1, limit);
int result_count = 0;
tikker_word_entry_t *result = malloc(limit * sizeof(tikker_word_entry_t));
if (!result) {
sqlite3_finalize(stmt);
sqlite3_close(db);
return -1;
}
while (sqlite3_step(stmt) == SQLITE_ROW && result_count < limit) {
const char *word_str = (const char *)sqlite3_column_text(stmt, 0);
uint64_t word_count = sqlite3_column_int64(stmt, 1);
result[result_count].word = strdup(word_str);
result[result_count].count = word_count;
result[result_count].rank = result_count + 1;
result_count++;
}
sqlite3_finalize(stmt);
sqlite3_close(db);
*entries = result;
*count = result_count;
return 0;
}
int tikker_word_get_total_count(const char *db_path, uint64_t *total) {
if (!db_path || !total) return -1;
sqlite3 *db;
if (sqlite3_open(db_path, &db) != SQLITE_OK) return -1;
sqlite3_stmt *stmt;
const char *sql = "SELECT SUM(count) FROM words";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
*total = sqlite3_column_int64(stmt, 0);
sqlite3_finalize(stmt);
sqlite3_close(db);
return 0;
}
sqlite3_finalize(stmt);
}
*total = 0;
sqlite3_close(db);
return -1;
}
int tikker_word_get_unique_count(const char *db_path, int *count) {
if (!db_path || !count) return -1;
sqlite3 *db;
if (sqlite3_open(db_path, &db) != SQLITE_OK) return -1;
sqlite3_stmt *stmt;
const char *sql = "SELECT COUNT(*) FROM words";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
*count = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
sqlite3_close(db);
return 0;
}
sqlite3_finalize(stmt);
}
*count = 0;
sqlite3_close(db);
return -1;
}
void tikker_word_entries_free(tikker_word_entry_t *entries, int count) {
if (entries) {
for (int i = 0; i < count; i++) {
if (entries[i].word) free((char *)entries[i].word);
}
free(entries);
}
}

View File

@ -1,143 +0,0 @@
#define _DEFAULT_SOURCE
#include <report.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <dirent.h>
int tikker_merge_text_files(const char *input_dir, const char *pattern, const char *output_path) {
if (!input_dir || !output_path) return -1;
DIR *dir = opendir(input_dir);
if (!dir) return -1;
FILE *output_file = fopen(output_path, "w");
if (!output_file) {
closedir(dir);
return -1;
}
struct dirent *entry;
int first = 1;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_REG && strstr(entry->d_name, ".txt")) {
if (strcmp(entry->d_name, "merged.txt") == 0) continue;
char file_path[1024];
snprintf(file_path, sizeof(file_path), "%s/%s", input_dir, entry->d_name);
FILE *input_file = fopen(file_path, "r");
if (input_file) {
if (!first) {
fprintf(output_file, "\n\n");
}
first = 0;
char buffer[4096];
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), input_file)) > 0) {
fwrite(buffer, 1, bytes_read, output_file);
}
fclose(input_file);
}
}
}
closedir(dir);
fclose(output_file);
return 0;
}
static int count_token_in_file(const char *file_path, const char *token) {
FILE *f = fopen(file_path, "r");
if (!f) return 0;
int count = 0;
char buffer[4096];
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), f)) > 0) {
for (size_t i = 0; i < bytes_read; ) {
if (buffer[i] == '[') {
size_t j = i + 1;
while (j < bytes_read && buffer[j] != ']') j++;
if (j < bytes_read && strcmp(token, "ENTER") == 0) {
if (j - i - 1 == 5 && strncmp(buffer + i + 1, "ENTER", 5) == 0) {
count++;
}
}
i = j + 1;
} else {
i++;
}
}
}
fclose(f);
return count;
}
static int internal_generate_html(sqlite3 *db, const char *output_file, const char *title) {
if (!db || !output_file) return -1;
FILE *f = fopen(output_file, "w");
if (!f) return -1;
fprintf(f, "<html>\n");
fprintf(f, "<style>\n");
fprintf(f, " body { width:100%%; background-color: #000; color: #fff; font-family: monospace; }\n");
fprintf(f, " img { width:40%%; padding: 4%%; float:left; }\n");
fprintf(f, " .stats { clear: both; padding: 20px; }\n");
fprintf(f, "</style>\n");
fprintf(f, "<body>\n");
if (title) {
fprintf(f, "<h1>%s</h1>\n", title);
}
fprintf(f, "<div class=\"stats\">\n");
fprintf(f, "<p>Report generated by Tikker</p>\n");
fprintf(f, "</div>\n");
fprintf(f, "</body>\n");
fprintf(f, "</html>\n");
fclose(f);
return 0;
}
static int internal_generate_json(sqlite3 *db, char **json_output) {
if (!db || !json_output) return -1;
size_t buffer_size = 8192;
char *buffer = malloc(buffer_size);
if (!buffer) return -1;
snprintf(buffer, buffer_size, "{\"status\":\"success\",\"timestamp\":%ld}", (long)time(NULL));
*json_output = buffer;
return 0;
}
static int internal_generate_summary(sqlite3 *db, char *buffer, size_t buffer_size) {
if (!db || !buffer || buffer_size == 0) return -1;
snprintf(buffer, buffer_size,
"Tikker Statistics Summary\n"
"========================\n"
"Database: %s\n"
"Generated: %s\n",
"tikker.db", __DATE__);
return 0;
}
void tikker_report_free(tikker_report_t *report) {
if (!report) return;
if (report->title) free(report->title);
if (report->data) free(report->data);
free(report);
}

View File

@ -1,246 +0,0 @@
#include <tikker.h>
#include <config.h>
#include <decoder.h>
#include <indexer.h>
#include <report.h>
#include <stdlib.h>
#include <string.h>
tikker_context_t* tikker_open(const char *db_path) {
tikker_context_t *ctx = malloc(sizeof(tikker_context_t));
if (!ctx) return NULL;
ctx->db_path = db_path ? strdup(db_path) : strdup(TIKKER_DEFAULT_DB_PATH);
ctx->flags = 0;
int ret = sqlite3_open(ctx->db_path, &ctx->db);
if (ret != SQLITE_OK) {
free((void*)ctx->db_path);
free(ctx);
return NULL;
}
return ctx;
}
void tikker_close(tikker_context_t *ctx) {
if (!ctx) return;
if (ctx->db) sqlite3_close(ctx->db);
if (ctx->db_path) free((void*)ctx->db_path);
free(ctx);
}
int tikker_init_schema(tikker_context_t *ctx) {
if (!ctx || !ctx->db) return TIKKER_ERROR_DB;
const char *schema = "CREATE TABLE IF NOT EXISTS kevent ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"code INTEGER,"
"event TEXT,"
"name TEXT,"
"timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,"
"char TEXT"
");"
"CREATE INDEX IF NOT EXISTS idx_kevent_event ON kevent(event);"
"CREATE VIEW IF NOT EXISTS presses_per_hour AS "
"SELECT COUNT(0) as press_count, "
"(SELECT COUNT(0) FROM kevent) as total, "
"strftime('%Y-%m-%d.%H', timestamp) as period "
"FROM kevent WHERE event='PRESSED' GROUP BY period;";
char *errmsg = NULL;
int ret = sqlite3_exec(ctx->db, schema, NULL, NULL, &errmsg);
if (ret != SQLITE_OK) {
if (errmsg) sqlite3_free(errmsg);
return TIKKER_ERROR_DB;
}
return TIKKER_SUCCESS;
}
int tikker_get_version(char *buffer, size_t size) {
if (!buffer || size < strlen(TIKKER_VERSION) + 1) {
return TIKKER_ERROR_INVALID;
}
strncpy(buffer, TIKKER_VERSION, size - 1);
buffer[size - 1] = '\0';
return TIKKER_SUCCESS;
}
int tikker_get_daily_stats(tikker_context_t *ctx,
tikker_daily_stat_t **stats,
int *count) {
if (!ctx || !stats || !count) return TIKKER_ERROR_INVALID;
*stats = NULL;
*count = 0;
return TIKKER_SUCCESS;
}
int tikker_get_hourly_stats(tikker_context_t *ctx,
const char *date,
tikker_hourly_stat_t **stats,
int *count) {
if (!ctx || !date || !stats || !count) return TIKKER_ERROR_INVALID;
*stats = NULL;
*count = 0;
return TIKKER_SUCCESS;
}
int tikker_get_weekday_stats(tikker_context_t *ctx,
tikker_weekday_stat_t **stats,
int *count) {
if (!ctx || !stats || !count) return TIKKER_ERROR_INVALID;
*stats = NULL;
*count = 0;
return TIKKER_SUCCESS;
}
int tikker_get_top_words(tikker_context_t *ctx,
int limit,
tikker_word_stat_t **words,
int *count) {
if (!ctx || limit <= 0 || !words || !count) return TIKKER_ERROR_INVALID;
*words = NULL;
*count = 0;
return TIKKER_SUCCESS;
}
int tikker_get_top_keys(tikker_context_t *ctx,
int limit,
tikker_key_stat_t **keys,
int *count) {
if (!ctx || limit <= 0 || !keys || !count) return TIKKER_ERROR_INVALID;
*keys = NULL;
*count = 0;
return TIKKER_SUCCESS;
}
int tikker_get_date_range(tikker_context_t *ctx,
char *min_date,
char *max_date) {
if (!ctx || !min_date || !max_date) return TIKKER_ERROR_INVALID;
return TIKKER_SUCCESS;
}
int tikker_get_event_counts(tikker_context_t *ctx,
uint64_t *pressed,
uint64_t *released,
uint64_t *repeated) {
if (!ctx || !pressed || !released || !repeated) return TIKKER_ERROR_INVALID;
*pressed = 0;
*released = 0;
*repeated = 0;
return TIKKER_SUCCESS;
}
int tikker_decode_keylog(const char *input_file,
const char *output_file) {
if (!input_file || !output_file) return TIKKER_ERROR_INVALID;
if (tikker_decode_file(input_file, output_file) != 0) {
return TIKKER_ERROR_IO;
}
return TIKKER_SUCCESS;
}
int tikker_decode_keylog_buffer(const char *input,
size_t input_len,
char **output,
size_t *output_len) {
if (!input || !output || !output_len) return TIKKER_ERROR_INVALID;
tikker_text_buffer_t *buf = tikker_text_buffer_create(input_len);
if (!buf) return TIKKER_ERROR_MEMORY;
if (tikker_decode_buffer(input, input_len, buf) != 0) {
tikker_text_buffer_free(buf);
return TIKKER_ERROR_IO;
}
*output = malloc(buf->length + 1);
if (!*output) {
tikker_text_buffer_free(buf);
return TIKKER_ERROR_MEMORY;
}
memcpy(*output, buf->data, buf->length + 1);
*output_len = buf->length;
tikker_text_buffer_free(buf);
return TIKKER_SUCCESS;
}
int tikker_index_text_file(const char *file_path,
const char *db_path) {
if (!file_path || !db_path) return TIKKER_ERROR_INVALID;
return TIKKER_SUCCESS;
}
int tikker_index_directory(const char *dir_path,
const char *db_path) {
if (!dir_path || !db_path) return TIKKER_ERROR_INVALID;
return TIKKER_SUCCESS;
}
int tikker_get_word_frequency(const char *db_path,
const char *word,
uint64_t *count) {
if (!db_path || !word || !count) return TIKKER_ERROR_INVALID;
return TIKKER_SUCCESS;
}
int tikker_get_top_words_from_db(const char *db_path,
int limit,
tikker_word_stat_t **words,
int *count) {
if (!db_path || limit <= 0 || !words || !count) return TIKKER_ERROR_INVALID;
return TIKKER_SUCCESS;
}
int tikker_generate_html_report(tikker_context_t *ctx,
const char *output_file,
const char *graph_dir) {
if (!ctx || !output_file) return TIKKER_ERROR_INVALID;
return TIKKER_SUCCESS;
}
int tikker_generate_json_report(tikker_context_t *ctx,
char **json_output) {
if (!ctx || !json_output) return TIKKER_ERROR_INVALID;
return TIKKER_SUCCESS;
}
int tikker_merge_text_files(const char *input_dir,
const char *pattern,
const char *output_path) {
if (!input_dir || !output_path) return TIKKER_ERROR_INVALID;
return TIKKER_SUCCESS;
}
int tikker_get_metrics(tikker_perf_metrics_t *metrics) {
if (!metrics) return TIKKER_ERROR_INVALID;
return TIKKER_SUCCESS;
}
void tikker_free_words(tikker_word_stat_t *words, int count) {
if (words) free(words);
}
void tikker_free_keys(tikker_key_stat_t *keys, int count) {
if (keys) free(keys);
}
void tikker_free_daily_stats(tikker_daily_stat_t *stats, int count) {
if (stats) free(stats);
}
void tikker_free_hourly_stats(tikker_hourly_stat_t *stats, int count) {
if (stats) free(stats);
}
void tikker_free_weekday_stats(tikker_weekday_stat_t *stats, int count) {
if (stats) free(stats);
}
void tikker_free_json(char *json) {
if (json) free(json);
}

View File

@ -1,63 +0,0 @@
#include <types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
tikker_string_t* tikker_string_create(size_t capacity) {
tikker_string_t *str = malloc(sizeof(tikker_string_t));
if (!str) return NULL;
str->capacity = capacity ? capacity : 256;
str->buffer = malloc(str->capacity);
if (!str->buffer) { free(str); return NULL; }
str->length = 0;
str->buffer[0] = '\0';
return str;
}
void tikker_string_free(tikker_string_t *str) {
if (!str) return;
if (str->buffer) free(str->buffer);
free(str);
}
int tikker_string_append(tikker_string_t *str, const char *data) {
if (!str || !data) return -1;
size_t data_len = strlen(data);
if (str->length + data_len >= str->capacity) {
size_t new_capacity = str->capacity * 2;
while (new_capacity <= str->length + data_len) new_capacity *= 2;
char *new_buffer = realloc(str->buffer, new_capacity);
if (!new_buffer) return -1;
str->buffer = new_buffer;
str->capacity = new_capacity;
}
strcpy(str->buffer + str->length, data);
str->length += data_len;
return 0;
}
int tikker_string_append_char(tikker_string_t *str, char c) {
if (!str) return -1;
if (str->length + 1 >= str->capacity) {
size_t new_capacity = str->capacity * 2;
char *new_buffer = realloc(str->buffer, new_capacity);
if (!new_buffer) return -1;
str->buffer = new_buffer;
str->capacity = new_capacity;
}
str->buffer[str->length] = c;
str->length++;
str->buffer[str->length] = '\0';
return 0;
}
void tikker_string_clear(tikker_string_t *str) {
if (!str) return;
str->length = 0;
str->buffer[0] = '\0';
}
char* tikker_string_cstr(tikker_string_t *str) {
if (!str) return NULL;
return str->buffer;
}

View File

@ -1,25 +0,0 @@
CC ?= gcc
CFLAGS ?= -Wall -Wextra -pedantic -std=c11 -O2
CFLAGS += -I../../libtikker/include -I../../third_party
BIN_DIR ?= ../../../build/bin
LIB_DIR ?= ../../../build/lib
LDFLAGS ?= -L$(LIB_DIR) -ltikker -lsqlite3 -lm
TARGET := $(BIN_DIR)/tikker-aggregator
.PHONY: all clean
all: $(TARGET)
$(BIN_DIR):
@mkdir -p $(BIN_DIR)
$(TARGET): main.c | $(BIN_DIR)
@echo "Building tikker-aggregator..."
@$(CC) $(CFLAGS) main.c -o $@ $(LDFLAGS)
@echo "✓ tikker-aggregator built"
clean:
@rm -f $(TARGET)
@echo "✓ aggregator cleaned"

View File

@ -1,152 +0,0 @@
#include <tikker.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void print_usage(const char *prog) {
printf("Usage: %s [options]\n\n", prog);
printf("Options:\n");
printf(" --daily Generate daily statistics\n");
printf(" --hourly <date> Generate hourly stats for specific date\n");
printf(" --weekly Generate weekly statistics\n");
printf(" --weekday Generate weekday comparison\n");
printf(" --top-keys [N] Show top N keys (default: 10)\n");
printf(" --top-words [N] Show top N words (default: 10)\n");
printf(" --format <format> Output format: json, csv, text (default: text)\n");
printf(" --output <file> Write to file instead of stdout\n");
printf(" --database <path> Use custom database (default: tikker.db)\n");
printf(" --help Show this help message\n");
}
int main(int argc, char *argv[]) {
const char *db_path = "tikker.db";
const char *action = NULL;
const char *date_filter = NULL;
const char *format = "text";
const char *output_file = NULL;
int top_count = 10;
FILE *out = stdout;
int i;
if (argc < 2) {
print_usage(argv[0]);
return 1;
}
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "--help") == 0) {
print_usage(argv[0]);
return 0;
} else if (strcmp(argv[i], "--database") == 0) {
if (i + 1 < argc) {
db_path = argv[++i];
}
} else if (strcmp(argv[i], "--daily") == 0) {
action = "daily";
} else if (strcmp(argv[i], "--hourly") == 0) {
action = "hourly";
if (i + 1 < argc) {
date_filter = argv[++i];
}
} else if (strcmp(argv[i], "--weekly") == 0) {
action = "weekly";
} else if (strcmp(argv[i], "--weekday") == 0) {
action = "weekday";
} else if (strcmp(argv[i], "--format") == 0) {
if (i + 1 < argc) {
format = argv[++i];
}
} else if (strcmp(argv[i], "--output") == 0) {
if (i + 1 < argc) {
output_file = argv[++i];
}
} else if (strcmp(argv[i], "--top-keys") == 0 || strcmp(argv[i], "--top-words") == 0) {
if (i + 1 < argc && argv[i + 1][0] != '-') {
top_count = atoi(argv[++i]);
if (top_count <= 0) top_count = 10;
}
}
}
if (!action) {
fprintf(stderr, "Error: Please specify an action (--daily, --hourly, --weekly, or --weekday)\n");
print_usage(argv[0]);
return 1;
}
if (output_file) {
out = fopen(output_file, "w");
if (!out) {
fprintf(stderr, "Error: Cannot open output file '%s'\n", output_file);
return 1;
}
}
tikker_context_t *ctx = tikker_open(db_path);
if (!ctx) {
fprintf(stderr, "Error: Cannot open database '%s'\n", db_path);
if (out != stdout) fclose(out);
return 1;
}
if (strcmp(action, "daily") == 0) {
fprintf(out, "Daily Statistics\n");
fprintf(out, "================\n\n");
uint64_t pressed, released, repeated;
tikker_get_event_counts(ctx, &pressed, &released, &repeated);
fprintf(out, "Total Key Presses: %lu\n", (unsigned long)pressed);
fprintf(out, "Total Releases: %lu\n", (unsigned long)released);
fprintf(out, "Total Repeats: %lu\n", (unsigned long)repeated);
fprintf(out, "Total Events: %lu\n", (unsigned long)(pressed + released + repeated));
} else if (strcmp(action, "hourly") == 0) {
if (!date_filter) {
fprintf(stderr, "Error: --hourly requires a date argument (YYYY-MM-DD)\n");
tikker_close(ctx);
if (out != stdout) fclose(out);
return 1;
}
fprintf(out, "Hourly Statistics for %s\n", date_filter);
fprintf(out, "========================\n\n");
fprintf(out, "Hour Presses\n");
fprintf(out, "----- -------\n");
for (int h = 0; h < 24; h++) {
fprintf(out, "%02d:00 ~1000\n", h);
}
} else if (strcmp(action, "weekly") == 0) {
fprintf(out, "Weekly Statistics\n");
fprintf(out, "=================\n\n");
fprintf(out, "Mon 12500 presses\n");
fprintf(out, "Tue 13200 presses\n");
fprintf(out, "Wed 12800 presses\n");
fprintf(out, "Thu 11900 presses\n");
fprintf(out, "Fri 13100 presses\n");
fprintf(out, "Sat 8200 presses\n");
fprintf(out, "Sun 9100 presses\n");
} else if (strcmp(action, "weekday") == 0) {
fprintf(out, "Weekday Comparison\n");
fprintf(out, "==================\n\n");
fprintf(out, "Day Total Presses Avg Per Hour\n");
fprintf(out, "--- -------- ----- --- ---- ----\n");
fprintf(out, "Monday 12500 521\n");
fprintf(out, "Tuesday 13200 550\n");
fprintf(out, "Wednesday 12800 533\n");
fprintf(out, "Thursday 11900 496\n");
fprintf(out, "Friday 13100 546\n");
fprintf(out, "Saturday 8200 342\n");
fprintf(out, "Sunday 9100 379\n");
}
tikker_close(ctx);
if (out != stdout) fclose(out);
if (output_file) {
printf("✓ Statistics written to %s\n", output_file);
}
return 0;
}

View File

@ -1,25 +0,0 @@
CC ?= gcc
CFLAGS ?= -Wall -Wextra -pedantic -std=c11 -O2
CFLAGS += -I../../libtikker/include -I../../third_party
BIN_DIR ?= ../../../build/bin
LIB_DIR ?= ../../../build/lib
LDFLAGS ?= -L$(LIB_DIR) -ltikker -lsqlite3 -lm
TARGET := $(BIN_DIR)/tikker-decoder
.PHONY: all clean
all: $(TARGET)
$(BIN_DIR):
@mkdir -p $(BIN_DIR)
$(TARGET): main.c | $(BIN_DIR)
@echo "Building tikker-decoder..."
@$(CC) $(CFLAGS) main.c -o $@ $(LDFLAGS)
@echo "✓ tikker-decoder built"
clean:
@rm -f $(TARGET)
@echo "✓ decoder cleaned"

View File

@ -1,63 +0,0 @@
#include <tikker.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void print_usage(const char *prog) {
printf("Usage: %s [options] <input_file> <output_file>\n", prog);
printf("\nOptions:\n");
printf(" --verbose Show processing progress\n");
printf(" --stats Print decoding statistics\n");
printf(" --help Show this help message\n");
}
int main(int argc, char *argv[]) {
if (argc < 2) {
print_usage(argv[0]);
return 1;
}
int verbose = 0;
int show_stats = 0;
const char *input_file = NULL;
const char *output_file = NULL;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--verbose") == 0) {
verbose = 1;
} else if (strcmp(argv[i], "--stats") == 0) {
show_stats = 1;
} else if (strcmp(argv[i], "--help") == 0) {
print_usage(argv[0]);
return 0;
} else if (argv[i][0] != '-') {
if (!input_file) {
input_file = argv[i];
} else if (!output_file) {
output_file = argv[i];
}
}
}
if (!input_file || !output_file) {
fprintf(stderr, "Error: input and output files required\n");
print_usage(argv[0]);
return 1;
}
if (verbose) {
printf("Decoding keylog: %s -> %s\n", input_file, output_file);
}
int ret = tikker_decode_keylog(input_file, output_file);
if (ret != 0) {
fprintf(stderr, "Error: Failed to decode keylog\n");
return 1;
}
if (verbose) {
printf("✓ Decoding complete\n");
}
return 0;
}

View File

@ -1,25 +0,0 @@
CC ?= gcc
CFLAGS ?= -Wall -Wextra -pedantic -std=c11 -O2
CFLAGS += -I../../libtikker/include -I../../third_party
BIN_DIR ?= ../../../build/bin
LIB_DIR ?= ../../../build/lib
LDFLAGS ?= -L$(LIB_DIR) -ltikker -lsqlite3 -lm
TARGET := $(BIN_DIR)/tikker-indexer
.PHONY: all clean
all: $(TARGET)
$(BIN_DIR):
@mkdir -p $(BIN_DIR)
$(TARGET): main.c | $(BIN_DIR)
@echo "Building tikker-indexer..."
@$(CC) $(CFLAGS) main.c -o $@ $(LDFLAGS)
@echo "✓ tikker-indexer built"
clean:
@rm -f $(TARGET)
@echo "✓ indexer cleaned"

View File

@ -1,135 +0,0 @@
#include <tikker.h>
#include <indexer.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void print_usage(const char *prog) {
printf("Usage: %s [options]\n\n", prog);
printf("Options:\n");
printf(" --index Build word index from logs_plain directory\n");
printf(" --popular [N] Show top N most popular words (default: 10)\n");
printf(" --find <word> Find frequency of a specific word\n");
printf(" --database <path> Use custom database (default: tags.db)\n");
printf(" --help Show this help message\n");
}
int main(int argc, char *argv[]) {
const char *db_path = "tags.db";
const char *action = NULL;
const char *word_to_find = NULL;
int popular_count = 10;
int i;
if (argc < 2) {
print_usage(argv[0]);
return 1;
}
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "--help") == 0) {
print_usage(argv[0]);
return 0;
} else if (strcmp(argv[i], "--database") == 0) {
if (i + 1 < argc) {
db_path = argv[++i];
}
} else if (strcmp(argv[i], "--index") == 0) {
action = "index";
} else if (strcmp(argv[i], "--popular") == 0) {
action = "popular";
if (i + 1 < argc && argv[i + 1][0] != '-') {
popular_count = atoi(argv[++i]);
if (popular_count <= 0) popular_count = 10;
}
} else if (strcmp(argv[i], "--find") == 0) {
action = "find";
if (i + 1 < argc) {
word_to_find = argv[++i];
}
}
}
if (!action) {
fprintf(stderr, "Error: Please specify an action (--index, --popular, or --find)\n");
print_usage(argv[0]);
return 1;
}
if (strcmp(action, "index") == 0) {
printf("Building word index from logs_plain directory...\n");
int ret = tikker_index_directory("logs_plain", db_path);
if (ret != 0) {
fprintf(stderr, "Error: Failed to index directory\n");
return 1;
}
printf("✓ Index built successfully\n");
int unique_count;
tikker_word_get_unique_count(db_path, &unique_count);
printf(" Total unique words: %d\n", unique_count);
uint64_t total_count;
tikker_word_get_total_count(db_path, &total_count);
printf(" Total word count: %lu\n", (unsigned long)total_count);
} else if (strcmp(action, "popular") == 0) {
printf("Top %d most popular words:\n\n", popular_count);
printf("%-5s %-20s %10s %10s\n", "#", "Word", "Count", "Percent");
printf("%-5s %-20s %10s %10s\n", "-", "----", "-----", "-------");
uint64_t total_count;
tikker_word_get_total_count(db_path, &total_count);
if (total_count == 0) {
printf("No words indexed yet. Run with --index first.\n");
return 0;
}
tikker_word_entry_t *entries;
int count;
int ret = tikker_word_get_top(db_path, popular_count, &entries, &count);
if (ret != 0 || count == 0) {
printf("No words found in database.\n");
return 0;
}
for (int j = 0; j < count; j++) {
double percent = (double)entries[j].count / total_count * 100.0;
printf("#%-4d %-20s %10lu %9.2f%%\n",
entries[j].rank,
entries[j].word,
(unsigned long)entries[j].count,
percent);
}
tikker_word_entries_free(entries, count);
} else if (strcmp(action, "find") == 0) {
if (!word_to_find) {
fprintf(stderr, "Error: --find requires a word argument\n");
return 1;
}
uint64_t count;
int rank;
int ret = tikker_word_get_rank(db_path, word_to_find, &rank, &count);
if (ret != 0) {
uint64_t freq;
tikker_word_get_frequency(db_path, word_to_find, &freq);
if (freq > 0) {
printf("Word: '%s'\n", word_to_find);
printf("Frequency: %lu\n", (unsigned long)freq);
} else {
printf("Word '%s' not found in database.\n", word_to_find);
}
} else {
printf("Word: '%s'\n", word_to_find);
printf("Rank: #%d\n", rank);
printf("Frequency: %lu\n", (unsigned long)count);
}
}
return 0;
}

View File

@ -1,25 +0,0 @@
CC ?= gcc
CFLAGS ?= -Wall -Wextra -pedantic -std=c11 -O2
CFLAGS += -I../../libtikker/include -I../../third_party
BIN_DIR ?= ../../../build/bin
LIB_DIR ?= ../../../build/lib
LDFLAGS ?= -L$(LIB_DIR) -ltikker -lsqlite3 -lm
TARGET := $(BIN_DIR)/tikker-report
.PHONY: all clean
all: $(TARGET)
$(BIN_DIR):
@mkdir -p $(BIN_DIR)
$(TARGET): main.c | $(BIN_DIR)
@echo "Building tikker-report..."
@$(CC) $(CFLAGS) main.c -o $@ $(LDFLAGS)
@echo "✓ tikker-report built"
clean:
@rm -f $(TARGET)
@echo "✓ report cleaned"

View File

@ -1,123 +0,0 @@
#include <tikker.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
void print_usage(const char *prog) {
printf("Usage: %s [options]\n\n", prog);
printf("Options:\n");
printf(" --input <dir> Input logs directory (default: logs_plain)\n");
printf(" --output <file> Output HTML file (default: report.html)\n");
printf(" --graph-dir <dir> Directory with PNG graphs to embed\n");
printf(" --include-graphs Include embedded PNG graphs (requires --graph-dir)\n");
printf(" --database <path> Use custom database (default: tikker.db)\n");
printf(" --title <title> Report title\n");
printf(" --help Show this help message\n");
}
int count_graph_files(const char *dir) {
if (!dir) return 0;
DIR *d = opendir(dir);
if (!d) return 0;
struct dirent *entry;
int count = 0;
while ((entry = readdir(d)) != NULL) {
if (strstr(entry->d_name, ".png")) count++;
}
closedir(d);
return count;
}
int main(int argc, char *argv[]) {
const char *input_dir = "logs_plain";
const char *output_file = "report.html";
const char *graph_dir = NULL;
const char *db_path = "tikker.db";
const char *title = "Tikker Activity Report";
int include_graphs = 0;
int i;
if (argc < 2) {
print_usage(argv[0]);
return 1;
}
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "--help") == 0) {
print_usage(argv[0]);
return 0;
} else if (strcmp(argv[i], "--input") == 0) {
if (i + 1 < argc) {
input_dir = argv[++i];
}
} else if (strcmp(argv[i], "--output") == 0) {
if (i + 1 < argc) {
output_file = argv[++i];
}
} else if (strcmp(argv[i], "--graph-dir") == 0) {
if (i + 1 < argc) {
graph_dir = argv[++i];
}
} else if (strcmp(argv[i], "--include-graphs") == 0) {
include_graphs = 1;
} else if (strcmp(argv[i], "--database") == 0) {
if (i + 1 < argc) {
db_path = argv[++i];
}
} else if (strcmp(argv[i], "--title") == 0) {
if (i + 1 < argc) {
title = argv[++i];
}
}
}
printf("Generating report...\n");
printf(" Input directory: %s\n", input_dir);
printf(" Output file: %s\n", output_file);
tikker_context_t *ctx = tikker_open(db_path);
if (!ctx) {
fprintf(stderr, "Error: Cannot open database '%s'\n", db_path);
return 1;
}
if (tikker_generate_html_report(ctx, output_file, graph_dir) != 0) {
fprintf(stderr, "Error: Failed to generate report\n");
tikker_close(ctx);
return 1;
}
FILE *out = fopen(output_file, "a");
if (out) {
fprintf(out, "\n<!-- Report Statistics -->\n");
fprintf(out, "<div class='stats'>\n");
fprintf(out, "<h2>Statistics</h2>\n");
fprintf(out, "<p>Report generated at: %s</p>\n", __DATE__);
uint64_t pressed, released, repeated;
tikker_get_event_counts(ctx, &pressed, &released, &repeated);
fprintf(out, "<p>Total Key Presses: %lu</p>\n", (unsigned long)pressed);
fprintf(out, "<p>Total Releases: %lu</p>\n", (unsigned long)released);
fprintf(out, "<p>Total Repeats: %lu</p>\n", (unsigned long)repeated);
if (include_graphs && graph_dir) {
int graph_count = count_graph_files(graph_dir);
fprintf(out, "<p>Graphs embedded: %d</p>\n", graph_count);
}
fprintf(out, "</div>\n");
fprintf(out, "</body>\n");
fprintf(out, "</html>\n");
fclose(out);
}
tikker_close(ctx);
printf("✓ Report generated: %s\n", output_file);
return 0;
}

BIN
tikker

Binary file not shown.

376
tikker.c
View File

@ -1,21 +1,33 @@
/*
Written by retoor@molodetz.nl
This program captures keyboard input events, resolves device names, and logs these events into a specified database.
Tikker - Keystroke monitoring and productivity analytics application.
Captures keyboard input events, stores in SQLite, provides statistics.
Includes:
- sormc.h: Custom library file for database management.
MIT License
MIT License:
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
*/
#include "sormc.h"
#include "tikker_types.h"
#include "tikker_db.h"
#include "tikker_decode.h"
#include "tikker_words.h"
#include "tikker_stats.h"
#include <fcntl.h>
#include <linux/input.h>
#include <stdio.h>
@ -24,109 +36,182 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
#include <sys/ioctl.h>
#include <unistd.h>
#define DATABASE_NAME "tikker.db"
#define DEVICE_TO_READ_DEFAULT "keyboard"
#define MAX_DEVICES 32
#define DEVICE_PATH "/dev/input/event"
static int tikker_db = 0;
const char *keycode_to_char[] = {
[2] = "1", [3] = "2", [4] = "3", [5] = "4", [6] = "5",
[7] = "6", [8] = "7", [9] = "8", [10] = "9", [11] = "0",
[12] = "-", [13] = "=", [14] = "[BACKSPACE]", [15] = "[TAB]",
[16] = "Q", [17] = "W", [18] = "E", [19] = "R", [20] = "T",
[21] = "Y", [22] = "U", [23] = "I", [24] = "O", [25] = "P",
[26] = "[", [27] = "]", [28] = "[ENTER]\n", [29] = "[LEFT_CTRL]",
[30] = "A", [31] = "S", [32] = "D", [33] = "F", [34] = "G",
[35] = "H", [36] = "J", [37] = "K", [38] = "L", [39] = ";",
[40] = "'", [41] = "`", [42] = "[LEFT_SHIFT]", [43] = "\\",
[44] = "Z", [45] = "X", [46] = "C", [47] = "V", [48] = "B",
[49] = "N", [50] = "M", [51] = ",", [52] = ".", [53] = "/",
[54] = "[RIGHT_SHIFT]", [55] = "[KEYPAD_*]", [56] = "[LEFT_ALT]",
[57] = " ", [58] = "[CAPSLOCK]",
[59] = "[F1]", [60] = "[F2]", [61] = "[F3]", [62] = "[F4]",
[63] = "[F5]", [64] = "[F6]", [65] = "[F7]", [66] = "[F8]",
[67] = "[F9]", [68] = "[F10]", [87] = "[F11]", [88] = "[F12]",
[69] = "[NUMLOCK]", [70] = "[SCROLLLOCK]", [71] = "[KEYPAD_7]",
[72] = "[KEYPAD_8]", [73] = "[KEYPAD_9]", [74] = "[KEYPAD_-]",
[75] = "[KEYPAD_4]", [76] = "[KEYPAD_5]", [77] = "[KEYPAD_6]",
[78] = "[KEYPAD_+]", [79] = "[KEYPAD_1]", [80] = "[KEYPAD_2]",
[81] = "[KEYPAD_3]", [82] = "[KEYPAD_0]", [83] = "[KEYPAD_.]",
[86] = "<", [100] = "[RIGHT_ALT]", [97] = "[RIGHT_CTRL]",
[119] = "[PAUSE]", [120] = "[SYSRQ]", [121] = "[BREAK]",
[102] = "[HOME]", [103] = "[UP]", [104] = "[PAGEUP]",
[105] = "[LEFT]", [106] = "[RIGHT]", [107] = "[END]",
[108] = "[DOWN]", [109] = "[PAGEDOWN]", [110] = "[INSERT]",
[111] = "[DELETE]",
[113] = "[MUTE]", [114] = "[VOLUME_DOWN]", [115] = "[VOLUME_UP]",
[163] = "[MEDIA_NEXT]", [165] = "[MEDIA_PREV]", [164] = "[MEDIA_PLAY_PAUSE]"
};
static void tikker_init_pragmas(void) {
sormq(tikker_db, "PRAGMA journal_mode=WAL");
sormq(tikker_db, "PRAGMA synchronous=NORMAL");
sormq(tikker_db, "PRAGMA cache_size=-65536");
sormq(tikker_db, "PRAGMA temp_store=MEMORY");
sormq(tikker_db, "PRAGMA mmap_size=268435456");
}
char *resolve_device_name(int fd) {
static void tikker_init_schema(void) {
sormq(tikker_db, "CREATE TABLE IF NOT EXISTS kevent ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"code, event, name, timestamp, char)");
sormq(tikker_db, "CREATE INDEX IF NOT EXISTS idx_kevent_event ON kevent(event)");
sormq(tikker_db, "CREATE INDEX IF NOT EXISTS idx_kevent_timestamp ON kevent(timestamp)");
sormq(tikker_db, "CREATE INDEX IF NOT EXISTS idx_kevent_code ON kevent(code)");
sormq(tikker_db, "CREATE INDEX IF NOT EXISTS idx_kevent_event_code ON kevent(event, code)");
sormq(tikker_db, "CREATE INDEX IF NOT EXISTS idx_kevent_event_timestamp ON kevent(event, timestamp)");
sormq(tikker_db, "CREATE INDEX IF NOT EXISTS idx_kevent_event_timestamp_char ON kevent(event, timestamp, char)");
sormq(tikker_db, "CREATE TABLE IF NOT EXISTS kevent_daily ("
"date TEXT PRIMARY KEY, "
"pressed INTEGER DEFAULT 0, "
"released INTEGER DEFAULT 0, "
"repeated INTEGER DEFAULT 0)");
sormq(tikker_db, "CREATE TABLE IF NOT EXISTS kevent_hourly ("
"date_hour TEXT PRIMARY KEY, "
"pressed INTEGER DEFAULT 0)");
sormq(tikker_db, "CREATE TABLE IF NOT EXISTS kevent_key_counts ("
"code INTEGER PRIMARY KEY, "
"count INTEGER DEFAULT 0)");
}
static char *tikker_resolve_device_name(int fd) {
static char device_name[256];
device_name[0] = 0;
if (ioctl(fd, EVIOCGNAME(sizeof(device_name)), device_name) < 0) {
return 0;
return NULL;
}
return device_name;
}
char * sormgetc(char *result,int index){
char * end = NULL;
int current_index = 0;
while((end = strstr((char *)result, ";")) != NULL){
if(index == current_index){
result[end - (char *)result] = 0;
return result;
}
result = end + 1;
current_index++;
static void tikker_populate_cache(void) {
sorm_ptr count = sormq(tikker_db, "SELECT COUNT(*) FROM kevent_daily");
if (count) {
char *csv = (char *)count;
char *line = strchr(csv, '\n');
if (line) line++;
else line = csv;
int cached_days = atoi(line);
free(count);
if (cached_days > 0) return;
}
*end = 0;
return result;
sormq(tikker_db, "INSERT OR REPLACE INTO kevent_daily (date, pressed, released, repeated) "
"SELECT STRFTIME('%%Y-%%m-%%d', timestamp) as date, "
"SUM(CASE WHEN event = 'PRESSED' THEN 1 ELSE 0 END), "
"SUM(CASE WHEN event = 'RELEASED' THEN 1 ELSE 0 END), "
"SUM(CASE WHEN event = 'REPEATED' THEN 1 ELSE 0 END) "
"FROM kevent GROUP BY date");
sormq(tikker_db, "INSERT OR REPLACE INTO kevent_hourly (date_hour, pressed) "
"SELECT STRFTIME('%%Y-%%m-%%d.%%H', timestamp), COUNT(*) "
"FROM kevent WHERE event = 'PRESSED' GROUP BY 1");
sormq(tikker_db, "INSERT OR REPLACE INTO kevent_key_counts (code, count) "
"SELECT code, COUNT(*) FROM kevent WHERE event = 'PRESSED' GROUP BY code");
}
int main(int argc, char *argv[]) {
char *device_to_read = rargs_get_option_string(argc, argv, "--device", DEVICE_TO_READ_DEFAULT);
//printf("%s\n", device_to_read);
static void tikker_update_cache_for_event(const char *event, int code) {
sormq(tikker_db, "INSERT INTO kevent_daily (date, pressed, released, repeated) "
"VALUES (DATE('now'), 0, 0, 0) "
"ON CONFLICT(date) DO NOTHING");
if (strcmp(event, "PRESSED") == 0) {
sormq(tikker_db, "UPDATE kevent_daily SET pressed = pressed + 1 WHERE date = DATE('now')");
sormq(tikker_db, "INSERT INTO kevent_hourly (date_hour, pressed) "
"VALUES (STRFTIME('%%Y-%%m-%%d.%%H', 'now'), 1) "
"ON CONFLICT(date_hour) DO UPDATE SET pressed = pressed + 1");
sormq(tikker_db, "INSERT INTO kevent_key_counts (code, count) VALUES (%d, 1) "
"ON CONFLICT(code) DO UPDATE SET count = count + 1", code);
} else if (strcmp(event, "RELEASED") == 0) {
sormq(tikker_db, "UPDATE kevent_daily SET released = released + 1 WHERE date = DATE('now')");
} else {
sormq(tikker_db, "UPDATE kevent_daily SET repeated = repeated + 1 WHERE date = DATE('now')");
}
}
static void tikker_print_usage(void) {
printf("Usage: tikker [command] [options]\n\n");
printf("Commands:\n");
printf(" (no command) Start keyboard monitoring\n");
printf(" presses_today Show today's keystroke count\n");
printf(" stats daily Daily keystroke statistics\n");
printf(" stats hourly [DATE] Hourly breakdown (default: today)\n");
printf(" stats weekly Weekly keystroke statistics\n");
printf(" stats weekday Weekday comparison\n");
printf(" stats top-keys [N] Top N keys (default: 10)\n");
printf(" stats top-words [N] Top N words (default: 10)\n");
printf(" stats summary Overall summary statistics\n");
printf(" decode [FILE] Decode keystroke log file\n");
printf("\nOptions:\n");
printf(" --device='NAME' Monitor specific device\n");
}
static int tikker_handle_stats_command(int argc, char *argv[]) {
if (argc < 1) {
tikker_print_usage();
return 1;
}
const char *cmd = argv[0];
if (strcmp(cmd, "daily") == 0) {
return tikker_stats_daily(tikker_db);
}
if (strcmp(cmd, "hourly") == 0) {
const char *date = (argc > 1) ? argv[1] : tikker_get_today_date();
return tikker_stats_hourly(tikker_db, date);
}
if (strcmp(cmd, "weekly") == 0) {
return tikker_stats_weekly(tikker_db);
}
if (strcmp(cmd, "weekday") == 0) {
return tikker_stats_weekday(tikker_db);
}
if (strcmp(cmd, "top-keys") == 0) {
int limit = (argc > 1) ? atoi(argv[1]) : 10;
if (limit <= 0) limit = 10;
return tikker_stats_top_keys(tikker_db, limit);
}
if (strcmp(cmd, "top-words") == 0) {
int limit = (argc > 1) ? atoi(argv[1]) : 10;
if (limit <= 0) limit = 10;
return tikker_stats_top_words(tikker_db, limit);
}
if (strcmp(cmd, "summary") == 0) {
return tikker_stats_summary(tikker_db);
}
fprintf(stderr, "Unknown stats command: %s\n", cmd);
tikker_print_usage();
return 1;
}
static int tikker_run_keylogger(int argc, char *argv[]) {
char *device_to_read = rargs_get_option_string(argc, argv, "--device", TIKKER_DEVICE_DEFAULT);
int db = sormc(DATABASE_NAME);
ulonglong times_repeated = 0;
ulonglong times_pressed = 0;
ulonglong times_released = 0;
sormq(db, "CREATE TABLE IF NOT EXISTS kevent (id INTEGER PRIMARY KEY AUTOINCREMENT, code,event,name,timestamp,char)");
if(argc > 1 && !strcmp(argv[1],"presses_today")){
time_t now = time(NULL);
char time_string[32];
strftime(time_string, sizeof(time_string), "%Y-%m-%d", localtime(&now));
sorm_ptr result = sormq(db, "SELECT COUNT(id) as total FROM kevent WHERE timestamp >= %s AND event = 'PRESSED'",time_string);
printf("%s",sormgetc((char *)result,1));
//fflush(stdout);
free(result);
exit(0);
}
int keyboard_fds[MAX_DEVICES];
int keyboard_fds[TIKKER_MAX_DEVICES];
int num_keyboards = 0;
for (int i = 0; i < MAX_DEVICES; i++) {
for (int i = 0; i < TIKKER_MAX_DEVICES; i++) {
char device_path[32];
snprintf(device_path, sizeof(device_path), "%s%d", DEVICE_PATH, i);
snprintf(device_path, sizeof(device_path), "%s%d", TIKKER_DEVICE_PATH, i);
int fd = open(device_path, O_RDONLY);
if (fd < 0) {
continue;
}
char *device_name = resolve_device_name(fd);
if (fd < 0) continue;
char *device_name = tikker_resolve_device_name(fd);
if (!device_name) {
close(fd);
continue;
}
bool is_device_to_read = strstr(device_name, device_to_read) != NULL;
printf("[%s] %s. Mount: %s.\n", is_device_to_read ? "-" : "+", device_name, device_path);
if (is_device_to_read) {
bool is_target = strstr(device_name, device_to_read) != NULL;
printf("[%s] %s. Mount: %s.\n", is_target ? "-" : "+", device_name, device_path);
if (is_target) {
keyboard_fds[num_keyboards++] = fd;
} else {
close(fd);
@ -135,12 +220,13 @@ int main(int argc, char *argv[]) {
if (num_keyboards == 0) {
fprintf(stderr, "No keyboard found. Are you running as root?\n"
"If your device is listed above with a minus [-] in front, \n"
"If your device is listed above with a minus [-] in front,\n"
"run this application using --device='[DEVICE_NAME]'\n");
return 1;
}
printf("Monitoring %d keyboards.\n", num_keyboards);
struct input_event ev;
fd_set read_fds;
@ -149,9 +235,7 @@ int main(int argc, char *argv[]) {
int max_fd = -1;
for (int i = 0; i < num_keyboards; i++) {
FD_SET(keyboard_fds[i], &read_fds);
if (keyboard_fds[i] > max_fd) {
max_fd = keyboard_fds[i];
}
if (keyboard_fds[i] > max_fd) max_fd = keyboard_fds[i];
}
if (select(max_fd + 1, &read_fds, NULL, NULL, NULL) < 0) {
@ -160,37 +244,42 @@ int main(int argc, char *argv[]) {
}
for (int i = 0; i < num_keyboards; i++) {
if (FD_ISSET(keyboard_fds[i], &read_fds)) {
ssize_t bytes = read(keyboard_fds[i], &ev, sizeof(struct input_event));
if (bytes == sizeof(struct input_event)) {
if (ev.type == EV_KEY) {
char *char_name = NULL;
if (ev.code < sizeof(keycode_to_char) / sizeof(keycode_to_char[0])) {
char_name = (char *)keycode_to_char[ev.code];
}
char keyboard_name[256];
ioctl(keyboard_fds[i], EVIOCGNAME(sizeof(keyboard_name)), keyboard_name);
printf("Keyboard: %s, ", keyboard_name);
char *event_name = NULL;
if (ev.value == 1) {
event_name = "PRESSED";
times_pressed++;
} else if (ev.value == 0) {
event_name = "RELEASED";
times_released++;
} else {
event_name = "REPEATED";
times_repeated++;
}
sormq(db, "INSERT INTO kevent (code, event, name,timestamp,char) VALUES (%d, %s, %s, DATETIME('now'),%s)", ev.code,
event_name, keyboard_name, char_name);
printf("Event: %s, ", ev.value == 1 ? "PRESSED" : ev.value == 0 ? "RELEASED" : "REPEATED");
printf("Key Code: %d, ", ev.code);
printf("Name: %s, ", char_name);
printf("Pr: %lld Rel: %lld Rep: %lld\n", times_pressed, times_released, times_repeated);
}
}
if (!FD_ISSET(keyboard_fds[i], &read_fds)) continue;
ssize_t bytes = read(keyboard_fds[i], &ev, sizeof(struct input_event));
if (bytes != sizeof(struct input_event)) continue;
if (ev.type != EV_KEY) continue;
char *char_name = NULL;
if (ev.code < sizeof(tikker_keycode_to_char) / sizeof(tikker_keycode_to_char[0])) {
char_name = (char *)tikker_keycode_to_char[ev.code];
}
char keyboard_name[256];
ioctl(keyboard_fds[i], EVIOCGNAME(sizeof(keyboard_name)), keyboard_name);
char *event_name;
if (ev.value == 1) {
event_name = "PRESSED";
times_pressed++;
} else if (ev.value == 0) {
event_name = "RELEASED";
times_released++;
} else {
event_name = "REPEATED";
times_repeated++;
}
sormq(tikker_db, "INSERT INTO kevent (code, event, name, timestamp, char) "
"VALUES (%d, %s, %s, DATETIME('now'), %s)",
ev.code, event_name, keyboard_name, char_name);
tikker_update_cache_for_event(event_name, ev.code);
printf("Keyboard: %s, Event: %s, Key Code: %d, Name: %s, "
"Pr: %lld Rel: %lld Rep: %lld\n",
keyboard_name, event_name, ev.code, char_name,
times_pressed, times_released, times_repeated);
}
}
@ -200,3 +289,50 @@ int main(int argc, char *argv[]) {
return 0;
}
int main(int argc, char *argv[]) {
tikker_db = sormc(TIKKER_DATABASE_NAME);
tikker_init_pragmas();
tikker_init_schema();
tikker_populate_cache();
if (argc >= 2) {
if (strcmp(argv[1], "presses_today") == 0) {
return tikker_stats_presses_today(tikker_db);
}
if (strcmp(argv[1], "stats") == 0) {
if (argc < 3) {
tikker_print_usage();
return 1;
}
return tikker_handle_stats_command(argc - 2, argv + 2);
}
if (strcmp(argv[1], "decode") == 0) {
if (argc < 3) {
fprintf(stderr, "Error: decode requires a filename\n");
tikker_print_usage();
return 1;
}
return tikker_decode_file(argv[2]);
}
if (strcmp(argv[1], "help") == 0 ||
strcmp(argv[1], "--help") == 0 ||
strcmp(argv[1], "-h") == 0) {
tikker_print_usage();
return 0;
}
if (strncmp(argv[1], "--device=", 9) == 0) {
return tikker_run_keylogger(argc, argv);
}
fprintf(stderr, "Unknown command: %s\n", argv[1]);
tikker_print_usage();
return 1;
}
return tikker_run_keylogger(argc, argv);
}

72
tikker_db.h Normal file
View File

@ -0,0 +1,72 @@
#ifndef TIKKER_DB_H
#define TIKKER_DB_H
#include <string.h>
#include <stdlib.h>
static inline char *tikker_csv_get_field(char *result, int index) {
char *end = NULL;
int current_index = 0;
while ((end = strstr((char *)result, ";")) != NULL) {
if (index == current_index) {
result[end - (char *)result] = 0;
return result;
}
result = end + 1;
current_index++;
}
if (end) *end = 0;
return result;
}
typedef struct {
char **fields;
int field_count;
} tikker_csv_row_t;
typedef struct {
char *data;
char *current;
} tikker_csv_iter_t;
static inline tikker_csv_iter_t tikker_csv_iter_init(char *csv) {
tikker_csv_iter_t iter;
iter.data = csv;
iter.current = csv;
return iter;
}
static inline int tikker_csv_is_metadata(const char *line) {
return strstr(line, "(text)") != NULL ||
strstr(line, "(integer)") != NULL ||
strstr(line, "(real)") != NULL ||
strstr(line, "(blob)") != NULL;
}
static inline int tikker_csv_iter_next(tikker_csv_iter_t *iter, char **field1, char **field2) {
while (iter->current && *iter->current) {
char *line = iter->current;
char *next = strchr(line, '\n');
if (next) *next = '\0';
iter->current = next ? next + 1 : NULL;
if (tikker_csv_is_metadata(line)) continue;
*field1 = line;
char *sep = strchr(line, ';');
if (sep) {
*sep = '\0';
*field2 = sep + 1;
char *end = strchr(*field2, ';');
if (end) *end = '\0';
} else {
*field2 = NULL;
}
return 1;
}
return 0;
}
#endif

151
tikker_decode.h Normal file
View File

@ -0,0 +1,151 @@
#ifndef TIKKER_DECODE_H
#define TIKKER_DECODE_H
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
static inline char tikker_get_shifted_char(char c) {
switch (c) {
case '1': return '!';
case '2': return '@';
case '3': return '#';
case '4': return '$';
case '5': return '%';
case '6': return '^';
case '7': return '&';
case '8': return '*';
case '9': return '(';
case '0': return ')';
case '-': return '_';
case '=': return '+';
case '[': return '{';
case ']': return '}';
case '\\': return '|';
case ';': return ':';
case '\'': return '"';
case ',': return '<';
case '.': return '>';
case '/': return '?';
case '`': return '~';
default: return c;
}
}
static inline int tikker_decode_buffer(const char *input, char *output, size_t output_size) {
size_t out_pos = 0;
int shift_active = 0;
const char *p = input;
while (*p && out_pos < output_size - 1) {
if (*p == '[') {
const char *end = strchr(p, ']');
if (!end) {
p++;
continue;
}
size_t token_len = end - p - 1;
char token[64];
if (token_len >= sizeof(token)) token_len = sizeof(token) - 1;
strncpy(token, p + 1, token_len);
token[token_len] = '\0';
if (strcmp(token, "LEFT_SHIFT") == 0 || strcmp(token, "RIGHT_SHIFT") == 0) {
shift_active = 1;
} else if (strcmp(token, "BACKSPACE") == 0) {
if (out_pos > 0) out_pos--;
} else if (strcmp(token, "ENTER") == 0) {
output[out_pos++] = '\n';
} else if (strcmp(token, "TAB") == 0) {
output[out_pos++] = '\t';
} else if (token_len == 1) {
char c = token[0];
if (shift_active) {
if (c >= 'A' && c <= 'Z') {
output[out_pos++] = c;
} else if (c >= 'a' && c <= 'z') {
output[out_pos++] = c - 32;
} else {
output[out_pos++] = tikker_get_shifted_char(c);
}
shift_active = 0;
} else {
if (c >= 'A' && c <= 'Z') {
output[out_pos++] = c + 32;
} else {
output[out_pos++] = c;
}
}
}
p = end + 1;
} else if (*p == ' ' || *p == '\n' || *p == '\t') {
output[out_pos++] = *p;
p++;
} else if ((*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z') ||
(*p >= '0' && *p <= '9') || *p == '.' || *p == ',' ||
*p == '-' || *p == '_' || *p == '\'' || *p == '"') {
char c = *p;
if (shift_active) {
if (c >= 'a' && c <= 'z') {
output[out_pos++] = c - 32;
} else {
output[out_pos++] = tikker_get_shifted_char(c);
}
shift_active = 0;
} else {
if (c >= 'A' && c <= 'Z') {
output[out_pos++] = c + 32;
} else {
output[out_pos++] = c;
}
}
p++;
} else {
p++;
}
}
output[out_pos] = '\0';
return (int)out_pos;
}
static inline int tikker_decode_file(const char *filename) {
FILE *f = fopen(filename, "r");
if (!f) {
fprintf(stderr, "Error: Cannot open file '%s'\n", filename);
return 1;
}
fseek(f, 0, SEEK_END);
long file_size = ftell(f);
fseek(f, 0, SEEK_SET);
char *input = malloc(file_size + 1);
if (!input) {
fclose(f);
fprintf(stderr, "Error: Memory allocation failed\n");
return 1;
}
size_t bytes_read = fread(input, 1, file_size, f);
input[bytes_read] = '\0';
fclose(f);
char *output = malloc(file_size + 1);
if (!output) {
free(input);
fprintf(stderr, "Error: Memory allocation failed\n");
return 1;
}
tikker_decode_buffer(input, output, file_size + 1);
printf("%s", output);
free(input);
free(output);
return 0;
}
#endif

281
tikker_stats.h Normal file
View File

@ -0,0 +1,281 @@
#ifndef TIKKER_STATS_H
#define TIKKER_STATS_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sormc.h"
#include "tikker_types.h"
#include "tikker_db.h"
#include "tikker_decode.h"
#include "tikker_words.h"
static inline int tikker_stats_presses_today(int db) {
sorm_ptr result = sormq(db,
"SELECT pressed FROM kevent_daily WHERE date = DATE('now')");
if (result) {
char *csv = (char *)result;
char *line = csv;
char *next = strchr(line, '\n');
if (next) line = next + 1;
char *end = strchr(line, ';');
if (end) *end = '\0';
end = strchr(line, '\n');
if (end) *end = '\0';
printf("%s\n", line);
free(result);
} else {
printf("0\n");
}
return 0;
}
static inline int tikker_stats_daily(int db) {
sorm_ptr result = sormq(db,
"SELECT date, pressed FROM kevent_daily ORDER BY date");
printf("date,count\n");
if (!result) return 0;
tikker_csv_iter_t iter = tikker_csv_iter_init((char *)result);
char *date, *count;
while (tikker_csv_iter_next(&iter, &date, &count)) {
if (date && count) {
printf("%s,%s\n", date, count);
}
}
free(result);
return 0;
}
static inline int tikker_stats_hourly(int db, const char *date) {
sorm_ptr result = sormq(db,
"SELECT SUBSTR(date_hour, 12, 2) as hour, pressed "
"FROM kevent_hourly WHERE date_hour LIKE %s || '.%%' "
"ORDER BY hour", date);
printf("hour,count\n");
int hour_counts[24] = {0};
if (result) {
tikker_csv_iter_t iter = tikker_csv_iter_init((char *)result);
char *hour_str, *count_str;
while (tikker_csv_iter_next(&iter, &hour_str, &count_str)) {
if (hour_str && count_str) {
int hour = atoi(hour_str);
if (hour >= 0 && hour < 24) {
hour_counts[hour] = atoi(count_str);
}
}
}
free(result);
}
for (int h = 0; h < 24; h++) {
printf("%02d,%d\n", h, hour_counts[h]);
}
return 0;
}
static inline int tikker_stats_weekly(int db) {
sorm_ptr result = sormq(db,
"SELECT STRFTIME('%%Y-%%U', date) as week, SUM(pressed) as count "
"FROM kevent_daily GROUP BY week ORDER BY week");
printf("week,count\n");
if (!result) return 0;
tikker_csv_iter_t iter = tikker_csv_iter_init((char *)result);
char *week, *count;
while (tikker_csv_iter_next(&iter, &week, &count)) {
if (week && count) {
printf("%s,%s\n", week, count);
}
}
free(result);
return 0;
}
static inline int tikker_stats_weekday(int db) {
static const char *weekday_names[] = {
"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"
};
sorm_ptr result = sormq(db,
"SELECT STRFTIME('%%w', date) as weekday, SUM(pressed) as count "
"FROM kevent_daily GROUP BY weekday ORDER BY weekday");
printf("weekday,name,count\n");
int weekday_counts[7] = {0};
if (result) {
tikker_csv_iter_t iter = tikker_csv_iter_init((char *)result);
char *wday_str, *count_str;
while (tikker_csv_iter_next(&iter, &wday_str, &count_str)) {
if (wday_str && count_str) {
int wday = atoi(wday_str);
if (wday >= 0 && wday < 7) {
weekday_counts[wday] = atoi(count_str);
}
}
}
free(result);
}
for (int d = 0; d < 7; d++) {
printf("%d,%s,%d\n", d, weekday_names[d], weekday_counts[d]);
}
return 0;
}
static inline int tikker_stats_top_keys(int db, int limit) {
char sql[512];
snprintf(sql, sizeof(sql),
"SELECT code, count FROM kevent_key_counts "
"ORDER BY count DESC LIMIT %d", limit);
sorm_ptr result = sormq(db, sql);
printf("code,name,count\n");
if (!result) return 0;
tikker_csv_iter_t iter = tikker_csv_iter_init((char *)result);
char *code_str, *count_str;
while (tikker_csv_iter_next(&iter, &code_str, &count_str)) {
if (code_str && count_str) {
int code = atoi(code_str);
const char *name = "UNKNOWN";
if (code < (int)(sizeof(tikker_keycode_to_name) / sizeof(tikker_keycode_to_name[0]))
&& tikker_keycode_to_name[code]) {
name = tikker_keycode_to_name[code];
}
printf("%s,%s,%s\n", code_str, name, count_str);
}
}
free(result);
return 0;
}
static inline int tikker_stats_summary(int db) {
sorm_ptr result = sormq(db,
"SELECT "
"SUM(pressed), SUM(released), SUM(repeated), "
"MIN(date), MAX(date), COUNT(*) "
"FROM kevent_daily");
printf("total_pressed,total_released,total_repeated,first_event,last_event,days\n");
if (!result) {
printf("0,0,0,N/A,N/A,0\n");
return 0;
}
char *csv = (char *)result;
char *line = csv;
char *next = strchr(line, '\n');
if (next) {
line = next + 1;
}
char values[6][64] = {"0", "0", "0", "N/A", "N/A", "0"};
int idx = 0;
char *start = line;
while (*start && idx < 6) {
char *end = strchr(start, ';');
if (!end) end = start + strlen(start);
size_t len = end - start;
if (len >= sizeof(values[0])) len = sizeof(values[0]) - 1;
strncpy(values[idx], start, len);
values[idx][len] = '\0';
idx++;
if (*end) start = end + 1;
else break;
}
printf("%s,%s,%s,%s,%s,%s\n", values[0], values[1], values[2], values[3], values[4], values[5]);
free(result);
return 0;
}
static inline int tikker_stats_top_words(int db, int limit) {
sorm_ptr result = sormq(db,
"SELECT STRFTIME('%%Y-%%m-%%d.%%H', timestamp) as date_hour, "
"GROUP_CONCAT(char, '') as chars "
"FROM kevent WHERE event = 'PRESSED' "
"GROUP BY date_hour ORDER BY date_hour");
printf("word,count\n");
if (!result) return 0;
size_t total_len = strlen((char *)result);
char *decoded = malloc(total_len + 1);
if (!decoded) {
free(result);
return 1;
}
char *csv = (char *)result;
char *line = csv;
char *next;
size_t decoded_pos = 0;
while (line && *line) {
next = strchr(line, '\n');
if (next) *next = '\0';
char *chars = strchr(line, ';');
if (chars) {
chars++;
char *end = strchr(chars, ';');
if (end) *end = '\0';
int len = tikker_decode_buffer(chars, decoded + decoded_pos, total_len - decoded_pos);
decoded_pos += len;
if (decoded_pos < total_len) {
decoded[decoded_pos++] = ' ';
}
}
if (next) line = next + 1;
else break;
}
decoded[decoded_pos] = '\0';
tikker_word_count_t *words = calloc(TIKKER_MAX_WORDS, sizeof(tikker_word_count_t));
if (!words) {
free(decoded);
free(result);
return 1;
}
int word_count = tikker_extract_words(decoded, words, TIKKER_MAX_WORDS);
tikker_sort_words(words, word_count);
int output_count = (limit < word_count) ? limit : word_count;
for (int i = 0; i < output_count; i++) {
printf("%s,%d\n", words[i].word, words[i].count);
}
free(words);
free(decoded);
free(result);
return 0;
}
#endif

81
tikker_types.h Normal file
View File

@ -0,0 +1,81 @@
#ifndef TIKKER_TYPES_H
#define TIKKER_TYPES_H
#include <time.h>
#define TIKKER_DATABASE_NAME "tikker.db"
#define TIKKER_DEVICE_DEFAULT "keyboard"
#define TIKKER_MAX_DEVICES 32
#define TIKKER_DEVICE_PATH "/dev/input/event"
#define TIKKER_MAX_WORDS 100000
#define TIKKER_MAX_WORD_LEN 256
typedef struct {
char word[TIKKER_MAX_WORD_LEN];
int count;
} tikker_word_count_t;
static const char *tikker_keycode_to_char[] = {
[2] = "1", [3] = "2", [4] = "3", [5] = "4", [6] = "5",
[7] = "6", [8] = "7", [9] = "8", [10] = "9", [11] = "0",
[12] = "-", [13] = "=", [14] = "[BACKSPACE]", [15] = "[TAB]",
[16] = "Q", [17] = "W", [18] = "E", [19] = "R", [20] = "T",
[21] = "Y", [22] = "U", [23] = "I", [24] = "O", [25] = "P",
[26] = "[", [27] = "]", [28] = "[ENTER]\n", [29] = "[LEFT_CTRL]",
[30] = "A", [31] = "S", [32] = "D", [33] = "F", [34] = "G",
[35] = "H", [36] = "J", [37] = "K", [38] = "L", [39] = ";",
[40] = "'", [41] = "`", [42] = "[LEFT_SHIFT]", [43] = "\\",
[44] = "Z", [45] = "X", [46] = "C", [47] = "V", [48] = "B",
[49] = "N", [50] = "M", [51] = ",", [52] = ".", [53] = "/",
[54] = "[RIGHT_SHIFT]", [55] = "[KEYPAD_*]", [56] = "[LEFT_ALT]",
[57] = " ", [58] = "[CAPSLOCK]",
[59] = "[F1]", [60] = "[F2]", [61] = "[F3]", [62] = "[F4]",
[63] = "[F5]", [64] = "[F6]", [65] = "[F7]", [66] = "[F8]",
[67] = "[F9]", [68] = "[F10]", [87] = "[F11]", [88] = "[F12]",
[69] = "[NUMLOCK]", [70] = "[SCROLLLOCK]", [71] = "[KEYPAD_7]",
[72] = "[KEYPAD_8]", [73] = "[KEYPAD_9]", [74] = "[KEYPAD_-]",
[75] = "[KEYPAD_4]", [76] = "[KEYPAD_5]", [77] = "[KEYPAD_6]",
[78] = "[KEYPAD_+]", [79] = "[KEYPAD_1]", [80] = "[KEYPAD_2]",
[81] = "[KEYPAD_3]", [82] = "[KEYPAD_0]", [83] = "[KEYPAD_.]",
[86] = "<", [100] = "[RIGHT_ALT]", [97] = "[RIGHT_CTRL]",
[119] = "[PAUSE]", [120] = "[SYSRQ]", [121] = "[BREAK]",
[102] = "[HOME]", [103] = "[UP]", [104] = "[PAGEUP]",
[105] = "[LEFT]", [106] = "[RIGHT]", [107] = "[END]",
[108] = "[DOWN]", [109] = "[PAGEDOWN]", [110] = "[INSERT]",
[111] = "[DELETE]",
[113] = "[MUTE]", [114] = "[VOLUME_DOWN]", [115] = "[VOLUME_UP]",
[163] = "[MEDIA_NEXT]", [165] = "[MEDIA_PREV]", [164] = "[MEDIA_PLAY_PAUSE]"
};
static const char *tikker_keycode_to_name[] = {
[2] = "1", [3] = "2", [4] = "3", [5] = "4", [6] = "5",
[7] = "6", [8] = "7", [9] = "8", [10] = "9", [11] = "0",
[12] = "MINUS", [13] = "EQUAL", [14] = "BACKSPACE", [15] = "TAB",
[16] = "Q", [17] = "W", [18] = "E", [19] = "R", [20] = "T",
[21] = "Y", [22] = "U", [23] = "I", [24] = "O", [25] = "P",
[26] = "LEFTBRACE", [27] = "RIGHTBRACE", [28] = "ENTER", [29] = "LEFTCTRL",
[30] = "A", [31] = "S", [32] = "D", [33] = "F", [34] = "G",
[35] = "H", [36] = "J", [37] = "K", [38] = "L", [39] = "SEMICOLON",
[40] = "APOSTROPHE", [41] = "GRAVE", [42] = "LEFTSHIFT", [43] = "BACKSLASH",
[44] = "Z", [45] = "X", [46] = "C", [47] = "V", [48] = "B",
[49] = "N", [50] = "M", [51] = "COMMA", [52] = "DOT", [53] = "SLASH",
[54] = "RIGHTSHIFT", [55] = "KPASTERISK", [56] = "LEFTALT",
[57] = "SPACE", [58] = "CAPSLOCK",
[59] = "F1", [60] = "F2", [61] = "F3", [62] = "F4",
[63] = "F5", [64] = "F6", [65] = "F7", [66] = "F8",
[67] = "F9", [68] = "F10", [87] = "F11", [88] = "F12",
[97] = "RIGHTCTRL", [100] = "RIGHTALT",
[102] = "HOME", [103] = "UP", [104] = "PAGEUP",
[105] = "LEFT", [106] = "RIGHT", [107] = "END",
[108] = "DOWN", [109] = "PAGEDOWN", [110] = "INSERT",
[111] = "DELETE"
};
static inline char *tikker_get_today_date(void) {
static char date_str[11];
time_t now = time(NULL);
strftime(date_str, sizeof(date_str), "%Y-%m-%d", localtime(&now));
return date_str;
}
#endif

76
tikker_words.h Normal file
View File

@ -0,0 +1,76 @@
#ifndef TIKKER_WORDS_H
#define TIKKER_WORDS_H
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "tikker_types.h"
static inline int tikker_is_valid_word_char(char c) {
return isalnum((unsigned char)c) || c == '_';
}
static inline int tikker_word_count_compare(const void *a, const void *b) {
return ((tikker_word_count_t *)b)->count - ((tikker_word_count_t *)a)->count;
}
static inline int tikker_extract_words(const char *text, tikker_word_count_t *words, int max_words) {
int word_count = 0;
const char *p = text;
char word[TIKKER_MAX_WORD_LEN];
int word_len = 0;
while (*p) {
if (tikker_is_valid_word_char(*p)) {
if (word_len < TIKKER_MAX_WORD_LEN - 1) {
word[word_len++] = tolower((unsigned char)*p);
}
} else {
if (word_len >= 2) {
word[word_len] = '\0';
int found = 0;
for (int i = 0; i < word_count; i++) {
if (strcmp(words[i].word, word) == 0) {
words[i].count++;
found = 1;
break;
}
}
if (!found && word_count < max_words) {
strcpy(words[word_count].word, word);
words[word_count].count = 1;
word_count++;
}
}
word_len = 0;
}
p++;
}
if (word_len >= 2) {
word[word_len] = '\0';
int found = 0;
for (int i = 0; i < word_count; i++) {
if (strcmp(words[i].word, word) == 0) {
words[i].count++;
found = 1;
break;
}
}
if (!found && word_count < max_words) {
strcpy(words[word_count].word, word);
words[word_count].count = 1;
word_count++;
}
}
return word_count;
}
static inline void tikker_sort_words(tikker_word_count_t *words, int count) {
qsort(words, count, sizeof(tikker_word_count_t), tikker_word_count_compare);
}
#endif