From f4a1b3264ab1310cba761077dd7a8e773f9a3843 Mon Sep 17 00:00:00 2001 From: TheBrokenRail Date: Wed, 9 Sep 2020 20:44:59 -0400 Subject: [PATCH] Initial Commit --- .gitignore | 1 + .vscode/launch.json | 29 ++ .vscode/settings.json | 7 + .vscode/tasks.json | 26 + CMakeLists.txt | 37 ++ LICENSE | 21 + README.md | 2 + ...brokenrail.FeedbackD-Configuration.desktop | 8 + ....thebrokenrail.FeedbackD-Configuration.png | Bin 0 -> 2031 bytes src/.gresource.xml | 7 + src/dialog.c | 165 ++++++ src/dialog.h | 11 + src/dialog_window.glade | 475 ++++++++++++++++++ src/main.c | 278 ++++++++++ src/main.h | 17 + src/main_window.glade | 136 +++++ src/util.c | 46 ++ src/util.h | 25 + 18 files changed, 1291 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 data/com.thebrokenrail.FeedbackD-Configuration.desktop create mode 100644 data/com.thebrokenrail.FeedbackD-Configuration.png create mode 100644 src/.gresource.xml create mode 100644 src/dialog.c create mode 100644 src/dialog.h create mode 100644 src/dialog_window.glade create mode 100644 src/main.c create mode 100644 src/main.h create mode 100644 src/main_window.glade create mode 100644 src/util.c create mode 100644 src/util.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c08ce63 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "clang - Build and debug active file", + "type": "cppdbg", + "request": "launch", + "program": "${fileDirname}/${fileBasenameNoExtension}", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "lldb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + "preLaunchTask": "C/C++: clang build active file", + "miDebuggerPath": "/usr/bin/lldb-mi" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a997812 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "cmake.configureOnOpen": true, + "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", + "files.associations": { + "limits": "c" + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..decc719 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,26 @@ +{ + "tasks": [ + { + "type": "shell", + "label": "C/C++: clang build active file", + "command": "/usr/bin/clang", + "args": [ + "-g", + "${file}", + "-o", + "${fileDirname}/${fileBasenameNoExtension}" + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + } + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b668716 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.1.0) + +project(feedbackd-configuration) + +find_package(PkgConfig REQUIRED) + +pkg_check_modules(GTK3 REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(LIBHANDY REQUIRED IMPORTED_TARGET libhandy-1) +pkg_check_modules(JSON_GLIB REQUIRED IMPORTED_TARGET json-glib-1.0) + +link_libraries(PkgConfig::GTK3 PkgConfig::LIBHANDY PkgConfig::JSON_GLIB) + +add_compile_options(-Wall -Wextra -Werror) + +add_custom_command( + OUTPUT "${CMAKE_BINARY_DIR}/.gresource.c" non-existent.c + COMMAND glib-compile-resources + ARGS --target "${CMAKE_BINARY_DIR}/.gresource.c" --generate-source --sourcedir "${CMAKE_SOURCE_DIR}/src" "${CMAKE_SOURCE_DIR}/src/.gresource.xml" +) + +function(add_debug_flag x) + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + add_compile_options("${x}") + add_link_options("${x}") + endif() +endfunction() + +add_debug_flag("-fno-omit-frame-pointer;-fsanitize=address") + +add_executable(feedbackd-configuration "${CMAKE_BINARY_DIR}/.gresource.c" src/main.c src/dialog.c src/util.c) + +install(TARGETS feedbackd-configuration) + +include(GNUInstallDirs) + +install(FILES "data/com.thebrokenrail.FeedbackD-Configuration.desktop" DESTINATION "${CMAKE_INSTALL_FULL_DATAROOTDIR}/applications") +install(FILES "data/com.thebrokenrail.FeedbackD-Configuration.png" DESTINATION "${CMAKE_INSTALL_FULL_DATAROOTDIR}/icons/hicolor/96x96/apps") \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..886b254 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 TheBrokenRail + +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 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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8696f0c --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# FeedbackD Configuration +A Simple Configuration Tool For [FeedbackD](https://source.puri.sm/Librem5/feedbackd) diff --git a/data/com.thebrokenrail.FeedbackD-Configuration.desktop b/data/com.thebrokenrail.FeedbackD-Configuration.desktop new file mode 100644 index 0000000..d64e7ab --- /dev/null +++ b/data/com.thebrokenrail.FeedbackD-Configuration.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Name=FeedbackD Configuration +Exec=feedbackd-configuration +Icon=com.thebrokenrail.FeedbackD-Configuration +Terminal=false +Type=Application +Categories=GTK; +StartupNotify=true \ No newline at end of file diff --git a/data/com.thebrokenrail.FeedbackD-Configuration.png b/data/com.thebrokenrail.FeedbackD-Configuration.png new file mode 100644 index 0000000000000000000000000000000000000000..2c28b3ca3095898b7bd7c2b70edbc64d296c1513 GIT binary patch literal 2031 zcmVaTcv(fI@m$fAww017K(y6Y8|>1DYb%Cm*h2Rg(M9xDNZLG90D{+H(ia2WM71q3u_>z!nt}Ig@ zr5cBgEK*29f`SS*p~FF(x=ERJ_9Ys=C6vj-=xLz}PL3i<+5%7;@B3Rfu*k!4X3 z1-YRJhmefm_{#q|M-1%3gVyJ5lb=)~QIeBz^eEe{B zcJ^_YvRVny+S(fL>+Acvy1F`v0$mGJ0N>MsLGJYQbY^*Zd46|yH_xpPTY$mA!S1@c zx+Q>b=4MvO_7GwZE&r>rv9T3SJ!?3|=x)9N{QH34#7P$gTVjH)sk^PM?d^??jUP?x zY}^8dhld|f2BwaqYzHw`&=Pt~7#OvHk&%%!;NLNEC^oSeSbf~t+1a?hzWzwZLn{FF z`RLykb&llvYXsUlIy#;}geNLC)vAj$+rhCUaP0`-#$6(Y)&kH}^Wx${W;=HLY0c0~ z46OyAdA?=dlB_@gi#H|1z%Hn;!j1GI%f(%U9^_hofB$Dez5)fH@Pdm1h>uvTp`qb( zwg6~4fvO2q9)a50T8fhuJpx+*1h~P=My!JffaOPQ0Vt{|NZk+s%fPWTumx0VNZAci zo|m(;v%mz(g%~dzv9=)~0@zTKG(tcK2mv7=1cZPP5CS0y7(Wx%o{-W{Ged_ z2Y!FS`3&c`A#qN<$k=!PHcZXaVDNn&R)B(!qM7>c-<;$> zmNs1wH;-cW=PsNme0bxZEdX9u`78@2$*;Ad_oMeJ2ReD}}7VW;4uuzPVIlP)<%;PfJ&>Up4Gp+yZAsJy zzWWbZeMv3^gn$qb0zyCt2!To?5I7AT7#K)mV~()X$4cX6RUd3;lbV{EnkkkzFc^o; zh}NuflSHo(P_YaYU~X=13!wlPk;zC)fc-PTq(fn>S`YU@Jk@RV_AggCL?CJ`+JF@ih_2(AZ)+2gMDur z;iN6PIQccl%;)a(^t5*WIIRGbHSPQa5#EEUpYV$ky%gv*<}vLYHL1Cg00j}g$A)x` z=+~qr1dyd}hwgk+_~eG&xnm2no7hF`JK4zTWff>MSIqYl>2&&6lRN*)hAjXkOe7L- zwY0R{!Adz9bs+%$X+EEy!HhAh8*qyWVX>@ed2|Jq?vs{$FEI!abui#nry|QE; zkZGg@AO`5K`ym2rpe&_ekrM!|02<_bFkY!%k1QqgmwZP~0IEGi*u!G!3;G^FxsON+ zK+q>9CLW_D+!X=JNB|R3b^pai-+_v9u_?Z#v;IfAWlwBrqBwsU%ha^ z{a+U%z$-Mjx3_!fi%2dZ%OK34nayT%hlhu^|F1=ww16Vw;Olt5(Ko>ux>=iw00HO_ zZoY@0UB;TMVL}l=0VfWfVe^}!kj<&wN7D*_k@Kqlkkr`xU?>2YA#&P_^?}oul|=xC z0EnFC@G1(uf>V~rDWj;BZHm8d^;LEarFKF<2nYcoAOwVf5GXZ)zX1-Qi!L=T)K~xj N002ovPDHLkV1glex`6-y literal 0 HcmV?d00001 diff --git a/src/.gresource.xml b/src/.gresource.xml new file mode 100644 index 0000000..b705582 --- /dev/null +++ b/src/.gresource.xml @@ -0,0 +1,7 @@ + + + + main_window.glade + dialog_window.glade + + \ No newline at end of file diff --git a/src/dialog.c b/src/dialog.c new file mode 100644 index 0000000..a57b369 --- /dev/null +++ b/src/dialog.c @@ -0,0 +1,165 @@ +#include +#include + +#include "main.h" + +// Context For The Save/Modify Event Dialog +struct save_event_data { + struct event_location *location; + GtkWindow *window; + GtkEntry *name; + GtkStack *stack; + // Sound + GtkWidget *sound; + GtkEntry *sound_name; + // Vibration (Rumble) + GtkWidget *vibration_rumble; + GtkEntry *vibration_rumble_duration; + // Vibration (Periodic) + GtkWidget *vibration_periodic; + GtkEntry *magnitude; + GtkEntry *vibration_periodic_duration; + GtkEntry *fade_in_time; + GtkEntry *fade_in_level; + // LED + GtkWidget *led; + GtkEntry *color; + GtkEntry *frequency; +}; + +// Save Event Handler +static void on_save_event(__attribute__((unused)) GtkButton *button, struct save_event_data *data) { + JsonObject *event = json_object_new(); + int free_obj = 0; + if (data->location->id != -1) { + JsonNode *node = json_array_get_element(data->location->profile, data->location->id); + json_node_set_object(node, event); + free_obj = 1; + } else { + json_array_add_object_element(data->location->profile, event); + } + + json_object_set_string_member(event, "event-name", gtk_entry_get_text(data->name)); + + GtkWidget *visible_widget = gtk_stack_get_visible_child(data->stack); + + if (visible_widget == data->sound) { + json_object_set_string_member(event, "type", "Sound"); + json_object_set_string_member(event, "effect", gtk_entry_get_text(data->sound_name)); + } else if (visible_widget == data->vibration_rumble) { + json_object_set_string_member(event, "type", "VibraRumble"); + json_object_set_double_member(event, "duration", g_strtod(gtk_entry_get_text(data->vibration_rumble_duration), NULL)); + } else if (visible_widget == data->vibration_periodic) { + json_object_set_string_member(event, "type", "VibraPeriodic"); + json_object_set_double_member(event, "magnitude", g_strtod(gtk_entry_get_text(data->magnitude), NULL)); + json_object_set_double_member(event, "duration", g_strtod(gtk_entry_get_text(data->vibration_periodic_duration), NULL)); + json_object_set_double_member(event, "fade-in-time", g_strtod(gtk_entry_get_text(data->fade_in_time), NULL)); + json_object_set_double_member(event, "fade-in-level", g_strtod(gtk_entry_get_text(data->fade_in_level), NULL)); + } else if (visible_widget == data->led) { + json_object_set_string_member(event, "type", "Led"); + json_object_set_string_member(event, "color", gtk_entry_get_text(data->color)); + json_object_set_double_member(event, "frequency", g_strtod(gtk_entry_get_text(data->frequency), NULL)); + } + + gtk_window_close(GTK_WINDOW(data->window)); + + reload_profiles(1); + + if (free_obj) { + json_object_unref(event); + } +} + +// Sets Long In GtkEntry Text Field +static void set_entry_long(GtkEntry *entry, long x) { + gchar *str = g_strdup_printf("%ld", x); + gtk_entry_set_text(entry, str); + g_free(str); +} + +// Pre-Fill Dialog With Existing Event Data +static void apply_event_to_dialog(struct save_event_data *data) { + if (data->location->id != -1) { + JsonObject *event = json_array_get_object_element(data->location->profile, data->location->id); + + gtk_entry_set_text(data->name, json_object_get_string_member(event, "event-name")); + + const gchar *type = json_object_get_string_member(event, "type"); + + if (g_strcmp0(type, "Sound") == 0) { + gtk_stack_set_visible_child(data->stack, data->sound); + gtk_entry_set_text(data->sound_name, json_object_get_string_member(event, "effect")); + } else if (g_strcmp0(type, "VibraRumble") == 0) { + gtk_stack_set_visible_child(data->stack, data->vibration_rumble); + set_entry_long(data->vibration_rumble_duration, json_object_get_int_member(event, "duration")); + } else if (g_strcmp0(type, "VibraPeriodic") == 0) { + gtk_stack_set_visible_child(data->stack, data->vibration_periodic); + set_entry_long(data->magnitude, json_object_get_int_member(event, "magnitude")); + set_entry_long(data->vibration_periodic_duration, json_object_get_int_member(event, "duration")); + set_entry_long(data->fade_in_time, json_object_get_int_member(event, "fade-in-time")); + set_entry_long(data->fade_in_level, json_object_get_int_member(event, "fade-in-level")); + } else if (g_strcmp0(type, "Led") == 0) { + gtk_stack_set_visible_child(data->stack, data->led); + gtk_entry_set_text(data->color, json_object_get_string_member(event, "color")); + set_entry_long(data->frequency, json_object_get_int_member(event, "frequency")); + } + } else { + gtk_stack_set_visible_child(data->stack, data->sound); + } +} + +// Open Create/Modify Event Dialog +void open_dialog(GtkWidget *widget, struct event_location *location, int free_location) { + GtkBuilder *builder = gtk_builder_new(); + + GError *err = NULL; + if (gtk_builder_add_from_resource(builder, "/dialog_window.glade", &err) == 0) { + g_error("Error Loading UI: %s", err->message); + } + + GtkWindow *window = GTK_WINDOW(gtk_builder_get_object(builder, "dialog_window")); + + gtk_builder_connect_signals(builder, NULL); + + struct save_event_data *data = malloc(sizeof (struct save_event_data)); + data->location = location; + data->window = window; + data->name = GTK_ENTRY(gtk_builder_get_object(builder, "name")); + data->stack = GTK_STACK(gtk_builder_get_object(builder, "stack")); + // Sound + data->sound = GTK_WIDGET(gtk_builder_get_object(builder, "sound")); + data->sound_name = GTK_ENTRY(gtk_builder_get_object(builder, "sound-name")); + // Vibration (Rumble) + data->vibration_rumble = GTK_WIDGET(gtk_builder_get_object(builder, "vibration-rumble")); + data->vibration_rumble_duration = GTK_ENTRY(gtk_builder_get_object(builder, "vibration-rumble-duration")); + // Vibration (Periodic) + data->vibration_periodic = GTK_WIDGET(gtk_builder_get_object(builder, "vibration-periodic")); + data->magnitude = GTK_ENTRY(gtk_builder_get_object(builder, "magnitude")); + data->vibration_periodic_duration = GTK_ENTRY(gtk_builder_get_object(builder, "vibration-periodic-duration")); + data->fade_in_time = GTK_ENTRY(gtk_builder_get_object(builder, "fade-in-time")); + data->fade_in_level = GTK_ENTRY(gtk_builder_get_object(builder, "fade-in-level")); + // LED + data->led = GTK_WIDGET(gtk_builder_get_object(builder, "led")); + data->color = GTK_ENTRY(gtk_builder_get_object(builder, "color")); + data->frequency = GTK_ENTRY(gtk_builder_get_object(builder, "frequency")); + + g_signal_connect(gtk_builder_get_object(builder, "save"), "clicked", G_CALLBACK(on_save_event), data); + + g_signal_connect(window, "destroy", G_CALLBACK(free_userdata), data); + if (free_location) { + g_signal_connect(window, "destroy", G_CALLBACK(free_userdata), location); + } + + g_object_unref(builder); + + apply_event_to_dialog(data); + + GtkWindow *parent_window = GTK_WINDOW(gtk_widget_get_toplevel(widget)); + + gtk_window_set_application(window, gtk_window_get_application(parent_window)); + + gtk_window_set_modal(window, 1); + gtk_window_set_transient_for(window, parent_window); + + gtk_window_present(window); +} \ No newline at end of file diff --git a/src/dialog.h b/src/dialog.h new file mode 100644 index 0000000..178cdb0 --- /dev/null +++ b/src/dialog.h @@ -0,0 +1,11 @@ +#ifndef DIALOG_H + +#define DIALOG_H + +#include + +#include "main.h" + +void open_dialog(GtkWidget *, struct event_location *, int); + +#endif \ No newline at end of file diff --git a/src/dialog_window.glade b/src/dialog_window.glade new file mode 100644 index 0000000..47c6fbb --- /dev/null +++ b/src/dialog_window.glade @@ -0,0 +1,475 @@ + + + + + + + False + center-on-parent + dialog + + + True + False + vertical + + + True + False + start + Create/Modify Event + True + + + Save + True + True + True + + + end + + + + + False + True + 0 + + + + + True + False + 16 + 16 + 16 + 16 + vertical + + + True + False + + + True + False + Name: + + + False + True + 4 + 0 + + + + + True + True + + + True + True + 4 + 1 + + + + + False + True + 4 + 0 + + + + + True + False + end + stack + + + False + True + 4 + 1 + + + + + True + False + crossfade + + + True + False + start + vertical + + + True + False + + + True + False + Sound Name: + + + False + True + 4 + 0 + + + + + True + True + + + True + True + 4 + 1 + + + + + False + True + 4 + 2 + + + + + Sound + + + + + True + False + start + vertical + + + True + False + + + True + False + Duration: + + + False + True + 4 + 0 + + + + + True + True + digits + + + True + True + 4 + 1 + + + + + False + True + 4 + 0 + + + + + Vibration (Rumble) + 1 + + + + + True + False + start + vertical + + + True + False + start + + + True + False + Magnitude: + + + False + True + 4 + 0 + + + + + True + True + digits + + + True + True + 4 + 1 + + + + + False + True + 4 + 0 + + + + + True + False + start + + + True + False + Duration: + + + False + True + 4 + 0 + + + + + True + True + digits + + + True + True + 4 + 1 + + + + + False + True + 4 + 1 + + + + + True + False + start + + + True + False + Fade In Time: + + + False + True + 4 + 0 + + + + + True + True + digits + + + True + True + 4 + 1 + + + + + False + True + 4 + 2 + + + + + True + False + start + + + True + False + Fade In Level: + + + False + True + 4 + 0 + + + + + True + True + digits + + + True + True + 4 + 1 + + + + + False + True + 4 + 3 + + + + + Vibration (Periodic) + 2 + + + + + True + False + start + vertical + + + True + False + + + True + False + Color: + + + False + True + 4 + 0 + + + + + True + True + + + True + True + 4 + 1 + + + + + False + True + 4 + 0 + + + + + True + False + + + True + False + Frequency: + + + False + True + 4 + 0 + + + + + True + True + digits + + + True + True + 4 + 1 + + + + + False + True + 4 + 1 + + + + + LED + 3 + + + + + False + True + 2 + + + + + False + True + 1 + + + + + + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..ba938c9 --- /dev/null +++ b/src/main.c @@ -0,0 +1,278 @@ +#include +#include +#include +#include + +#include "main.h" +#include "dialog.h" +#include "util.h" + +static void on_startup() { + hdy_init(); +} + +#define CONFIG_JSON "/usr/share/feedbackd/themes/default.json" +#define ADMIN_CONFIG_JSON "admin://"CONFIG_JSON + +JsonNode *root = NULL; + +// Extracts Profile Obj From JSON Root (Only Runs In Init) +static JsonArray *get_profile(gchar *name) { + if (root != NULL) { + JsonObject *obj = json_node_get_object(root); + if (obj != NULL) { + JsonArray *profiles = json_object_get_array_member(obj, "profiles"); + if (profiles != NULL) { + int length = json_array_get_length(profiles); + for (int i = 0; i < length; i++) { + JsonObject *element = json_array_get_object_element(profiles, i); + if (element != NULL) { + const gchar *elementName = json_object_get_string_member(element, "name"); + if (g_strcmp0(name, elementName) == 0) { + return json_object_get_array_member(element, "feedbacks"); + } + } + } + } + } + } + return NULL; +} + +#define LABEL_MARGIN 16 + +// Event Delete Handler +static void on_delete(__attribute__((unused)) GtkButton *button, struct event_location *data) { + json_array_remove_element(data->profile, data->id); + reload_profiles(1); +} + +// Handler For Freeing Userdata +void free_userdata(__attribute__((unused)) GtkWidget *widget, void *userdata) { + free(userdata); +} + +// Edit Event Row Handler +static void on_edit(GtkWidget *button, struct event_location *data) { + open_dialog(button, data, 0); +} + +// Load Profile Into List +static void load_profile(GtkListBox *list) { + char *name = profile_list_to_name(list); + + gtk_container_foreach(GTK_CONTAINER(list), (GtkCallback) gtk_widget_destroy, NULL); + + JsonArray *profile = name_to_profile(name); + if (profile != NULL && list != NULL) { + int length = json_array_get_length(profile); + for (int i = 0; i < length; i++) { + JsonObject *event = json_array_get_object_element(profile, i); + if (event != NULL) { + const gchar *event_name = json_object_get_string_member(event, "event-name"); + if (event_name != NULL) { + GtkWidget *row = gtk_list_box_row_new(); + gtk_widget_set_visible(row, 1); + + GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_set_visible(box, 1); + + gtk_widget_set_halign(box, GTK_ALIGN_FILL); + + GtkWidget *label = gtk_label_new(event_name); + gtk_widget_set_visible(label, 1); + + gtk_label_set_xalign(GTK_LABEL(label), 0); + gtk_label_set_lines(GTK_LABEL(label), 2); + gtk_label_set_line_wrap(GTK_LABEL(label), 1); + gtk_label_set_line_wrap_mode(GTK_LABEL(label), PANGO_WRAP_WORD_CHAR); + gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END); + + gtk_widget_set_margin_start(label, LABEL_MARGIN); + gtk_widget_set_margin_end(label, LABEL_MARGIN); + gtk_widget_set_margin_top(label, LABEL_MARGIN); + gtk_widget_set_margin_bottom(label, LABEL_MARGIN); + + gtk_box_pack_start(GTK_BOX(box), label, 0, 0, 0); + + GtkWidget *edit_button = gtk_button_new_with_label("Edit"); + gtk_widget_set_visible(edit_button, 1); + + gtk_widget_set_halign(edit_button, GTK_ALIGN_END); + gtk_widget_set_valign(edit_button, GTK_ALIGN_CENTER); + + struct event_location *location = malloc(sizeof (struct event_location)); + location->profile = profile; + location->id = i; + + g_signal_connect(edit_button, "clicked", G_CALLBACK(on_edit), location); + + gtk_widget_set_margin_end(edit_button, LABEL_MARGIN); + gtk_widget_set_margin_top(edit_button, LABEL_MARGIN); + gtk_widget_set_margin_bottom(edit_button, LABEL_MARGIN); + + GtkWidget *delete_button = gtk_button_new_with_label("Delete"); + gtk_widget_set_visible(delete_button, 1); + + gtk_widget_set_halign(delete_button, GTK_ALIGN_END); + gtk_widget_set_valign(delete_button, GTK_ALIGN_CENTER); + + g_signal_connect(delete_button, "clicked", G_CALLBACK(on_delete), location); + + gtk_widget_set_margin_end(delete_button, LABEL_MARGIN); + gtk_widget_set_margin_top(delete_button, LABEL_MARGIN); + gtk_widget_set_margin_bottom(delete_button, LABEL_MARGIN); + + gtk_box_pack_end(GTK_BOX(box), delete_button, 0, 0, 0); + gtk_box_pack_end(GTK_BOX(box), edit_button, 0, 0, 0); + + gtk_container_add(GTK_CONTAINER(row), box); + + g_signal_connect(row, "destroy", G_CALLBACK(free_userdata), location); + + gtk_container_add(GTK_CONTAINER(list), row); + } + } + } + } +} + +// JSON File On admin:// +GFile *file; + +// Reload And Save Profiles +void reload_profiles(int save) { + load_profile(get_app_data()->full_list); + load_profile(get_app_data()->quiet_list); + load_profile(get_app_data()->silent_list); + + if (save) { + JsonGenerator *generator = json_generator_new(); + + json_generator_set_root(generator, root); + json_generator_set_pretty(generator, 1); + json_generator_set_indent(generator, 4); + json_generator_set_indent_char(generator, ' '); + + GError *open_err = NULL; + GFileOutputStream *stream = g_file_replace(file, NULL, 0, G_FILE_CREATE_NONE, NULL, &open_err); + if (stream == NULL) { + g_error("Error Opening File Stream: %s", open_err->message); + } + + GError *write_err = NULL; + if (json_generator_to_stream(generator, G_OUTPUT_STREAM(stream), NULL, &write_err) == 0) { + g_error("Error Saving JSON: %s", write_err->message); + } + + GError *close_err = NULL; + if (g_output_stream_close(G_OUTPUT_STREAM(stream), NULL, &close_err) == 0) { + g_error("Error Closing File Stream: %s", close_err->message); + } + g_object_unref(stream); + + // Restart FeedbackD + system("killall feedbackd"); + + g_object_unref(generator); + } +} + +// New Event Handler +void on_new_event(GtkButton *button, GtkStack *stack) { + struct event_location *location = malloc(sizeof (struct event_location)); + location->id = -1; + GtkListBox *visible_widget = GTK_LIST_BOX(gtk_stack_get_visible_child(stack)); + location->profile = name_to_profile(profile_list_to_name(visible_widget)); + open_dialog(GTK_WIDGET(button), location, 1); +} + +// Finish Mount Handler +static void finish_mount(__attribute__((unused)) GObject *object, GAsyncResult *result, __attribute__((unused)) gpointer user_data) { + GError *err = NULL; + if (g_file_mount_enclosing_volume_finish(file, result, &err) == 0) { + g_error("Error Mounting JSON: %s", err->message); + } +} + +// App Start Handler +static void on_activate(GtkApplication *app) { + g_assert(GTK_IS_APPLICATION(app)); + + GtkWindow *window = gtk_application_get_active_window(app); + + if (window == NULL) { + GtkBuilder *builder = gtk_builder_new(); + + GError *err = NULL; + if (gtk_builder_add_from_resource(builder, "/main_window.glade", &err) == 0) { + g_error("Error Loading UI: %s", err->message); + } + + window = GTK_WINDOW(gtk_builder_get_object(builder, "main_window")); + + gtk_builder_connect_signals(builder, NULL); + + JsonParser *parser = json_parser_new(); + + GError *json_err = NULL; + if (json_parser_load_from_file(parser, CONFIG_JSON, &json_err) == 0) { + g_error("Error Parsing JSON: %s", json_err->message); + } + + root = json_node_copy(json_parser_get_root(parser)); + + init_app_data(); + + get_app_data()->full_list = GTK_LIST_BOX(gtk_builder_get_object(builder, "full")); + get_app_data()->full = get_profile("full"); + get_app_data()->quiet_list = GTK_LIST_BOX(gtk_builder_get_object(builder, "quiet")); + get_app_data()->quiet = get_profile("quiet"); + get_app_data()->silent_list = GTK_LIST_BOX(gtk_builder_get_object(builder, "silent")); + get_app_data()->silent = get_profile("silent"); + + reload_profiles(0); + + g_object_unref(parser); + + g_object_unref(builder); + + gtk_window_set_application(window, app); + + file = g_file_new_for_uri(ADMIN_CONFIG_JSON); + + // Mount File + GMountOperation *operation = gtk_mount_operation_new(window); + g_file_mount_enclosing_volume(file, G_MOUNT_MOUNT_NONE, operation, NULL, finish_mount, NULL); + g_object_unref(operation); + } + + gtk_window_present(window); +} + +// Memory Cleanup +static void cleanup() { + if (root != NULL) { + json_node_free(root); + } + free_app_data(); + if (file != NULL) { + g_object_unref(file); + } +} + +// Main +int main(int argc, char *argv[]) { + GtkApplication *app = gtk_application_new("com.thebrokenrail.FeedbackD-Configuration", G_APPLICATION_FLAGS_NONE); + + g_signal_connect(app, "startup", G_CALLBACK(on_startup), NULL); + g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL); + + int ret = g_application_run(G_APPLICATION(app), argc, argv); + + cleanup(); + + g_object_unref(app); + + return ret; +} \ No newline at end of file diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..73afbc7 --- /dev/null +++ b/src/main.h @@ -0,0 +1,17 @@ +#ifndef MAIN_H + +#define MAIN_H + +#include + +// Represnts One Item In A Profile +struct event_location { + JsonArray *profile; + int id; +}; + +void free_userdata(GtkWidget *, void *); + +void reload_profiles(int); + +#endif \ No newline at end of file diff --git a/src/main_window.glade b/src/main_window.glade new file mode 100644 index 0000000..ee358f2 --- /dev/null +++ b/src/main_window.glade @@ -0,0 +1,136 @@ + + + + + + + 360 + 360 + False + + + True + False + vertical + + + True + False + start + FeedbackD Configuration + True + + + New + True + True + True + + + + end + + + + + False + True + 0 + + + + + True + True + never + in + + + True + False + + + True + True + 32 + 32 + 32 + 32 + True + never + never + in + + + True + False + + + True + False + crossfade + + + True + False + none + + + Full + + + + + True + False + none + + + Quiet + 1 + + + + + True + False + none + + + Silent + 2 + + + + + + + + + + + + + True + True + 1 + + + + + True + False + auto + stack + True + + + False + True + 2 + + + + + + diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..a5c2828 --- /dev/null +++ b/src/util.c @@ -0,0 +1,46 @@ +#include "util.h" + +struct app_data *app_data = NULL; + +// Initialize App Data +void init_app_data() { + app_data = malloc(sizeof (struct app_data)); +} + +// Get App Data +struct app_data *get_app_data() { + return app_data; +} + +// Free App Data +void free_app_data() { + if (app_data != NULL) { + free(app_data); + } +} + +// Convert Profile List Widget To Profile Name +char *profile_list_to_name(GtkListBox *profile) { + if (profile == app_data->full_list) { + return "full"; + } else if (profile == app_data->quiet_list) { + return "quiet"; + } else if (profile == app_data->silent_list) { + return "silent"; + } else { + return NULL; + } +} + +// Convert Name To Profile Obj +JsonArray *name_to_profile(char *name) { + if (g_strcmp0(name, "full") == 0) { + return app_data->full; + } else if (g_strcmp0(name, "quiet") == 0) { + return app_data->quiet; + } else if (g_strcmp0(name, "silent") == 0) { + return app_data->silent; + } else { + return NULL; + } +} \ No newline at end of file diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..fb095b8 --- /dev/null +++ b/src/util.h @@ -0,0 +1,25 @@ +#ifndef UTIL_H + +#define UTIL_H + +#include +#include + +// Global App Context +struct app_data { + GtkListBox *full_list; + JsonArray *full; + GtkListBox *quiet_list; + JsonArray *quiet; + GtkListBox *silent_list; + JsonArray *silent; +}; + +void init_app_data(); +struct app_data *get_app_data(); +void free_app_data(); + +char *profile_list_to_name(GtkListBox *); +JsonArray *name_to_profile(char *); + +#endif \ No newline at end of file