mirror of
https://github.com/brl/mutter.git
synced 2024-12-23 11:32:04 +00:00
option to display error when a command fails to run.
2002-07-11 Havoc Pennington <hp@pobox.com> * src/metacity-dialog.c (main): option to display error when a command fails to run. * src/keybindings.c (handle_run_command): run commands in response to keybindings. * src/prefs.c: add command keybinding stuff * src/metacity.schemas.in: add keybindings for running commands, and keys to store the commands themselves.
This commit is contained in:
parent
a38c16e57e
commit
42639fc9fb
13
ChangeLog
13
ChangeLog
@ -1,3 +1,16 @@
|
||||
2002-07-11 Havoc Pennington <hp@pobox.com>
|
||||
|
||||
* src/metacity-dialog.c (main): option to display error when a
|
||||
command fails to run.
|
||||
|
||||
* src/keybindings.c (handle_run_command): run commands
|
||||
in response to keybindings.
|
||||
|
||||
* src/prefs.c: add command keybinding stuff
|
||||
|
||||
* src/metacity.schemas.in: add keybindings for running commands,
|
||||
and keys to store the commands themselves.
|
||||
|
||||
2002-07-10 Havoc Pennington <hp@redhat.com>
|
||||
|
||||
* src/display.c: properly attribute selection code to Matthias
|
||||
|
@ -102,6 +102,10 @@ static void handle_raise_or_lower (MetaDisplay *display,
|
||||
MetaWindow *window,
|
||||
XEvent *event,
|
||||
MetaKeyBinding *binding);
|
||||
static void handle_run_command (MetaDisplay *display,
|
||||
MetaWindow *window,
|
||||
XEvent *event,
|
||||
MetaKeyBinding *binding);
|
||||
|
||||
/* debug */
|
||||
static void handle_spew_mark (MetaDisplay *display,
|
||||
@ -190,6 +194,30 @@ static const MetaKeyHandler screen_handlers[] = {
|
||||
GINT_TO_POINTER (META_TAB_LIST_DOCKS) },
|
||||
{ META_KEYBINDING_SHOW_DESKTOP, handle_toggle_desktop,
|
||||
NULL },
|
||||
{ META_KEYBINDING_COMMAND_1, handle_run_command,
|
||||
GINT_TO_POINTER (0) },
|
||||
{ META_KEYBINDING_COMMAND_2, handle_run_command,
|
||||
GINT_TO_POINTER (1) },
|
||||
{ META_KEYBINDING_COMMAND_3, handle_run_command,
|
||||
GINT_TO_POINTER (2) },
|
||||
{ META_KEYBINDING_COMMAND_4, handle_run_command,
|
||||
GINT_TO_POINTER (3) },
|
||||
{ META_KEYBINDING_COMMAND_5, handle_run_command,
|
||||
GINT_TO_POINTER (4) },
|
||||
{ META_KEYBINDING_COMMAND_6, handle_run_command,
|
||||
GINT_TO_POINTER (5) },
|
||||
{ META_KEYBINDING_COMMAND_7, handle_run_command,
|
||||
GINT_TO_POINTER (6) },
|
||||
{ META_KEYBINDING_COMMAND_8, handle_run_command,
|
||||
GINT_TO_POINTER (7) },
|
||||
{ META_KEYBINDING_COMMAND_9, handle_run_command,
|
||||
GINT_TO_POINTER (8) },
|
||||
{ META_KEYBINDING_COMMAND_10, handle_run_command,
|
||||
GINT_TO_POINTER (9) },
|
||||
{ META_KEYBINDING_COMMAND_11, handle_run_command,
|
||||
GINT_TO_POINTER (10) },
|
||||
{ META_KEYBINDING_COMMAND_12, handle_run_command,
|
||||
GINT_TO_POINTER (11) },
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
@ -1893,6 +1921,86 @@ handle_activate_workspace (MetaDisplay *display,
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
error_on_command (int command_index,
|
||||
const char *command,
|
||||
const char *message)
|
||||
{
|
||||
GError *err;
|
||||
char *argv[6];
|
||||
char *key;
|
||||
|
||||
meta_warning ("Error on command %d \"%s\": %s\n",
|
||||
command_index, command, message);
|
||||
|
||||
key = meta_prefs_get_gconf_key_for_command (command_index);
|
||||
|
||||
argv[0] = METACITY_LIBEXECDIR"/metacity-dialog";
|
||||
argv[1] = "--command-failed-error";
|
||||
argv[2] = key;
|
||||
argv[3] = (char*) (command ? command : "");
|
||||
argv[4] = (char*) message;
|
||||
argv[5] = NULL;
|
||||
|
||||
err = NULL;
|
||||
if (!g_spawn_async_with_pipes ("/",
|
||||
argv,
|
||||
NULL,
|
||||
0,
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
&err))
|
||||
{
|
||||
meta_warning (_("Error launching metacity-dialog to print an error about a command: %s\n"),
|
||||
err->message);
|
||||
g_error_free (err);
|
||||
}
|
||||
|
||||
g_free (key);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_run_command (MetaDisplay *display,
|
||||
MetaWindow *window,
|
||||
XEvent *event,
|
||||
MetaKeyBinding *binding)
|
||||
{
|
||||
int which;
|
||||
const char *command;
|
||||
GError *err;
|
||||
|
||||
which = GPOINTER_TO_INT (binding->handler->data);
|
||||
|
||||
command = meta_prefs_get_command (which);
|
||||
|
||||
if (command == NULL)
|
||||
{
|
||||
char *s;
|
||||
|
||||
meta_topic (META_DEBUG_KEYBINDINGS,
|
||||
"No command %d to run in response to keybinding press\n",
|
||||
which);
|
||||
|
||||
s = g_strdup_printf (_("No command %d has been defined.\n"),
|
||||
which + 1);
|
||||
error_on_command (which, NULL, s);
|
||||
g_free (s);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
err = NULL;
|
||||
if (!g_spawn_command_line_async (command, &err))
|
||||
{
|
||||
error_on_command (which, command, err->message);
|
||||
|
||||
g_error_free (err);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
process_workspace_switch_grab (MetaDisplay *display,
|
||||
XEvent *event,
|
||||
|
@ -259,6 +259,35 @@ warn_about_no_sm_support (char **lame_apps)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
error_about_command (const char *gconf_key,
|
||||
const char *command,
|
||||
const char *error)
|
||||
{
|
||||
GtkWidget *dialog;
|
||||
|
||||
/* FIXME offer to change the value of the command's gconf key */
|
||||
|
||||
if (*command != '\0')
|
||||
dialog = gtk_message_dialog_new (NULL, 0,
|
||||
GTK_MESSAGE_ERROR,
|
||||
GTK_BUTTONS_CLOSE,
|
||||
_("There was an error running \"%s\":\n"
|
||||
"%s."),
|
||||
command, error);
|
||||
else
|
||||
dialog = gtk_message_dialog_new (NULL, 0,
|
||||
GTK_MESSAGE_ERROR,
|
||||
GTK_BUTTONS_CLOSE,
|
||||
"%s", error);
|
||||
|
||||
gtk_dialog_run (GTK_DIALOG (dialog));
|
||||
|
||||
gtk_widget_destroy (dialog);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
@ -295,7 +324,21 @@ main (int argc, char **argv)
|
||||
|
||||
return warn_about_no_sm_support (&argv[2]);
|
||||
}
|
||||
else if (strcmp (argv[1], "--command-failed-error") == 0)
|
||||
{
|
||||
|
||||
/* the args are the gconf key of the failed command, the text of
|
||||
* the command, and the error message
|
||||
*/
|
||||
if (argc != 5)
|
||||
{
|
||||
g_printerr ("bad args to metacity-dialog\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return error_about_command (argv[2], argv[3], argv[4]);
|
||||
}
|
||||
|
||||
g_printerr ("bad args to metacity-dialog\n");
|
||||
return 1;
|
||||
}
|
||||
|
@ -1231,6 +1231,71 @@ you set
|
||||
</locale>
|
||||
</schema>
|
||||
|
||||
<schema>
|
||||
<key>/schemas/apps/metacity/global_keybindings/run_command</key>
|
||||
<applyto>/apps/metacity/global_keybindings/run_command_1</applyto>
|
||||
<applyto>/apps/metacity/global_keybindings/run_command_2</applyto>
|
||||
<applyto>/apps/metacity/global_keybindings/run_command_3</applyto>
|
||||
<applyto>/apps/metacity/global_keybindings/run_command_4</applyto>
|
||||
<applyto>/apps/metacity/global_keybindings/run_command_5</applyto>
|
||||
<applyto>/apps/metacity/global_keybindings/run_command_6</applyto>
|
||||
<applyto>/apps/metacity/global_keybindings/run_command_7</applyto>
|
||||
<applyto>/apps/metacity/global_keybindings/run_command_8</applyto>
|
||||
<applyto>/apps/metacity/global_keybindings/run_command_9</applyto>
|
||||
<applyto>/apps/metacity/global_keybindings/run_command_10</applyto>
|
||||
<applyto>/apps/metacity/global_keybindings/run_command_11</applyto>
|
||||
<applyto>/apps/metacity/global_keybindings/run_command_12</applyto>
|
||||
<owner>metacity</owner>
|
||||
<type>string</type>
|
||||
<default>disabled</default>
|
||||
<locale name="C">
|
||||
<short>Run a defined command</short>
|
||||
<long>
|
||||
The keybinding that runs the correspondingly-numbered
|
||||
command in /apps/metacity/keybinding_commands
|
||||
|
||||
The format looks like "<Control>a" or
|
||||
"<Shift><Alt>F1.
|
||||
|
||||
The parser is fairly liberal and allows lower or upper case,
|
||||
and also abbreviations such as "<Ctl>" and
|
||||
"<Ctrl>". If you set the option to the special string
|
||||
"disabled", then there will be no keybinding for this
|
||||
action.
|
||||
</long>
|
||||
</locale>
|
||||
</schema>
|
||||
|
||||
<!-- commands to run with the run_command keybindings -->
|
||||
|
||||
<schema>
|
||||
<key>/schemas/apps/metacity/keybinding_commands/command</key>
|
||||
<applyto>/apps/metacity/keybinding_commands/command_1</applyto>
|
||||
<applyto>/apps/metacity/keybinding_commands/command_2</applyto>
|
||||
<applyto>/apps/metacity/keybinding_commands/command_3</applyto>
|
||||
<applyto>/apps/metacity/keybinding_commands/command_4</applyto>
|
||||
<applyto>/apps/metacity/keybinding_commands/command_5</applyto>
|
||||
<applyto>/apps/metacity/keybinding_commands/command_6</applyto>
|
||||
<applyto>/apps/metacity/keybinding_commands/command_7</applyto>
|
||||
<applyto>/apps/metacity/keybinding_commands/command_8</applyto>
|
||||
<applyto>/apps/metacity/keybinding_commands/command_9</applyto>
|
||||
<applyto>/apps/metacity/keybinding_commands/command_10</applyto>
|
||||
<applyto>/apps/metacity/keybinding_commands/command_11</applyto>
|
||||
<applyto>/apps/metacity/keybinding_commands/command_12</applyto>
|
||||
<owner>metacity</owner>
|
||||
<type>string</type>
|
||||
<default> </default>
|
||||
<locale name="C">
|
||||
<short>Commands to run in response to keybindings</short>
|
||||
<long>
|
||||
The /apps/metacity/global_keybindings/run_command_N
|
||||
keys define keybindings that correspond to these commands.
|
||||
Pressing the keybinding for run_command_N will
|
||||
execute command_N.
|
||||
</long>
|
||||
</locale>
|
||||
</schema>
|
||||
|
||||
<!-- Not used and/or crackrock -->
|
||||
|
||||
<schema>
|
||||
|
132
src/prefs.c
132
src/prefs.c
@ -25,6 +25,7 @@
|
||||
#include "util.h"
|
||||
#include <gconf/gconf-client.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* If you add a key, it needs updating in init() and in the gconf
|
||||
* notify listener and of course in the .schemas file
|
||||
@ -39,6 +40,7 @@
|
||||
#define KEY_APPLICATION_BASED "/apps/metacity/general/application_based"
|
||||
#define KEY_DISABLE_WORKAROUNDS "/apps/metacity/general/disable_workarounds"
|
||||
|
||||
#define KEY_COMMAND_PREFIX "/apps/metacity/keybinding_commands/command_"
|
||||
#define KEY_SCREEN_BINDINGS_PREFIX "/apps/metacity/global_keybindings"
|
||||
#define KEY_WINDOW_BINDINGS_PREFIX "/apps/metacity/window_keybindings"
|
||||
|
||||
@ -55,6 +57,9 @@ static gboolean application_based = FALSE;
|
||||
static gboolean disable_workarounds = FALSE;
|
||||
static gboolean auto_raise = FALSE;
|
||||
static gboolean auto_raise_delay = 500;
|
||||
#define NUM_COMMANDS 12
|
||||
static char *commands[NUM_COMMANDS] = { NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL, NULL, NULL, NULL, NULL };
|
||||
|
||||
static gboolean update_use_system_font (gboolean value);
|
||||
static gboolean update_titlebar_font (const char *value);
|
||||
@ -72,6 +77,9 @@ static gboolean update_screen_binding (const char *name,
|
||||
static void init_bindings (void);
|
||||
static gboolean update_binding (MetaKeyPref *binding,
|
||||
const char *value);
|
||||
static gboolean update_command (const char *name,
|
||||
const char *value);
|
||||
static void init_commands (void);
|
||||
|
||||
static void queue_changed (MetaPreference pref);
|
||||
static void change_notify (GConfClient *client,
|
||||
@ -284,6 +292,9 @@ meta_prefs_init (void)
|
||||
|
||||
/* Load keybindings prefs */
|
||||
init_bindings ();
|
||||
|
||||
/* commands */
|
||||
init_commands ();
|
||||
|
||||
gconf_client_notify_add (default_client, "/apps/metacity",
|
||||
change_notify,
|
||||
@ -505,6 +516,23 @@ change_notify (GConfClient *client,
|
||||
|
||||
if (update_auto_raise_delay (d))
|
||||
queue_changed (META_PREF_AUTO_RAISE_DELAY);
|
||||
|
||||
}
|
||||
else if (str_has_prefix (key, KEY_COMMAND_PREFIX))
|
||||
{
|
||||
const char *str;
|
||||
|
||||
if (value && value->type != GCONF_VALUE_STRING)
|
||||
{
|
||||
meta_warning (_("GConf key \"%s\" is set to an invalid type\n"),
|
||||
key);
|
||||
goto out;
|
||||
}
|
||||
|
||||
str = value ? gconf_value_get_string (value) : NULL;
|
||||
|
||||
if (update_command (key, str))
|
||||
queue_changed (META_PREF_COMMANDS);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -767,6 +795,9 @@ meta_preference_to_string (MetaPreference pref)
|
||||
|
||||
case META_PREF_AUTO_RAISE_DELAY:
|
||||
return "AUTO_RAISE_DELAY";
|
||||
|
||||
case META_PREF_COMMANDS:
|
||||
return "COMMANDS";
|
||||
}
|
||||
|
||||
return "(unknown)";
|
||||
@ -822,6 +853,18 @@ static MetaKeyPref screen_bindings[] = {
|
||||
{ META_KEYBINDING_CYCLE_WINDOWS, 0, 0 },
|
||||
{ META_KEYBINDING_CYCLE_PANELS, 0, 0 },
|
||||
{ META_KEYBINDING_SHOW_DESKTOP, 0, 0 },
|
||||
{ META_KEYBINDING_COMMAND_1, 0, 0 },
|
||||
{ META_KEYBINDING_COMMAND_2, 0, 0 },
|
||||
{ META_KEYBINDING_COMMAND_3, 0, 0 },
|
||||
{ META_KEYBINDING_COMMAND_4, 0, 0 },
|
||||
{ META_KEYBINDING_COMMAND_5, 0, 0 },
|
||||
{ META_KEYBINDING_COMMAND_6, 0, 0 },
|
||||
{ META_KEYBINDING_COMMAND_7, 0, 0 },
|
||||
{ META_KEYBINDING_COMMAND_8, 0, 0 },
|
||||
{ META_KEYBINDING_COMMAND_9, 0, 0 },
|
||||
{ META_KEYBINDING_COMMAND_10, 0, 0 },
|
||||
{ META_KEYBINDING_COMMAND_11, 0, 0 },
|
||||
{ META_KEYBINDING_COMMAND_12, 0, 0 },
|
||||
{ NULL, 0, 0 }
|
||||
};
|
||||
|
||||
@ -904,6 +947,33 @@ init_bindings (void)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
init_commands (void)
|
||||
{
|
||||
int i;
|
||||
GError *err;
|
||||
|
||||
i = 0;
|
||||
while (i < NUM_COMMANDS)
|
||||
{
|
||||
char *str_val;
|
||||
char *key;
|
||||
|
||||
key = meta_prefs_get_gconf_key_for_command (i);
|
||||
|
||||
err = NULL;
|
||||
str_val = gconf_client_get_string (default_client, key, &err);
|
||||
cleanup_error (&err);
|
||||
|
||||
update_command (key, str_val);
|
||||
|
||||
g_free (str_val);
|
||||
g_free (key);
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
update_binding (MetaKeyPref *binding,
|
||||
const char *value)
|
||||
@ -1004,6 +1074,68 @@ update_screen_binding (const char *name,
|
||||
return find_and_update_binding (screen_bindings, name, value);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
update_command (const char *name,
|
||||
const char *value)
|
||||
{
|
||||
char *p;
|
||||
int i;
|
||||
|
||||
p = strrchr (name, '_');
|
||||
if (p == NULL)
|
||||
{
|
||||
meta_topic (META_DEBUG_KEYBINDINGS,
|
||||
"Command %s has no underscore?\n", name);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
++p;
|
||||
|
||||
if (!g_ascii_isdigit (*p))
|
||||
{
|
||||
meta_topic (META_DEBUG_KEYBINDINGS,
|
||||
"Command %s doesn't end in number?\n", name);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
i = atoi (p);
|
||||
i -= 1; /* count from 0 not 1 */
|
||||
|
||||
if (i >= NUM_COMMANDS)
|
||||
{
|
||||
meta_topic (META_DEBUG_KEYBINDINGS,
|
||||
"Command %d is too highly numbered, ignoring\n", i);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_free (commands[i]);
|
||||
commands[i] = g_strdup (value);
|
||||
|
||||
meta_topic (META_DEBUG_KEYBINDINGS,
|
||||
"Updated command %d to \"%s\"\n",
|
||||
i, commands[i] ? commands[i] : "none");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
const char*
|
||||
meta_prefs_get_command (int i)
|
||||
{
|
||||
g_return_val_if_fail (i >= 0 && i < NUM_COMMANDS, NULL);
|
||||
|
||||
return commands[i];
|
||||
}
|
||||
|
||||
char*
|
||||
meta_prefs_get_gconf_key_for_command (int i)
|
||||
{
|
||||
char *key;
|
||||
|
||||
key = g_strdup_printf (KEY_COMMAND_PREFIX"%d", i + 1);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
void
|
||||
meta_prefs_get_screen_bindings (const MetaKeyPref **bindings,
|
||||
int *n_bindings)
|
||||
|
33
src/prefs.h
33
src/prefs.h
@ -37,7 +37,8 @@ typedef enum
|
||||
META_PREF_APPLICATION_BASED,
|
||||
META_PREF_WINDOW_KEYBINDINGS,
|
||||
META_PREF_SCREEN_KEYBINDINGS,
|
||||
META_PREF_DISABLE_WORKAROUNDS
|
||||
META_PREF_DISABLE_WORKAROUNDS,
|
||||
META_PREF_COMMANDS
|
||||
} MetaPreference;
|
||||
|
||||
typedef void (* MetaPrefsChangedFunc) (MetaPreference pref,
|
||||
@ -61,6 +62,10 @@ gboolean meta_prefs_get_disable_workarounds (void);
|
||||
gboolean meta_prefs_get_auto_raise (void);
|
||||
int meta_prefs_get_auto_raise_delay (void);
|
||||
|
||||
const char* meta_prefs_get_command (int i);
|
||||
|
||||
char* meta_prefs_get_gconf_key_for_command (int i);
|
||||
|
||||
void meta_prefs_set_num_workspaces (int n_workspaces);
|
||||
|
||||
/* Screen bindings */
|
||||
@ -85,6 +90,18 @@ void meta_prefs_set_num_workspaces (int n_workspaces);
|
||||
#define META_KEYBINDING_CYCLE_WINDOWS "cycle_windows"
|
||||
#define META_KEYBINDING_CYCLE_PANELS "cycle_panels"
|
||||
#define META_KEYBINDING_SHOW_DESKTOP "show_desktop"
|
||||
#define META_KEYBINDING_COMMAND_1 "run_command_1"
|
||||
#define META_KEYBINDING_COMMAND_2 "run_command_2"
|
||||
#define META_KEYBINDING_COMMAND_3 "run_command_3"
|
||||
#define META_KEYBINDING_COMMAND_4 "run_command_4"
|
||||
#define META_KEYBINDING_COMMAND_5 "run_command_5"
|
||||
#define META_KEYBINDING_COMMAND_6 "run_command_6"
|
||||
#define META_KEYBINDING_COMMAND_7 "run_command_7"
|
||||
#define META_KEYBINDING_COMMAND_8 "run_command_8"
|
||||
#define META_KEYBINDING_COMMAND_9 "run_command_9"
|
||||
#define META_KEYBINDING_COMMAND_10 "run_command_10"
|
||||
#define META_KEYBINDING_COMMAND_11 "run_command_11"
|
||||
#define META_KEYBINDING_COMMAND_12 "run_command_12"
|
||||
|
||||
/* Window bindings */
|
||||
#define META_KEYBINDING_WINDOW_MENU "activate_window_menu"
|
||||
@ -137,7 +154,19 @@ typedef enum _MetaKeyBindingAction
|
||||
META_KEYBINDING_ACTION_SWITCH_PANELS,
|
||||
META_KEYBINDING_ACTION_CYCLE_WINDOWS,
|
||||
META_KEYBINDING_ACTION_CYCLE_PANELS,
|
||||
META_KEYBINDING_ACTION_SHOW_DESKTOP
|
||||
META_KEYBINDING_ACTION_SHOW_DESKTOP,
|
||||
META_KEYBINDING_ACTION_COMMAND_1,
|
||||
META_KEYBINDING_ACTION_COMMAND_2,
|
||||
META_KEYBINDING_ACTION_COMMAND_3,
|
||||
META_KEYBINDING_ACTION_COMMAND_4,
|
||||
META_KEYBINDING_ACTION_COMMAND_5,
|
||||
META_KEYBINDING_ACTION_COMMAND_6,
|
||||
META_KEYBINDING_ACTION_COMMAND_7,
|
||||
META_KEYBINDING_ACTION_COMMAND_8,
|
||||
META_KEYBINDING_ACTION_COMMAND_9,
|
||||
META_KEYBINDING_ACTION_COMMAND_10,
|
||||
META_KEYBINDING_ACTION_COMMAND_11,
|
||||
META_KEYBINDING_ACTION_COMMAND_12,
|
||||
} MetaKeyBindingAction;
|
||||
|
||||
typedef struct
|
||||
|
Loading…
Reference in New Issue
Block a user