diff --git a/src/extensions-tool/command-create.c b/src/extensions-tool/command-create.c new file mode 100644 index 000000000..5c43c68b9 --- /dev/null +++ b/src/extensions-tool/command-create.c @@ -0,0 +1,256 @@ +/* command-create.c + * + * Copyright 2018 Florian Müllner + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include + +#include "commands.h" +#include "common.h" + +static char * +get_shell_version (GError **error) +{ + g_autoptr (GDBusProxy) proxy = NULL; + g_autoptr (GVariant) variant = NULL; + g_auto (GStrv) split_version = NULL; + + proxy = get_shell_proxy (error); + if (proxy == NULL) + return NULL; + + variant = g_dbus_proxy_get_cached_property (proxy, "ShellVersion"); + if (variant == NULL) + return NULL; + + split_version = g_strsplit (g_variant_get_string (variant, NULL), ".", 3); + if (g_ascii_strtoll (split_version[1], NULL, 10) % 2 == 0) + g_clear_pointer (&split_version[2], g_free); + + return g_strjoinv (".", split_version); +} + +static gboolean +create_metadata (GFile *target_dir, + const char *uuid, + const char *name, + const char *description, + GError **error) +{ + g_autoptr (GFile) target = NULL; + g_autoptr (GString) json = NULL; + g_autofree char *version = NULL; + + version = get_shell_version (error); + if (version == NULL) + return FALSE; + + json = g_string_new ("{\n"); + + g_string_append_printf (json, " \"name\": \"%s\",\n", name); + g_string_append_printf (json, " \"description\": \"%s\",\n", description); + g_string_append_printf (json, " \"uuid\": \"%s\",\n", uuid); + g_string_append_printf (json, " \"shell-version\": [\n"); + g_string_append_printf (json, " \"%s\"\n", version); + g_string_append_printf (json, " ]\n}\n"); + + target = g_file_get_child (target_dir, "metadata.json"); + return g_file_replace_contents (target, + json->str, + json->len, + NULL, + FALSE, + 0, + NULL, + NULL, + error); +} + + +#define TEMPLATE_PATH "/org/gnome/extensions-tool/template" +static gboolean +copy_extension_template (GFile *target_dir, GError **error) +{ + g_auto (GStrv) templates; + char **s; + + templates = g_resources_enumerate_children (TEMPLATE_PATH, 0, NULL); + for (s = templates; *s; s++) + { + g_autoptr (GFile) target = NULL; + g_autoptr (GFile) source = NULL; + g_autofree char *uri = NULL; + + uri = g_strdup_printf ("resource://%s/%s", TEMPLATE_PATH, *s); + source = g_file_new_for_uri (uri); + target = g_file_get_child (target_dir, *s); + + if (!g_file_copy (source, target, G_FILE_COPY_TARGET_DEFAULT_PERMS, NULL, NULL, NULL, error)) + return FALSE; + } + + return TRUE; +} + +static gboolean +launch_extension_source (GFile *dir, GError **error) +{ + g_autoptr (GFile) main_source = NULL; + g_autoptr (GAppInfo) handler = NULL; + GList l; + + main_source = g_file_get_child (dir, "extension.js"); + handler = g_file_query_default_handler (main_source, NULL, error); + if (handler == NULL) + return FALSE; + + l.data = main_source; + l.next = l.prev = NULL; + + return g_app_info_launch (handler, &l, NULL, error); +} + +static gboolean +create_extension (const char *uuid, const char *name, const char *description) +{ + g_autoptr (GFile) dir = NULL; + g_autoptr (GError) error = NULL; + + dir = g_file_new_build_filename (g_get_user_data_dir (), + "gnome-shell", + "extensions", + uuid, + NULL); + + if (!g_file_make_directory_with_parents (dir, NULL, &error)) + { + g_printerr ("%s\n", error->message); + return FALSE; + } + + if (!create_metadata (dir, uuid, name, description, &error)) + { + g_printerr ("%s\n", error->message); + return FALSE; + } + + if (!copy_extension_template (dir, &error)) + { + g_printerr ("%s\n", error->message); + return FALSE; + } + + if (!launch_extension_source (dir, &error)) + { + g_printerr ("%s\n", error->message); + return FALSE; + } + + return TRUE; +} + +static void +prompt_metadata (char **uuid, char **name, char **description) +{ + g_autoptr (GInputStream) stdin = NULL; + g_autoptr (GDataInputStream) istream = NULL; + + stdin = g_unix_input_stream_new (0, FALSE); + istream = g_data_input_stream_new (stdin); + + if (name != NULL) + { + char *line; + + g_print ( + _("Name should be a very short (ideally descriptive) string.\n" + "Examples are: %s"), + "“Click To Focus”, “Adblock”, “Shell Window Shrinker”\n"); + g_print ("%s: ", _("Name")); + + line = g_data_input_stream_read_line_utf8 (istream, NULL, NULL, NULL); + *name = g_strdelimit (line, "\n", '\0'); + } + + if (description != NULL) + { + char *line; + + g_print ( + _("Description is a single-sentence explanation of what your extension does.\n" + "Examples are: %s"), + "“Make windows visible on click”, “Block advertisement popups”, “Animate windows shrinking on minimize”\n"); + g_print ("%s: ", _("Description")); + + line = g_data_input_stream_read_line_utf8 (istream, NULL, NULL, NULL); + *description = g_strdelimit (line, "\n", '\0'); + } + + if (uuid != NULL) + { + char *line; + + g_print ( + _("UUID is a globally-unique identifier for your extension.\n" + "This should be in the format of an email address (clicktofocus@janedoe.example.com)\n")); + g_print ("UUID: "); + + line = g_data_input_stream_read_line_utf8 (istream, NULL, NULL, NULL); + *uuid = g_strdelimit (line, "\n", '\0'); + } +} + +int +handle_create (int argc, char *argv[], gboolean do_help) +{ + g_autoptr (GOptionContext) context = NULL; + g_autoptr (GError) error = NULL; + g_autofree char *name = NULL; + g_autofree char *description = NULL; + g_autofree char *uuid = NULL; + + g_set_prgname ("gnome-extensions create"); + + context = g_option_context_new (NULL); + g_option_context_set_help_enabled (context, FALSE); + g_option_context_set_summary (context, _("Create a new extension")); + + if (do_help) + { + show_help (context, NULL); + return 0; + } + + if (!g_option_context_parse (context, &argc, &argv, &error)) + { + show_help (context, error->message); + return 1; + } + + if (argc > 1) + { + show_help (context, _("Unknown arguments")); + return 1; + } + + prompt_metadata (&uuid, &name, &description); + + return create_extension (uuid, name, description) ? 0 : 2; +} diff --git a/src/extensions-tool/commands.h b/src/extensions-tool/commands.h index c0b0ba18c..f2b4c712f 100644 --- a/src/extensions-tool/commands.h +++ b/src/extensions-tool/commands.h @@ -26,5 +26,6 @@ G_BEGIN_DECLS int handle_enable (int argc, char *argv[], gboolean do_help); int handle_disable (int argc, char *argv[], gboolean do_help); +int handle_create (int argc, char *argv[], gboolean do_help); G_END_DECLS diff --git a/src/extensions-tool/common.h b/src/extensions-tool/common.h index c3a243c92..1c8b4cdd2 100644 --- a/src/extensions-tool/common.h +++ b/src/extensions-tool/common.h @@ -20,13 +20,14 @@ #pragma once -#include +#include G_BEGIN_DECLS void show_help (GOptionContext *context, const char *message); +GDBusProxy *get_shell_proxy (GError **error); GSettings *get_shell_settings (void); G_END_DECLS diff --git a/src/extensions-tool/gnome-extensions-tool.gresource.xml b/src/extensions-tool/gnome-extensions-tool.gresource.xml new file mode 100644 index 000000000..2aad7b1ce --- /dev/null +++ b/src/extensions-tool/gnome-extensions-tool.gresource.xml @@ -0,0 +1,7 @@ + + + + template/extension.js + template/stylesheet.css + + diff --git a/src/extensions-tool/main.c b/src/extensions-tool/main.c index 81c72a3ac..19c331112 100644 --- a/src/extensions-tool/main.c +++ b/src/extensions-tool/main.c @@ -38,6 +38,19 @@ show_help (GOptionContext *context, const char *message) g_printerr ("%s", help); } +GDBusProxy * +get_shell_proxy (GError **error) +{ + return g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.gnome.Shell", + "/org/gnome/Shell", + "org.gnome.Shell.Extensions", + NULL, + error); +} + GSettings * get_shell_settings (void) { @@ -87,6 +100,7 @@ usage (void) g_printerr (" version %s\n", _("Print version")); g_printerr (" enable %s\n", _("Enable extension")); g_printerr (" disable %s\n", _("Disable extension")); + g_printerr (" create %s\n", _("Create extension")); g_printerr ("\n"); g_printerr (_("Use %s to get detailed help.\n"), "“gnome-extensions help COMMAND”"); } @@ -144,6 +158,8 @@ main (int argc, char *argv[]) return handle_enable (argc, argv, do_help); else if (g_str_equal (command, "disable")) return handle_disable (argc, argv, do_help); + else if (g_str_equal (command, "create")) + return handle_create (argc, argv, do_help); else usage (); diff --git a/src/extensions-tool/meson-src.build b/src/extensions-tool/meson-src.build index fb3ab04d1..d11a75ba2 100644 --- a/src/extensions-tool/meson-src.build +++ b/src/extensions-tool/meson-src.build @@ -1,12 +1,19 @@ sources = [ 'commands.h', + 'command-create.c', 'command-disable.c', 'command-enable.c', 'common.h', 'main.c' ] + +resources = gnome.compile_resources('resources', + 'gnome-extensions-tool.gresource.xml', + source_dir: '.' +) + executable('gnome-extensions', - sources, - dependencies: gio_dep, + sources, resources, + dependencies: [gio_dep, gio_unix_dep], install: true ) diff --git a/src/extensions-tool/meson.build b/src/extensions-tool/meson.build index 2e03539c4..35d5245b5 100644 --- a/src/extensions-tool/meson.build +++ b/src/extensions-tool/meson.build @@ -9,13 +9,19 @@ configure_file( ) sources = [ + 'command-create.c', 'command-disable.c', 'command-enable.c', 'main.c' ] +resources = gnome.compile_resources('resources', + 'gnome-extensions-tool.gresource.xml', + source_dir: '.' +) + executable('gnome-extensions', - sources, - dependencies: gio_dep, + sources, resources, + dependencies: [gio_dep, gio_unix_dep], install: true ) diff --git a/src/extensions-tool/template/extension.js b/src/extensions-tool/template/extension.js new file mode 100644 index 000000000..64857afb7 --- /dev/null +++ b/src/extensions-tool/template/extension.js @@ -0,0 +1,34 @@ +/* extension.js + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* exported init */ + +class Extension { + constructor() { + } + + enable() { + } + + disable() { + } +} + +function init() { + return new Extension(); +} diff --git a/src/extensions-tool/template/stylesheet.css b/src/extensions-tool/template/stylesheet.css new file mode 100644 index 000000000..37b93f219 --- /dev/null +++ b/src/extensions-tool/template/stylesheet.css @@ -0,0 +1 @@ +/* Add your custom extension styling here */