1086 lines
41 KiB
Vala
1086 lines
41 KiB
Vala
namespace SwayNotificationCenter {
|
|
public enum PositionX {
|
|
RIGHT, LEFT, CENTER, NONE;
|
|
}
|
|
|
|
public enum PositionY {
|
|
TOP, BOTTOM, CENTER, NONE;
|
|
}
|
|
|
|
public enum ImageVisibility {
|
|
ALWAYS, WHEN_AVAILABLE, NEVER;
|
|
|
|
public string parse () {
|
|
switch (this) {
|
|
default:
|
|
return "always";
|
|
case WHEN_AVAILABLE:
|
|
return "when_available";
|
|
case NEVER:
|
|
return "never";
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum Layer {
|
|
BACKGROUND, BOTTOM, TOP, OVERLAY;
|
|
|
|
public string parse () {
|
|
switch (this) {
|
|
case BACKGROUND:
|
|
return "background";
|
|
case BOTTOM:
|
|
return "bottom";
|
|
case TOP:
|
|
return "top";
|
|
default:
|
|
case OVERLAY:
|
|
return "overlay";
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum CssPriority {
|
|
APPLICATION, USER;
|
|
|
|
public int get_priority () {
|
|
switch (this) {
|
|
case APPLICATION:
|
|
default:
|
|
return Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION;
|
|
case USER:
|
|
return Gtk.STYLE_PROVIDER_PRIORITY_USER;
|
|
}
|
|
}
|
|
}
|
|
|
|
public class Category : Object {
|
|
public string ? sound { get; set; default = null; }
|
|
public string ? icon { get; set; default = null; }
|
|
|
|
public string to_string () {
|
|
string[] fields = {};
|
|
if (sound != null) fields += "sound: %s".printf (sound);
|
|
if (icon != null) fields += "icon: %s".printf (icon);
|
|
return string.joinv (", ", fields);
|
|
}
|
|
}
|
|
|
|
public class NotificationMatching : Object, Json.Serializable {
|
|
public string ? app_name { get; set; default = null; }
|
|
public string ? desktop_entry { get; set; default = null; }
|
|
public string ? summary { get; set; default = null; }
|
|
public string ? body { get; set; default = null; }
|
|
public string ? urgency { get; set; default = null; }
|
|
public string ? category { get; set; default = null; }
|
|
|
|
private const RegexCompileFlags REGEX_COMPILE_OPTIONS =
|
|
RegexCompileFlags.MULTILINE
|
|
| RegexCompileFlags.JAVASCRIPT_COMPAT;
|
|
|
|
private const RegexMatchFlags REGEX_MATCH_FLAGS = RegexMatchFlags.NOTEMPTY;
|
|
|
|
public virtual bool matches_notification (NotifyParams param) {
|
|
if (app_name != null) {
|
|
if (param.app_name == null) return false;
|
|
bool result = Regex.match_simple (
|
|
app_name, param.app_name,
|
|
REGEX_COMPILE_OPTIONS,
|
|
REGEX_MATCH_FLAGS);
|
|
if (!result) return false;
|
|
}
|
|
if (desktop_entry != null) {
|
|
if (param.desktop_entry == null) return false;
|
|
bool result = Regex.match_simple (
|
|
desktop_entry, param.desktop_entry,
|
|
REGEX_COMPILE_OPTIONS,
|
|
REGEX_MATCH_FLAGS);
|
|
if (!result) return false;
|
|
}
|
|
if (summary != null) {
|
|
if (param.summary == null) return false;
|
|
bool result = Regex.match_simple (
|
|
summary, param.summary,
|
|
REGEX_COMPILE_OPTIONS,
|
|
REGEX_MATCH_FLAGS);
|
|
if (!result) return false;
|
|
}
|
|
if (body != null) {
|
|
if (param.body == null) return false;
|
|
bool result = Regex.match_simple (
|
|
body, param.body,
|
|
0,
|
|
REGEX_MATCH_FLAGS);
|
|
if (!result) return false;
|
|
}
|
|
if (urgency != null) {
|
|
bool result = Regex.match_simple (
|
|
urgency, param.urgency.to_string (),
|
|
REGEX_COMPILE_OPTIONS,
|
|
REGEX_MATCH_FLAGS);
|
|
if (!result) return false;
|
|
}
|
|
if (category != null) {
|
|
if (param.category == null) return false;
|
|
bool result = Regex.match_simple (
|
|
category, param.category,
|
|
REGEX_COMPILE_OPTIONS,
|
|
REGEX_MATCH_FLAGS);
|
|
if (!result) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public string to_string () {
|
|
string[] fields = {};
|
|
if (app_name != null) fields += "app-name: %s".printf (app_name);
|
|
if (desktop_entry != null) fields += "desktop-entry: %s".printf (desktop_entry);
|
|
if (summary != null) fields += "summary: %s".printf (summary);
|
|
if (body != null) fields += "body: %s".printf (body);
|
|
if (urgency != null) fields += "urgency: %s".printf (urgency);
|
|
if (category != null) fields += "category: %s".printf (category);
|
|
return string.joinv (", ", fields);
|
|
}
|
|
|
|
public override Json.Node serialize_property (string property_name,
|
|
Value value,
|
|
ParamSpec pspec) {
|
|
// Return enum nickname instead of enum int value
|
|
if (value.type ().is_a (Type.ENUM)) {
|
|
var node = new Json.Node (Json.NodeType.VALUE);
|
|
EnumClass enumc = (EnumClass) value.type ().class_ref ();
|
|
unowned EnumValue ? eval
|
|
= enumc.get_value (value.get_enum ());
|
|
if (eval == null) {
|
|
node.set_value (value);
|
|
return node;
|
|
}
|
|
node.set_string (eval.value_nick);
|
|
return node;
|
|
}
|
|
return default_serialize_property (property_name, value, pspec);
|
|
}
|
|
}
|
|
|
|
public enum NotificationStatusEnum {
|
|
ENABLED,
|
|
MUTED,
|
|
IGNORED,
|
|
TRANSIENT;
|
|
|
|
public string to_string () {
|
|
switch (this) {
|
|
default :
|
|
return "enabled";
|
|
case MUTED:
|
|
return "muted";
|
|
case IGNORED:
|
|
return "ignored";
|
|
case TRANSIENT:
|
|
return "transient";
|
|
}
|
|
}
|
|
|
|
public static NotificationStatusEnum from_value (string value) {
|
|
switch (value) {
|
|
default:
|
|
return ENABLED;
|
|
case "muted":
|
|
return MUTED;
|
|
case "ignored":
|
|
return IGNORED;
|
|
case "transient":
|
|
return TRANSIENT;
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum NotificationUrgencyEnum {
|
|
UNSET = -1,
|
|
LOW = UrgencyLevels.LOW,
|
|
NORMAL = UrgencyLevels.NORMAL,
|
|
CRITICAL = UrgencyLevels.CRITICAL;
|
|
|
|
public uint8 to_byte () {
|
|
return this;
|
|
}
|
|
|
|
public string to_string () {
|
|
switch (this) {
|
|
case LOW:
|
|
return "Low";
|
|
case NORMAL:
|
|
return "Normal";
|
|
case CRITICAL:
|
|
return "Critical";
|
|
default:
|
|
return "Unset";
|
|
}
|
|
}
|
|
}
|
|
|
|
public class NotificationVisibility : NotificationMatching {
|
|
public NotificationStatusEnum state { get; set; }
|
|
public NotificationUrgencyEnum override_urgency {
|
|
get; set; default = NotificationUrgencyEnum.UNSET;
|
|
}
|
|
}
|
|
|
|
public enum ScriptRunOnType {
|
|
ACTION,
|
|
RECEIVE;
|
|
}
|
|
|
|
#if WANT_SCRIPTING
|
|
public class Script : NotificationMatching {
|
|
public string ? exec { get; set; default = null; }
|
|
public ScriptRunOnType run_on { get; set; default = ScriptRunOnType.RECEIVE; }
|
|
|
|
public async bool run_script (NotifyParams param, out string msg) {
|
|
msg = "";
|
|
try {
|
|
string[] spawn_env = Environ.get ();
|
|
// Export env variables
|
|
spawn_env += "SWAYNC_APP_NAME=%s".printf (param.app_name);
|
|
spawn_env += "SWAYNC_SUMMARY=%s".printf (param.summary);
|
|
spawn_env += "SWAYNC_BODY=%s".printf (param.body);
|
|
spawn_env += "SWAYNC_URGENCY=%s".printf (param.urgency.to_string ());
|
|
spawn_env += "SWAYNC_CATEGORY=%s".printf (param.category);
|
|
spawn_env += "SWAYNC_ID=%s".printf (param.applied_id.to_string ());
|
|
spawn_env += "SWAYNC_REPLACES_ID=%s".printf (param.replaces_id.to_string ());
|
|
spawn_env += "SWAYNC_TIME=%s".printf (param.time.to_string ());
|
|
spawn_env += "SWAYNC_DESKTOP_ENTRY=%s".printf (param.desktop_entry ?? "");
|
|
|
|
Pid child_pid;
|
|
Process.spawn_async (
|
|
"/",
|
|
exec.split (" "),
|
|
spawn_env,
|
|
SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD,
|
|
null,
|
|
out child_pid);
|
|
|
|
// Close the child when the spawned process is idling
|
|
int end_status = 0;
|
|
ChildWatch.add (child_pid, (pid, status) => {
|
|
Process.close_pid (pid);
|
|
end_status = status;
|
|
run_script.callback ();
|
|
});
|
|
// Waits until `run_script.callback()` is called above
|
|
yield;
|
|
return end_status == 0;
|
|
} catch (Error e) {
|
|
stderr.printf ("Run_Script Error: %s\n", e.message);
|
|
msg = e.message;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public override bool matches_notification (NotifyParams param) {
|
|
if (exec == null) return false;
|
|
return base.matches_notification (param);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
public class ConfigModel : Object, Json.Serializable {
|
|
|
|
private static ConfigModel _instance;
|
|
private static string _path = "";
|
|
|
|
/** Get the static singleton */
|
|
public static unowned ConfigModel instance {
|
|
get {
|
|
if (_instance == null) _instance = new ConfigModel ();
|
|
if (_path.length <= 0) _path = Functions.get_config_path (null);
|
|
return _instance;
|
|
}
|
|
}
|
|
|
|
/** Get the static singleton and reload the config */
|
|
public static unowned ConfigModel init (string ? path) {
|
|
_path = Functions.get_config_path (path);
|
|
reload_config ();
|
|
return _instance;
|
|
}
|
|
|
|
public delegate void ModifyNode (Json.Node node);
|
|
|
|
/** Reloads the config and calls `ModifyNode` before deserializing */
|
|
public static void reload_config (ModifyNode modify_cb = () => {}) {
|
|
ConfigModel m = null;
|
|
try {
|
|
if (_path.length == 0) return;
|
|
Json.Parser parser = new Json.Parser ();
|
|
parser.load_from_file (_path);
|
|
Json.Node ? node = parser.get_root ();
|
|
if (node == null) {
|
|
throw new Json.ParserError.PARSE ("Node is null!");
|
|
}
|
|
|
|
modify_cb (node);
|
|
|
|
ConfigModel model = Json.gobject_deserialize (
|
|
typeof (ConfigModel), node) as ConfigModel;
|
|
if (model == null) {
|
|
throw new Json.ParserError.UNKNOWN ("Json model is null!");
|
|
}
|
|
m = model;
|
|
} catch (Error e) {
|
|
stderr.printf (e.message + "\n");
|
|
}
|
|
_instance = m ?? new ConfigModel ();
|
|
debug (_instance.to_string ());
|
|
}
|
|
|
|
/* Properties */
|
|
|
|
/** The notifications and controlcenters horizontal alignment */
|
|
public PositionX positionX { // vala-lint=naming-convention
|
|
get; set; default = PositionX.RIGHT;
|
|
}
|
|
/** The notifications and controlcenters vertical alignment */
|
|
public PositionY positionY { // vala-lint=naming-convention
|
|
get; set; default = PositionY.TOP;
|
|
}
|
|
|
|
/** Layer of notification window */
|
|
public Layer layer {
|
|
get; set; default = Layer.OVERLAY;
|
|
}
|
|
|
|
/**
|
|
* Wether or not the windows should be opened as layer-shell surfaces
|
|
*/
|
|
public bool layer_shell { get; set; default = true; }
|
|
|
|
/** The CSS loading priority */
|
|
public CssPriority cssPriority { // vala-lint=naming-convention
|
|
get; set; default = CssPriority.APPLICATION;
|
|
}
|
|
|
|
/** The timeout for notifications with NORMAL priority */
|
|
private const int TIMEOUT_DEFAULT = 10;
|
|
private int _timeout = TIMEOUT_DEFAULT;
|
|
public int timeout {
|
|
get {
|
|
return _timeout;
|
|
}
|
|
set {
|
|
_timeout = value < 0 ? TIMEOUT_DEFAULT : value;
|
|
}
|
|
}
|
|
|
|
/** The timeout for notifications with LOW priority */
|
|
private const int TIMEOUT_LOW_DEFAULT = 5;
|
|
private int _timeout_low = TIMEOUT_LOW_DEFAULT;
|
|
public int timeout_low {
|
|
get {
|
|
return _timeout_low;
|
|
}
|
|
set {
|
|
_timeout_low = value < 0 ? TIMEOUT_LOW_DEFAULT : value;
|
|
}
|
|
}
|
|
|
|
/** The timeout for notifications with CRITICAL priority */
|
|
private const int TIMEOUT_CRITICAL_DEFAULT = 0;
|
|
private int _timeout_critical = TIMEOUT_CRITICAL_DEFAULT;
|
|
public int timeout_critical {
|
|
get {
|
|
return _timeout_critical;
|
|
}
|
|
set {
|
|
_timeout_critical = value < 0 ? TIMEOUT_CRITICAL_DEFAULT : value;
|
|
}
|
|
}
|
|
|
|
/** The transition time for all animations */
|
|
private const int TRANSITION_TIME_DEFAULT = 200;
|
|
private int _transition_time = TRANSITION_TIME_DEFAULT;
|
|
public int transition_time {
|
|
get {
|
|
return _transition_time;
|
|
}
|
|
set {
|
|
_transition_time = value < 0 ? TRANSITION_TIME_DEFAULT : value;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Specifies if the control center should use keyboard shortcuts
|
|
* and block keyboard input for other applications while open
|
|
*/
|
|
public bool keyboard_shortcuts { get; set; default = true; }
|
|
|
|
/** Specifies if the notification image should be shown or not */
|
|
public ImageVisibility image_visibility {
|
|
get;
|
|
set;
|
|
default = ImageVisibility.WHEN_AVAILABLE;
|
|
}
|
|
|
|
/**
|
|
* Notification window's width, in pixels.
|
|
*/
|
|
public int notification_window_width { get; set; default = 500; }
|
|
|
|
/** Hides the control center after clearing all notifications */
|
|
public bool hide_on_clear { get; set; default = false; }
|
|
|
|
/** Hides the control center when clicking on notification action */
|
|
public bool hide_on_action { get; set; default = true; }
|
|
|
|
/** The controlcenters horizontal alignment. Supersedes `positionX` if not `NONE` */
|
|
public PositionX control_center_positionX { // vala-lint=naming-convention
|
|
get; set; default = PositionX.NONE;
|
|
}
|
|
/** The controlcenters vertical alignment. Supersedes `positionY` if not `NONE` */
|
|
public PositionY control_center_positionY { // vala-lint=naming-convention
|
|
get; set; default = PositionY.NONE;
|
|
}
|
|
|
|
/** GtkLayerShell margins around the notification center */
|
|
private int control_center_margin_top_ = 0;
|
|
public int control_center_margin_top {
|
|
get {
|
|
return control_center_margin_top_;
|
|
}
|
|
set {
|
|
control_center_margin_top_ = value < 0 ? 0 : value;
|
|
}
|
|
}
|
|
|
|
private int control_center_margin_bottom_ = 0;
|
|
public int control_center_margin_bottom {
|
|
get {
|
|
return control_center_margin_bottom_;
|
|
}
|
|
set {
|
|
control_center_margin_bottom_ = value < 0 ? 0 : value;
|
|
}
|
|
}
|
|
|
|
private int control_center_margin_left_ = 0;
|
|
public int control_center_margin_left {
|
|
get {
|
|
return control_center_margin_left_;
|
|
}
|
|
set {
|
|
control_center_margin_left_ = value < 0 ? 0 : value;
|
|
}
|
|
}
|
|
|
|
private int control_center_margin_right_ = 0;
|
|
public int control_center_margin_right {
|
|
get {
|
|
return control_center_margin_right_;
|
|
}
|
|
set {
|
|
control_center_margin_right_ = value < 0 ? 0 : value;
|
|
}
|
|
}
|
|
|
|
/** Layer of Control Center window */
|
|
public Layer control_center_layer {
|
|
get; set; default = Layer.TOP;
|
|
}
|
|
|
|
/** Categories settings */
|
|
public OrderedHashTable<Category> categories_settings {
|
|
get;
|
|
set;
|
|
default = new OrderedHashTable<Category> ();
|
|
}
|
|
|
|
|
|
/** Notification Status */
|
|
public OrderedHashTable<NotificationVisibility> notification_visibility {
|
|
get;
|
|
set;
|
|
default = new OrderedHashTable<NotificationVisibility> ();
|
|
}
|
|
|
|
#if WANT_SCRIPTING
|
|
/** Scripts */
|
|
public OrderedHashTable<Script> scripts {
|
|
get;
|
|
set;
|
|
default = new OrderedHashTable<Script> ();
|
|
}
|
|
|
|
/** Show notification if script fails */
|
|
public bool script_fail_notify { get; set; default = true; }
|
|
#endif
|
|
|
|
/** Whether to expand the notification center across both edges of the screen */
|
|
public bool fit_to_screen { get; set; default = true; }
|
|
|
|
/**
|
|
* Notification center's height, in pixels.
|
|
* Set `fit_to_screen` to true to ignore the height setting.
|
|
*/
|
|
private const int CONTROL_CENTER_MINIMUM_HEIGHT = 300;
|
|
private const int CONTROL_CENTER_DEFAULT_HEIGHT = 300;
|
|
private int _control_center_height = CONTROL_CENTER_DEFAULT_HEIGHT;
|
|
public int control_center_height {
|
|
get {
|
|
return _control_center_height;
|
|
}
|
|
set {
|
|
_control_center_height = value > CONTROL_CENTER_MINIMUM_HEIGHT
|
|
? value : CONTROL_CENTER_MINIMUM_HEIGHT;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notification center's width, in pixels.
|
|
*/
|
|
private const int CONTROL_CENTER_MINIMUM_WIDTH = 300;
|
|
private const int CONTROL_CENTER_DEFAULT_WIDTH = 500;
|
|
private int _control_center_width = CONTROL_CENTER_DEFAULT_WIDTH;
|
|
public int control_center_width {
|
|
get {
|
|
return _control_center_width;
|
|
}
|
|
set {
|
|
_control_center_width = value > CONTROL_CENTER_MINIMUM_WIDTH
|
|
? value : CONTROL_CENTER_MINIMUM_WIDTH;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If each notification should display a 'COPY \"1234\"' action
|
|
*/
|
|
public bool notification_2fa_action { get; set; default = true; }
|
|
|
|
/**
|
|
* Notification icon size, in pixels.
|
|
*/
|
|
private const int NOTIFICATION_ICON_MINIMUM_SIZE = 16;
|
|
private const int NOTIFICATION_ICON_DEFAULT_SIZE = 64;
|
|
private int _notification_icon_size = NOTIFICATION_ICON_DEFAULT_SIZE;
|
|
public int notification_icon_size {
|
|
get {
|
|
return _notification_icon_size;
|
|
}
|
|
set {
|
|
_notification_icon_size = value > NOTIFICATION_ICON_MINIMUM_SIZE
|
|
? value : NOTIFICATION_ICON_MINIMUM_SIZE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notification body image height, in pixels.
|
|
*/
|
|
private const int NOTIFICATION_BODY_IMAGE_MINIMUM_HEIGHT = 100;
|
|
private const int NOTIFICATION_BODY_IMAGE_DEFAULT_HEIGHT = 100;
|
|
private int _notification_body_image_height = NOTIFICATION_BODY_IMAGE_DEFAULT_HEIGHT;
|
|
public int notification_body_image_height {
|
|
get {
|
|
return _notification_body_image_height;
|
|
}
|
|
set {
|
|
_notification_body_image_height =
|
|
value > NOTIFICATION_BODY_IMAGE_MINIMUM_HEIGHT
|
|
? value : NOTIFICATION_BODY_IMAGE_MINIMUM_HEIGHT;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notification body image width, in pixels.
|
|
*/
|
|
private const int NOTIFICATION_BODY_IMAGE_MINIMUM_WIDTH = 200;
|
|
private const int NOTIFICATION_BODY_IMAGE_DEFAULT_WIDTH = 200;
|
|
private int _notification_body_image_width = NOTIFICATION_BODY_IMAGE_DEFAULT_WIDTH;
|
|
public int notification_body_image_width {
|
|
get {
|
|
return _notification_body_image_width;
|
|
}
|
|
set {
|
|
_notification_body_image_width =
|
|
value > NOTIFICATION_BODY_IMAGE_MINIMUM_WIDTH
|
|
? value : NOTIFICATION_BODY_IMAGE_MINIMUM_WIDTH;
|
|
}
|
|
}
|
|
|
|
/** Widgets to show in ControlCenter */
|
|
public GenericArray<string> widgets {
|
|
get;
|
|
set;
|
|
default = new GenericArray<string> ();
|
|
}
|
|
|
|
/** Widgets to show in ControlCenter */
|
|
public OrderedHashTable<Json.Object> widget_config {
|
|
get;
|
|
set;
|
|
default = new OrderedHashTable<Json.Object> ();
|
|
}
|
|
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* Selects the deserialization method based on the property name.
|
|
* Needed to parse those parameters of complex types like hashtables,
|
|
* which are not natively supported by the default deserialization function.
|
|
*/
|
|
public override bool deserialize_property (string property_name,
|
|
out Value value,
|
|
ParamSpec pspec,
|
|
Json.Node property_node) {
|
|
switch (property_name) {
|
|
case "categories-settings" :
|
|
bool status;
|
|
OrderedHashTable<Category> result =
|
|
extract_hashtable<Category> (
|
|
property_name,
|
|
property_node,
|
|
out status);
|
|
value = result;
|
|
return status;
|
|
case "notification-visibility":
|
|
bool status;
|
|
OrderedHashTable<NotificationVisibility> result =
|
|
extract_hashtable<NotificationVisibility> (
|
|
property_name,
|
|
property_node,
|
|
out status);
|
|
value = result;
|
|
return status;
|
|
#if WANT_SCRIPTING
|
|
case "scripts":
|
|
bool status;
|
|
OrderedHashTable<Script> result =
|
|
extract_hashtable<Script> (
|
|
property_name,
|
|
property_node,
|
|
out status);
|
|
value = result;
|
|
return status;
|
|
#endif
|
|
case "widgets":
|
|
bool status;
|
|
GenericArray<string> result =
|
|
extract_array<string> (property_name,
|
|
property_node,
|
|
out status);
|
|
value = result;
|
|
return status;
|
|
case "widget-config":
|
|
OrderedHashTable<Json.Object> result
|
|
= new OrderedHashTable<Json.Object> ();
|
|
if (property_node.get_value_type ().name () != "JsonObject") {
|
|
value = result;
|
|
return true;
|
|
}
|
|
Json.Object obj = property_node.get_object ();
|
|
if (obj.get_size () == 0) {
|
|
value = result;
|
|
return true;
|
|
}
|
|
foreach (var key in obj.get_members ()) {
|
|
Json.Node ? node = obj.get_member (key);
|
|
if (node.get_node_type () != Json.NodeType.OBJECT) continue;
|
|
Json.Object ? o = node.get_object ();
|
|
if (o != null) result.insert (key, o);
|
|
}
|
|
value = result;
|
|
return true;
|
|
default:
|
|
// Handles all other properties
|
|
return default_deserialize_property (
|
|
property_name, out value, pspec, property_node);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when `Json.gobject_serialize (ConfigModel.instance)` is called
|
|
*/
|
|
public Json.Node serialize_property (string property_name,
|
|
Value value,
|
|
ParamSpec pspec) {
|
|
var node = new Json.Node (Json.NodeType.VALUE);
|
|
if (value.type ().is_a (Type.ENUM)) {
|
|
EnumClass enumc = (EnumClass) pspec.value_type.class_ref ();
|
|
EnumValue ? eval
|
|
= enumc.get_value (value.get_enum ());
|
|
if (eval == null) {
|
|
node.set_value (value);
|
|
return node;
|
|
}
|
|
node.set_string (eval.value_nick);
|
|
return node;
|
|
}
|
|
// All other properties that can't be serialized
|
|
switch (property_name) {
|
|
case "categories-settings" :
|
|
node = new Json.Node (Json.NodeType.OBJECT);
|
|
var table = (OrderedHashTable<Category>) value;
|
|
node.set_object (serialize_hashtable<Category> (table));
|
|
break;
|
|
case "notification-visibility":
|
|
node = new Json.Node (Json.NodeType.OBJECT);
|
|
var table = (OrderedHashTable<NotificationVisibility>) value;
|
|
node.set_object (serialize_hashtable<NotificationVisibility> (table));
|
|
break;
|
|
#if WANT_SCRIPTING
|
|
case "scripts":
|
|
node = new Json.Node (Json.NodeType.OBJECT);
|
|
var table = (OrderedHashTable<Script>) value;
|
|
node.set_object (serialize_hashtable<Script> (table));
|
|
break;
|
|
#endif
|
|
case "widgets":
|
|
node = new Json.Node (Json.NodeType.ARRAY);
|
|
var table = (GenericArray<string>) value;
|
|
node.set_array (serialize_array<string> (table));
|
|
break;
|
|
case "widget-config":
|
|
node = new Json.Node (Json.NodeType.OBJECT);
|
|
var table = (OrderedHashTable<Json.Object>) value;
|
|
node.set_object (serialize_hashtable<Json.Object> (table));
|
|
break;
|
|
default:
|
|
node.set_value (value);
|
|
break;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
public string to_string () {
|
|
var json = Json.gobject_serialize (ConfigModel.instance);
|
|
return Json.to_string (json, true);
|
|
}
|
|
|
|
/**
|
|
* Extracts and returns a OrderedHashTable<GLib.Object>
|
|
* from a nested JSON Object.
|
|
*
|
|
* Can only accept these types:
|
|
* - string
|
|
* - bool
|
|
* - int64
|
|
* - GLib.Object
|
|
*/
|
|
private OrderedHashTable<T> extract_hashtable<T> (string property_name,
|
|
Json.Node node,
|
|
out bool status) {
|
|
status = false;
|
|
var tmp_table = new OrderedHashTable<T> ();
|
|
|
|
if (node.get_node_type () != Json.NodeType.OBJECT) {
|
|
stderr.printf ("Node %s is not a json object!...\n",
|
|
property_name);
|
|
return tmp_table;
|
|
}
|
|
|
|
Json.Object ? root_object = node.get_object ();
|
|
if (root_object == null) return tmp_table;
|
|
|
|
Type generic_type = Functions.get_base_type (typeof (T));
|
|
foreach (string * key in root_object.get_members ()) {
|
|
unowned Json.Node ? member = root_object.get_member (key);
|
|
if (member == null) continue;
|
|
|
|
if (!member.get_value_type ().is_a (generic_type)
|
|
&& !member.get_value_type ().is_a (typeof (Json.Object))) {
|
|
continue;
|
|
}
|
|
|
|
switch (generic_type) {
|
|
case Type.STRING:
|
|
unowned string ? str = member.get_string ();
|
|
if (str != null) tmp_table.insert (key, str);
|
|
break;
|
|
case Type.BOOLEAN :
|
|
tmp_table.insert (key, member.get_boolean ());
|
|
break;
|
|
case Type.INT64:
|
|
tmp_table.insert (key, (int64 ? ) member.get_int ());
|
|
break;
|
|
case Type.OBJECT:
|
|
if (!typeof (T).is_a (Type.OBJECT)) break;
|
|
|
|
unowned Json.Object ? object =
|
|
root_object.get_object_member (key);
|
|
if (object == null) break;
|
|
|
|
// Creates a new GLib.Object with all of the properties of T
|
|
Type type = typeof (T);
|
|
ObjectClass ocl = (ObjectClass) type.class_ref ();
|
|
Object obj = Object.new (type);
|
|
foreach (var name in object.get_members ()) {
|
|
Value value = object.get_member (name).get_value ();
|
|
|
|
unowned ParamSpec value_spec = null;
|
|
foreach (var spec in ocl.list_properties ()) {
|
|
if (spec.name == name) {
|
|
value_spec = spec;
|
|
break;
|
|
}
|
|
}
|
|
if (value_spec == null) continue;
|
|
|
|
unowned Type spec_type = value_spec.value_type;
|
|
unowned Type val_type = value.type ();
|
|
if (spec_type.is_a (val_type)) {
|
|
// Both are the same type
|
|
obj.set_property (name, value);
|
|
} else if (spec_type.is_a (Type.ENUM)
|
|
&& val_type.is_a (Type.STRING)) {
|
|
// Set enum from string
|
|
EnumClass enumc = (EnumClass) spec_type.class_ref ();
|
|
EnumValue ? eval
|
|
= enumc.get_value_by_nick (value.get_string ());
|
|
if (eval != null) {
|
|
obj.set_property (name, eval.value);
|
|
}
|
|
}
|
|
}
|
|
|
|
tmp_table.insert (key, (T) obj);
|
|
break;
|
|
}
|
|
}
|
|
|
|
status = true;
|
|
return tmp_table;
|
|
}
|
|
|
|
private Json.Object serialize_hashtable<T> (OrderedHashTable<T> table) {
|
|
var json_object = new Json.Object ();
|
|
|
|
if (table == null) return json_object;
|
|
|
|
foreach (string key in table.get_keys ()) {
|
|
unowned T item = table.get (key);
|
|
if (item == null) continue;
|
|
|
|
Type generic_type = Functions.get_base_type (typeof (T));
|
|
switch (generic_type) {
|
|
case Type.STRING:
|
|
string ? casted = (string) item;
|
|
if (casted != null) {
|
|
json_object.set_string_member (key, casted);
|
|
}
|
|
break;
|
|
case Type.BOOLEAN :
|
|
bool ? casted = (bool) item;
|
|
if (casted != null) {
|
|
json_object.set_boolean_member (key, casted);
|
|
}
|
|
break;
|
|
case Type.INT64 :
|
|
int64 ? casted = (int64 ? ) item;
|
|
if (casted != null) {
|
|
json_object.set_int_member (key, casted);
|
|
}
|
|
break;
|
|
case Type.OBJECT:
|
|
var node = Json.gobject_serialize (item as Object);
|
|
json_object.set_member (key, node);
|
|
break;
|
|
case Type.BOXED:
|
|
switch (typeof (T).name ()) {
|
|
case "JsonObject":
|
|
json_object.set_object_member (key,
|
|
(Json.Object) item);
|
|
break;
|
|
case "JsonArray":
|
|
json_object.set_array_member (key,
|
|
(Json.Array) item);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return json_object;
|
|
}
|
|
|
|
/**
|
|
* Extracts a JSON array and returns a GLib.GenericArray<T>
|
|
*
|
|
* Can only accept these types:
|
|
* - string
|
|
* - bool
|
|
* - int64
|
|
* - GLib.Object
|
|
*/
|
|
private GenericArray<T> extract_array<T> (string property_name,
|
|
Json.Node node,
|
|
out bool status) {
|
|
status = false;
|
|
GenericArray<T> tmp_array = new GenericArray<T> ();
|
|
|
|
if (node.get_node_type () != Json.NodeType.ARRAY) {
|
|
stderr.printf ("Node %s is not a json array!...\n",
|
|
property_name);
|
|
return tmp_array;
|
|
}
|
|
|
|
Json.Array ? root_array = node.get_array ();
|
|
if (root_array == null) return tmp_array;
|
|
|
|
foreach (Json.Node * member in root_array.get_elements ()) {
|
|
Type generic_type = Functions.get_base_type (typeof (T));
|
|
if (!member->get_value_type ().is_a (generic_type)
|
|
&& !member->get_value_type ().is_a (typeof (Json.Object))) {
|
|
continue;
|
|
}
|
|
|
|
switch (generic_type) {
|
|
case Type.STRING:
|
|
unowned string ? str = member->get_string ();
|
|
if (str != null) tmp_array.add (str);
|
|
break;
|
|
case Type.BOOLEAN :
|
|
tmp_array.add (member->get_boolean ());
|
|
break;
|
|
case Type.INT64:
|
|
tmp_array.add (member->get_int ());
|
|
break;
|
|
case Type.OBJECT:
|
|
if (!typeof (T).is_a (Type.OBJECT)) break;
|
|
|
|
unowned Json.Object ? object = member->get_object ();
|
|
if (object == null) break;
|
|
|
|
// Creates a new GLib.Object with all of the properties of T
|
|
Object obj = Object.new (typeof (T));
|
|
foreach (var name in object.get_members ()) {
|
|
Value value = object.get_member (name).get_value ();
|
|
obj.set_property (name, value);
|
|
}
|
|
|
|
tmp_array.add ((T) obj);
|
|
break;
|
|
}
|
|
}
|
|
|
|
status = true;
|
|
return tmp_array;
|
|
}
|
|
|
|
private Json.Array serialize_array<T> (GenericArray<T> array) {
|
|
var json_array = new Json.Array ();
|
|
|
|
if (array == null) return json_array;
|
|
|
|
foreach (T item in array.data) {
|
|
if (item == null) continue;
|
|
Type generic_type = Functions.get_base_type (typeof (T));
|
|
switch (generic_type) {
|
|
case Type.STRING :
|
|
string ? casted = (string) item;
|
|
if (casted != null) {
|
|
json_array.add_string_element (casted);
|
|
}
|
|
break;
|
|
case Type.BOOLEAN :
|
|
bool ? casted = (bool) item;
|
|
if (casted != null) {
|
|
json_array.add_boolean_element (casted);
|
|
}
|
|
break;
|
|
case Type.INT64 :
|
|
int64 ? casted = (int64) item;
|
|
if (casted != null) {
|
|
json_array.add_int_element (casted);
|
|
}
|
|
break;
|
|
case Type.OBJECT :
|
|
var node = Json.gobject_serialize (item as Object);
|
|
json_array.add_element (node);
|
|
break;
|
|
}
|
|
}
|
|
return json_array;
|
|
}
|
|
|
|
/**
|
|
* Changes the `member_name` to the specified value if their types match
|
|
*/
|
|
public void change_value (string member_name,
|
|
Variant value,
|
|
bool write = true,
|
|
string ? path = null) {
|
|
reload_config ((node) => {
|
|
unowned Json.Object obj = node.get_object ();
|
|
if (obj == null) return;
|
|
debug ("Config change: %s %s",
|
|
member_name, value.get_type_string ());
|
|
switch (value.get_type_string ()) {
|
|
case "i":
|
|
int val = value.get_int32 ();
|
|
obj.set_int_member (member_name, val);
|
|
debug ("Config changed %s", member_name);
|
|
break;
|
|
case "s":
|
|
string val = value.get_string ();
|
|
obj.set_string_member (member_name, val);
|
|
debug ("Config changed %s", member_name);
|
|
break;
|
|
case "b":
|
|
bool val = value.get_boolean ();
|
|
obj.set_boolean_member (member_name, val);
|
|
debug ("Config changed %s", member_name);
|
|
break;
|
|
}
|
|
});
|
|
|
|
if (!write) {
|
|
debug ("Skipped writing new config to %s", path);
|
|
return;
|
|
}
|
|
if (write_to_file (path)) {
|
|
debug ("Successfully wrote to %s", path);
|
|
} else {
|
|
error ("ERROR WRITING TO %s", path);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes and replaces settings with the new settings in `path`. If
|
|
* `path` is "null", the default user accessible config will be used
|
|
* ("~/.config/swaync/config.json")
|
|
*/
|
|
private bool write_to_file (owned string ? path = null) {
|
|
try {
|
|
if (path == null) {
|
|
// Use the default user accessible config
|
|
string dir_path = Path.build_path (
|
|
Path.DIR_SEPARATOR.to_string (),
|
|
Environment.get_user_config_dir (),
|
|
"swaync");
|
|
path = Path.build_path (Path.DIR_SEPARATOR.to_string (),
|
|
dir_path, "config.json");
|
|
var dir = File.new_for_path (dir_path);
|
|
if (!dir.query_exists ()) {
|
|
dir.make_directory ();
|
|
}
|
|
var file = File.new_for_path (path);
|
|
if (!file.query_exists ()) {
|
|
file.create (FileCreateFlags.NONE);
|
|
}
|
|
}
|
|
|
|
var file = File.new_for_path (path);
|
|
|
|
string data = ConfigModel.instance.to_string ();
|
|
return file.replace_contents (
|
|
data.data,
|
|
null,
|
|
false,
|
|
FileCreateFlags.REPLACE_DESTINATION,
|
|
null);
|
|
} catch (Error e) {
|
|
stderr.printf (e.message + "\n");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|