X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/dfc35f5fca6653357bbc8f1582aa86e65877267c..92f94a128df584f5a2040363a97c2f30530f059c:/src/xrdb.c diff --git a/src/xrdb.c b/src/xrdb.c index 02e28f30cd..79f1da81a4 100644 --- a/src/xrdb.c +++ b/src/xrdb.c @@ -1,35 +1,51 @@ /* Deal with the X Resource Manager. - Copyright (C) 1990, 1992 Free Software Foundation. + Copyright (C) 1990, 1993, 1994, 2000, 2001, 2002, 2003, 2004, + 2005, 2006, 2007 Free Software Foundation, Inc. -This program is free software; you can redistribute it and/or modify +This file is part of GNU Emacs. + +GNU Emacs 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, or (at your option) +the Free Software Foundation; either version 3, or (at your option) any later version. -This program is distributed in the hope that it will be useful, +GNU Emacs 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; see the file COPYING. If not, write to -the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ +along with GNU Emacs; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. */ /* Written by jla, 4/90 */ #ifdef emacs -#include "config.h" +#include +#endif + +#ifdef HAVE_UNISTD_H +#include #endif +#include + +#include + #if 1 /* I'd really appreciate it if this code could go away... -JimB */ -/* this avoids lossage in the `dual-universe' headers on AT&T SysV X11 */ -#ifdef USG5 +/* This avoids lossage in the `dual-universe' headers on AT&T SysV + X11. Don't do it on Solaris, because it breaks compilation with + XFree86 4.0.3 (and probably many other X11R6 releases) on Solaris + 2 */ +#if defined(USG5) && !defined(SOLARIS2) +#ifndef SYSV #define SYSV -#include -#endif /* USG5 */ +#endif +#endif /* USG5 && !SOLARIS2 */ #endif /* 1 */ - + #include #include #if 0 @@ -38,244 +54,396 @@ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include -#include -#ifdef VMS -#include "vms-pwd.h" -#else +#ifdef HAVE_PWD_H #include #endif #include -#ifndef MAXPATHLEN -#define MAXPATHLEN 256 +#if !defined(S_ISDIR) && defined(S_IFDIR) +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif +#include "lisp.h" + extern char *getenv (); + +/* This does cause trouble on AIX. I'm going to take the comment at + face value. */ +#if 0 extern short getuid (); /* If this causes portability problems, I think we should just delete it; it'll default to `int' anyway. */ +#endif + +#ifdef DECLARE_GETPWUID_WITH_UID_T +extern struct passwd *getpwuid (uid_t); +extern struct passwd *getpwnam (const char *); +#else extern struct passwd *getpwuid (); extern struct passwd *getpwnam (); +#endif + +extern char *get_system_name (); + +/* Make sure not to #include anything after these definitions. Let's + not step on anyone's prototypes. */ +#ifdef emacs +/* darwin.h may have already defined these. */ +#undef malloc +#undef realloc +#undef free +#define malloc xmalloc +#define realloc xrealloc +#define free xfree +#endif + +char *x_get_string_resource (); +static int file_p (); + + +/* X file search path processing. */ + + +/* The string which gets substituted for the %C escape in XFILESEARCHPATH + and friends, or zero if none was specified. */ +char *x_customization_string; + + +/* Return the value of the emacs.customization (Emacs.Customization) + resource, for later use in search path decoding. If we find no + such resource, return zero. */ +char * +x_get_customization_string (db, name, class) + XrmDatabase db; + char *name, *class; +{ + char *full_name + = (char *) alloca (strlen (name) + sizeof ("customization") + 3); + char *full_class + = (char *) alloca (strlen (class) + sizeof ("Customization") + 3); + char *result; + + sprintf (full_name, "%s.%s", name, "customization"); + sprintf (full_class, "%s.%s", class, "Customization"); + + result = x_get_string_resource (db, full_name, full_class); + + if (result) + { + char *copy = (char *) malloc (strlen (result) + 1); + strcpy (copy, result); + return copy; + } + else + return 0; +} + + +/* Expand all the Xt-style %-escapes in STRING, whose length is given + by STRING_LEN. Here are the escapes we're supposed to recognize: + + %N The value of the application's class name + %T The value of the type parameter ("app-defaults" in this + context) + %S The value of the suffix parameter ("" in this context) + %L The language string associated with the specified display + (We use the "LANG" environment variable here, if it's set.) + %l The language part of the display's language string + (We treat this just like %L. If someone can tell us what + we're really supposed to do, dandy.) + %t The territory part of the display's language string + (This never gets used.) + %c The codeset part of the display's language string + (This never gets used either.) + %C The customization string retrieved from the resource + database associated with display. + (This is x_customization_string.) + + Return the expanded file name if it exists and is readable, and + refers to %L only when the LANG environment variable is set, or + otherwise provided by X. + + ESCAPED_SUFFIX and SUFFIX are postpended to STRING if they are + non-zero. %-escapes in ESCAPED_SUFFIX are expanded; STRING is left + alone. + + Return NULL otherwise. */ static char * -gethomedir (dirname) - char *dirname; +magic_file_p (string, string_len, class, escaped_suffix, suffix) + char *string; + int string_len; + char *class, *escaped_suffix, *suffix; { - int uid; - struct passwd *pw; - char *ptr; + char *lang = getenv ("LANG"); - if ((ptr = getenv ("HOME")) == NULL) + int path_size = 100; + char *path = (char *) malloc (path_size); + int path_len = 0; + + char *p = string; + + while (p < string + string_len) { - if ((ptr = getenv ("USER")) != NULL) - pw = getpwnam (ptr); - else + /* The chunk we're about to stick on the end of result. */ + char *next = NULL; + int next_len; + + if (*p == '%') { - uid = getuid (); - pw = getpwuid (uid); + p++; + + if (p >= string + string_len) + next_len = 0; + else + switch (*p) + { + case '%': + next = "%"; + next_len = 1; + break; + + case 'C': + next = (x_customization_string + ? x_customization_string + : ""); + next_len = strlen (next); + break; + + case 'N': + next = class; + next_len = strlen (class); + break; + + case 'T': + next = "app-defaults"; + next_len = strlen (next); + break; + + default: + case 'S': + next_len = 0; + break; + + case 'L': + case 'l': + if (! lang) + { + free (path); + return NULL; + } + + next = lang; + next_len = strlen (next); + break; + + case 't': + case 'c': + free (path); + return NULL; + } } - if (pw) - ptr = pw->pw_dir; else + next = p, next_len = 1; + + /* Do we have room for this component followed by a '\0' ? */ + if (path_len + next_len + 1 > path_size) { - ptr = NULL; - *dirname = '\0'; + path_size = (path_len + next_len + 1) * 2; + path = (char *) realloc (path, path_size); } - } - if (ptr != NULL) - strcpy (dirname, ptr); + bcopy (next, path + path_len, next_len); + path_len += next_len; - dirname += strlen (dirname); - *dirname = '/'; - dirname++; - *dirname = '\0'; + p++; - return dirname; -} + /* If we've reached the end of the string, append ESCAPED_SUFFIX. */ + if (p >= string + string_len && escaped_suffix) + { + string = escaped_suffix; + string_len = strlen (string); + p = string; + escaped_suffix = NULL; + } + } -static int -file_p (path) - char *path; -{ - struct stat status; + /* Perhaps we should add the SUFFIX now. */ + if (suffix) + { + int suffix_len = strlen (suffix); - return (access (path, 4) == 0 /* exists and is readable */ - && stat (path, &status) == 0 /* get the status */ - && (status.st_mode & S_IFDIR) == 0); /* not a directory */ -} + if (path_len + suffix_len + 1 > path_size) + { + path_size = (path_len + suffix_len + 1); + path = (char *) realloc (path, path_size); + } -#if 0 -#define X_DEFAULT_SEARCH_PATH "/usr/lib/X11/" -#endif + bcopy (suffix, path + path_len, suffix_len); + path_len += suffix_len; + } -/* Isn't this just disgusting? */ + path[path_len] = '\0'; -#define X_DEFAULT_SEARCH_PATH "/usr/lib/X11/%L/%T/%N%S:/usr/lib/X11/%l/%T/%N%S:/usr/lib/X11/%T/%N%S" + if (! file_p (path)) + { + free (path); + return NULL; + } -static int -decode_magic (string, file, return_path) - char *string, *file, *return_path; + return path; +} + + +static char * +gethomedir () { - char *p = string; - char *t = return_path; + struct passwd *pw; + char *ptr; + char *copy; - while (*p) + if ((ptr = getenv ("HOME")) == NULL) { - if (*p == '%') - switch (*++p) - { - case '%': - *t++ = '%'; - p++; - break; - - case 'N': - case 'T': - case 'S': - case 'L': - case 'l': - case 't': - case 'c': - default: - p++; - if (*t == '/' && *p == '/') - p++; - break; - } + if ((ptr = getenv ("LOGNAME")) != NULL + || (ptr = getenv ("USER")) != NULL) + pw = getpwnam (ptr); else - *t++ = *p++; + pw = getpwuid (getuid ()); + + if (pw) + ptr = pw->pw_dir; } - *t = '\0'; - strcat (return_path, file); - if (file_p (return_path)) - return 1; + if (ptr == NULL) + return xstrdup ("/"); - return_path[0] = '\0'; - return 0; + copy = (char *) malloc (strlen (ptr) + 2); + strcpy (copy, ptr); + strcat (copy, "/"); + + return copy; } + static int -magic_searchpath_decoder (incantation_string, file, return_path) - char *incantation_string, *return_path, *file; +file_p (filename) + char *filename; { - register char *s = incantation_string; - register char *p; + struct stat status; - /* Must be big enough for "%N%S". */ - register int string_size = MAXPATHLEN; - register char *string = (char *) alloca (string_size * sizeof (*string)); + return (access (filename, 4) == 0 /* exists and is readable */ + && stat (filename, &status) == 0 /* get the status */ + && (S_ISDIR (status.st_mode)) == 0); /* not a directory */ +} - while (*s) - { - p = s; - while (*p && *p != ':') - p++; +/* Find the first element of SEARCH_PATH which exists and is readable, + after expanding the %-escapes. Return 0 if we didn't find any, and + the path name of the one we found otherwise. */ - if (*p == ':' && *(p + 1) == ':') - { - /* We know string is big enough for this. */ - bcopy ("%N%S", string, 5); - if (decode_magic (string, file, return_path)) - return 1; +static char * +search_magic_path (search_path, class, escaped_suffix, suffix) + char *search_path, *class, *escaped_suffix, *suffix; +{ + register char *s, *p; - s = p + 1; - continue; - } + for (s = search_path; *s; s = p) + { + for (p = s; *p && *p != ':'; p++) + ; if (p > s) { - int len = p - s; - - if (string_size < len+1) - { - string_size = 2 * len; - string = (char *) alloca (string_size * sizeof (*string)); - } - bcopy (s, string, len); - string[len + 1] = '\0'; - if (decode_magic (string, file, return_path)) - return 1; + char *path = magic_file_p (s, p - s, class, escaped_suffix, suffix); + if (path) + return path; + } + else if (*p == ':') + { + char *path; + + s = "%N%S"; + path = magic_file_p (s, strlen (s), class, escaped_suffix, suffix); + if (path) + return path; } - if (p) - s = p + 1; - else - return 0; + if (*p == ':') + p++; } return 0; } +/* Producing databases for individual sources. */ + static XrmDatabase get_system_app (class) char *class; { - XrmDatabase db; - char path[MAXPATHLEN]; - char *p; + XrmDatabase db = NULL; + char *path; - if ((p = getenv ("XFILESEARCHPATH")) == NULL) - p = X_DEFAULT_SEARCH_PATH; + path = getenv ("XFILESEARCHPATH"); + if (! path) path = PATH_X_DEFAULTS; - if (! magic_searchpath_decoder (p, class, path)) - return NULL; + path = search_magic_path (path, class, 0, 0); + if (path) + { + db = XrmGetFileDatabase (path); + free (path); + } - db = XrmGetFileDatabase (path); return db; } + static XrmDatabase get_fallback (display) Display *display; { - XrmDatabase db; - return NULL; } + static XrmDatabase get_user_app (class) char *class; { - XrmDatabase db; - char *magic_path; - char path[MAXPATHLEN]; - - if ((magic_path = getenv ("XUSERFILESEARCHPATH")) == NULL) + char *path; + char *file = 0; + char *free_it = 0; + + /* Check for XUSERFILESEARCHPATH. It is a path of complete file + names, not directories. */ + if (((path = getenv ("XUSERFILESEARCHPATH")) + && (file = search_magic_path (path, class, 0, 0))) + + /* Check for APPLRESDIR; it is a path of directories. In each, + we have to search for LANG/CLASS and then CLASS. */ + || ((path = getenv ("XAPPLRESDIR")) + && ((file = search_magic_path (path, class, "/%L/%N", 0)) + || (file = search_magic_path (path, class, "/%N", 0)))) + + /* Check in the home directory. This is a bit of a hack; let's + hope one's home directory doesn't contain any %-escapes. */ + || (free_it = gethomedir (), + ((file = search_magic_path (free_it, class, "%L/%N", 0)) + || (file = search_magic_path (free_it, class, "%N", 0))))) { - char homedir[MAXPATHLEN]; - char *default_magic; - char *p; - - gethomedir (homedir); - - if ((p = getenv ("XAPPLRESDIR")) == NULL) - { - default_magic = "%s/%%L/%%N:%s/%%l/%%N:%s/%%N"; - magic_path = (char *) alloca ((3 * strlen (homedir)) - + strlen (default_magic)); - sprintf (magic_path, default_magic, homedir, homedir, homedir); - } - else - { - default_magic = "%s/%%L/%%N:%s/%%l/%%N:%s/%%N:%s/%%N"; - magic_path = (char *) alloca ((3 * strlen (p)) - + strlen (default_magic) - + strlen (homedir)); - sprintf (magic_path, default_magic, p, p, p, homedir); - } + XrmDatabase db = XrmGetFileDatabase (file); + free (file); + if (free_it) + free (free_it); + return db; } - if (! magic_searchpath_decoder (magic_path, class, path)) - return NULL; - - db = XrmGetFileDatabase (path); - return db; + if (free_it) + free (free_it); + return NULL; } + static XrmDatabase get_user_db (display) Display *display; @@ -283,7 +451,7 @@ get_user_db (display) XrmDatabase db; char *xdefs; -#ifdef HAVE_X11R4 +#ifdef PBaseSize /* Cheap way to test for X11R4 or later. */ xdefs = XResourceManagerString (display); #else xdefs = display->xdefaults; @@ -293,12 +461,27 @@ get_user_db (display) db = XrmGetStringDatabase (xdefs); else { - char xdefault[MAXPATHLEN]; + char *home; + char *xdefault; - gethomedir (xdefault); + home = gethomedir (); + xdefault = (char *) malloc (strlen (home) + sizeof (".Xdefaults")); + strcpy (xdefault, home); strcat (xdefault, ".Xdefaults"); db = XrmGetFileDatabase (xdefault); + free (home); + free (xdefault); + } + +#ifdef HAVE_XSCREENRESOURCESTRING + /* Get the screen-specific resources too. */ + xdefs = XScreenResourceString (DefaultScreenOfDisplay (display)); + if (xdefs != NULL) + { + XrmMergeDatabases (XrmGetStringDatabase (xdefs), &db); + XFree (xdefs); } +#endif return db; } @@ -308,20 +491,29 @@ get_environ_db () { XrmDatabase db; char *p; - char path[MAXPATHLEN]; + char *path = 0, *home = 0, *host; if ((p = getenv ("XENVIRONMENT")) == NULL) { - gethomedir (path); - strcat (path, ".Xdefaults-"); - gethostname (path + strlen (path), MAXPATHLEN - strlen (path)); + home = gethomedir (); + host = get_system_name (); + path = (char *) malloc (strlen (home) + + sizeof (".Xdefaults-") + + strlen (host)); + sprintf (path, "%s%s%s", home, ".Xdefaults-", host); p = path; } db = XrmGetFileDatabase (p); + + if (path) free (path); + if (home) free (home); + return db; } +/* External interface. */ + /* Types of values that we can find in a database */ #define XrmStringType "String" /* String representation */ @@ -330,18 +522,103 @@ XrmRepresentation x_rm_string; /* Quark representation */ /* Load X resources based on the display and a possible -xrm option. */ XrmDatabase -x_load_resources (display, xrm_string, myclass) +x_load_resources (display, xrm_string, myname, myclass) Display *display; - char *xrm_string, *myclass; + char *xrm_string, *myname, *myclass; { - char *xdefs; + XrmDatabase user_database; XrmDatabase rdb; XrmDatabase db; + char line[256]; + + char *helv = "-*-helvetica-medium-r-*--*-120-*-*-*-*-iso8859-1"; + +#ifdef USE_MOTIF + char *courier = "-*-courier-medium-r-*-*-*-120-*-*-*-*-iso8859-1"; + extern Lisp_Object Vdouble_click_time; +#endif x_rm_string = XrmStringToQuark (XrmStringType); +#ifndef USE_X_TOOLKIT + /* pmr@osf.org says this shouldn't be done if USE_X_TOOLKIT. + I suspect it's because the toolkit version does this elsewhere. */ XrmInitialize (); +#endif rdb = XrmGetStringDatabase (""); + /* Add some font defaults. If the font `helv' doesn't exist, widgets + will use some other default font. */ +#ifdef USE_MOTIF + + sprintf (line, "%s.pane.background: grey75", myclass); + XrmPutLineResource (&rdb, line); + sprintf (line, "%s*fontList: %s", myclass, helv); + XrmPutLineResource (&rdb, line); + sprintf (line, "%s*menu*background: grey75", myclass); + XrmPutLineResource (&rdb, line); + sprintf (line, "%s*menubar*background: grey75", myclass); + XrmPutLineResource (&rdb, line); + sprintf (line, "%s*verticalScrollBar.background: grey75", myclass); + XrmPutLineResource (&rdb, line); + sprintf (line, "%s*verticalScrollBar.troughColor: grey75", myclass); + XrmPutLineResource (&rdb, line); + sprintf (line, "%s.dialog*.background: grey75", myclass); + XrmPutLineResource (&rdb, line); + sprintf (line, "%s*fsb.Text.background: white", myclass); + XrmPutLineResource (&rdb, line); + sprintf (line, "%s*fsb.FilterText.background: white", myclass); + XrmPutLineResource (&rdb, line); + sprintf (line, "%s*fsb*DirList.background: white", myclass); + XrmPutLineResource (&rdb, line); + sprintf (line, "%s*fsb*ItemsList.background: white", myclass); + XrmPutLineResource (&rdb, line); + sprintf (line, "%s*fsb*background: grey75", myclass); + XrmPutLineResource (&rdb, line); + sprintf (line, "%s*fsb.Text.fontList: %s", myclass, courier); + XrmPutLineResource (&rdb, line); + sprintf (line, "%s*fsb.FilterText.fontList: %s", myclass, courier); + XrmPutLineResource (&rdb, line); + sprintf (line, "%s*fsb*ItemsList.fontList: %s", myclass, courier); + XrmPutLineResource (&rdb, line); + sprintf (line, "%s*fsb*DirList.fontList: %s", myclass, courier); + XrmPutLineResource (&rdb, line); + + /* Set double click time of list boxes in the file selection + dialog from `double-click-time'. */ + if (INTEGERP (Vdouble_click_time) && XINT (Vdouble_click_time) > 0) + { + sprintf (line, "%s*fsb*DirList.doubleClickInterval: %d", + myclass, XFASTINT (Vdouble_click_time)); + XrmPutLineResource (&rdb, line); + sprintf (line, "%s*fsb*ItemsList.doubleClickInterval: %d", + myclass, XFASTINT (Vdouble_click_time)); + XrmPutLineResource (&rdb, line); + } + +#else /* not USE_MOTIF */ + + sprintf (line, "Emacs.dialog*.font: %s", helv); + XrmPutLineResource (&rdb, line); + sprintf (line, "Emacs.dialog*.background: grey75"); + XrmPutLineResource (&rdb, line); + sprintf (line, "*XlwMenu*font: %s", helv); + XrmPutLineResource (&rdb, line); + sprintf (line, "*XlwMenu*background: grey75"); + XrmPutLineResource (&rdb, line); + sprintf (line, "Emacs*verticalScrollBar.background: grey75"); + XrmPutLineResource (&rdb, line); + +#endif /* not USE_MOTIF */ + + user_database = get_user_db (display); + + /* Figure out what the "customization string" is, so we can use it + to decode paths. */ + if (x_customization_string) + free (x_customization_string); + x_customization_string + = x_get_customization_string (user_database, myname, myclass); + /* Get application system defaults */ db = get_system_app (myclass); if (db != NULL) @@ -358,15 +635,14 @@ x_load_resources (display, xrm_string, myclass) XrmMergeDatabases (db, &rdb); /* get User defaults */ - db = get_user_db (display); - if (db != NULL) - XrmMergeDatabases (db, &rdb); + if (user_database != NULL) + XrmMergeDatabases (user_database, &rdb); /* Get Environment defaults. */ db = get_environ_db (); if (db != NULL) XrmMergeDatabases (db, &rdb); - + /* Last, merge in any specification from the command line. */ if (xrm_string != NULL) { @@ -378,6 +654,7 @@ x_load_resources (display, xrm_string, myclass) return rdb; } + /* Retrieve the value of the resource specified by NAME with class CLASS and of type TYPE from database RDB. The value is returned in RET_VALUE. */ @@ -400,7 +677,7 @@ x_get_resource (rdb, name, class, expected_type, ret_value) && (type == expected_type)) { if (type == x_rm_string) - (char *) ret_value->addr = value.addr; + ret_value->addr = (char *) value.addr; else bcopy (value.addr, ret_value->addr, ret_value->size); @@ -426,9 +703,30 @@ x_get_string_resource (rdb, name, class) return (char *) 0; } +/* Stand-alone test facilities. */ + #ifdef TESTRM -#include -#include "arg-list.h" + +typedef char **List; +#define arg_listify(len, list) (list) +#define car(list) (*(list)) +#define cdr(list) (list + 1) +#define NIL(list) (! *(list)) +#define free_arglist(list) + +static List +member (elt, list) + char *elt; + List list; +{ + List p; + + for (p = list; ! NIL (p); p = cdr (p)) + if (! strcmp (elt, car (p))) + return p; + + return p; +} static void fatal (msg, prog, x1, x2, x3, x4, x5) @@ -449,9 +747,9 @@ main (argc, argv) char **argv; { Display *display; - char *displayname, *resource_string, *class; + char *displayname, *resource_string, *class, *name; XrmDatabase xdb; - List *arg_list, *lp; + List arg_list, lp; arg_list = arg_listify (argc, argv); @@ -473,32 +771,41 @@ main (argc, argv) else class = "Emacs"; - free_arglist (arg_list); - + lp = member ("-n", arg_list); + if (! NIL (lp)) + name = car (cdr (lp)); + else + name = "emacs"; + free_arglist (arg_list); if (!(display = XOpenDisplay (displayname))) fatal ("Can't open display '%s'\n", XDisplayName (displayname)); - xdb = x_load_resources (display, resource_string, class); + xdb = x_load_resources (display, resource_string, name, class); -#if 0 /* In a real program, you'd want to also do this: */ display->db = xdb; -#endif while (1) { - char line[90]; + char query_name[90]; + char query_class[90]; + + printf ("Name: "); + gets (query_name); - printf ("String: "); - gets (line); - if (strlen (line)) + if (strlen (query_name)) { - char *value = x_get_string_resource (xdb, line, class); + char *value; + + printf ("Class: "); + gets (query_class); + + value = x_get_string_resource (xdb, query_name, query_class); if (value != NULL) - printf ("\t%s: %s\n\n", line, value); + printf ("\t%s(%s): %s\n\n", query_name, query_class, value); else printf ("\tNo Value.\n\n"); } @@ -510,3 +817,6 @@ main (argc, argv) XCloseDisplay (display); } #endif /* TESTRM */ + +/* arch-tag: 37e6fbab-ed05-4363-9e76-6c4109ed511f + (do not change this comment) */