extensions-tool: Implement create command

This implements more functionality of the existing tool and, as
'reload' is an unreliable feature that doesn't work more often
than not, the last bit that we will replicate.

The command follows the original for the most part, with the most
important difference being the installed template, which doesn't
provide any sample functionality and uses modern JS syntax.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
This commit is contained in:
Florian Müllner 2018-08-27 03:24:10 +02:00
parent c8c93b2a70
commit 0b1e29e5e3
9 changed files with 334 additions and 5 deletions

View File

@ -0,0 +1,256 @@
/* command-create.c
*
* Copyright 2018 Florian Müllner <fmuellner@gnome.org>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <gio/gunixinputstream.h>
#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;
}

View File

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

View File

@ -20,13 +20,14 @@
#pragma once
#include <glib.h>
#include <gio/gio.h>
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

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/extensions-tool">
<file>template/extension.js</file>
<file>template/stylesheet.css</file>
</gresource>
</gresources>

View File

@ -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 ();

View File

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

View File

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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
/* exported init */
class Extension {
constructor() {
}
enable() {
}
disable() {
}
}
function init() {
return new Extension();
}

View File

@ -0,0 +1 @@
/* Add your custom extension styling here */