Initial Commit

This commit is contained in:
TheBrokenRail 2020-09-09 20:44:59 -04:00
commit f4a1b3264a
18 changed files with 1291 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build/

29
.vscode/launch.json vendored Normal file
View File

@ -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"
}
]
}

7
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"cmake.configureOnOpen": true,
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
"files.associations": {
"limits": "c"
}
}

26
.vscode/tasks.json vendored Normal file
View File

@ -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"
}

37
CMakeLists.txt Normal file
View File

@ -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")

21
LICENSE Normal file
View File

@ -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.

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# FeedbackD Configuration
A Simple Configuration Tool For [FeedbackD](https://source.puri.sm/Librem5/feedbackd)

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

7
src/.gresource.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource>
<file preprocess="xml-stripblanks">main_window.glade</file>
<file preprocess="xml-stripblanks">dialog_window.glade</file>
</gresource>
</gresources>

165
src/dialog.c Normal file
View File

@ -0,0 +1,165 @@
#include <gtk/gtk.h>
#include <json-glib/json-glib.h>
#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);
}

11
src/dialog.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef DIALOG_H
#define DIALOG_H
#include <gtk/gtk.h>
#include "main.h"
void open_dialog(GtkWidget *, struct event_location *, int);
#endif

475
src/dialog_window.glade Normal file
View File

@ -0,0 +1,475 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<requires lib="libhandy" version="1.0"/>
<object class="HdyWindow" id="dialog_window">
<property name="can_focus">False</property>
<property name="window_position">center-on-parent</property>
<property name="type_hint">dialog</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="HdyHeaderBar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="title" translatable="yes">Create/Modify Event</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkButton" id="save">
<property name="label" translatable="yes">Save</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="pack_type">end</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">16</property>
<property name="margin_right">16</property>
<property name="margin_top">16</property>
<property name="margin_bottom">16</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Name:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="name">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="HdyViewSwitcher">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="narrow_ellipsize">end</property>
<property name="stack">stack</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="transition_type">crossfade</property>
<child>
<object class="GtkBox" id="sound">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Sound Name:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="sound-name">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="title" translatable="yes">Sound</property>
</packing>
</child>
<child>
<object class="GtkBox" id="vibration-rumble">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Duration:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="vibration-rumble-duration">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="input_purpose">digits</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="title" translatable="yes">Vibration (Rumble)</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="vibration-periodic">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Magnitude:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="magnitude">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="input_purpose">digits</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Duration:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="vibration-periodic-duration">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="input_purpose">digits</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Fade In Time:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="fade-in-time">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="input_purpose">digits</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Fade In Level:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="fade-in-level">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="input_purpose">digits</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="title" translatable="yes">Vibration (Periodic)</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox" id="led">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Color:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="color">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Frequency:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="frequency">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="input_purpose">digits</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="title" translatable="yes">LED</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

278
src/main.c Normal file
View File

@ -0,0 +1,278 @@
#include <stdlib.h>
#include <json-glib/json-glib.h>
#include <gtk/gtk.h>
#include <handy.h>
#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;
}

17
src/main.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef MAIN_H
#define MAIN_H
#include <json-glib/json-glib.h>
// Represnts One Item In A Profile
struct event_location {
JsonArray *profile;
int id;
};
void free_userdata(GtkWidget *, void *);
void reload_profiles(int);
#endif

136
src/main_window.glade Normal file
View File

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<requires lib="libhandy" version="1.0"/>
<object class="HdyApplicationWindow" id="main_window">
<property name="width_request">360</property>
<property name="height_request">360</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="HdyHeaderBar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="title" translatable="yes">FeedbackD Configuration</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">New</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_new_event" object="stack" swapped="no"/>
</object>
<packing>
<property name="pack_type">end</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">32</property>
<property name="margin_right">32</property>
<property name="margin_top">32</property>
<property name="margin_bottom">32</property>
<property name="vexpand">True</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">never</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="transition_type">crossfade</property>
<child>
<object class="GtkListBox" id="full">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="selection_mode">none</property>
</object>
<packing>
<property name="title" translatable="yes">Full</property>
</packing>
</child>
<child>
<object class="GtkListBox" id="quiet">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="selection_mode">none</property>
</object>
<packing>
<property name="title" translatable="yes">Quiet</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkListBox" id="silent">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="selection_mode">none</property>
</object>
<packing>
<property name="title" translatable="yes">Silent</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="HdyViewSwitcherBar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="policy">auto</property>
<property name="stack">stack</property>
<property name="reveal">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

46
src/util.c Normal file
View File

@ -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;
}
}

25
src/util.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef UTIL_H
#define UTIL_H
#include <gtk/gtk.h>
#include <json-glib/json-glib.h>
// 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