#define PROG_NAME "xjsmessage" #define PROG_DESC "displays a message and gets for a button click from user" #define PROG_VERS "2005-01-30" /* Copyright (c) 1988, 1991, 1994 X Consortium.*/ /* Please read the full copyright notice at the end of this file.*/ /* Last edited on 2021-06-17 09:36:36 by jstolfi*/ #define PROG_CLASS "XJSMessage" #define PROG_HELP \ " " PROG_NAME " \\\n" \ " [--message=|-m \'{STRING}\'] \\\n" \ " [--message-file=|-f {FILENAME}] \\\n" \ " [--title=|-t \'{STRING}\'] \\\n" \ " [--buttons=|-b \'{NAME}[:{CODE}],..\'] \\\n" \ " [--timeout=|-w {SECS}] \\\n" \ " [--on-return-key|-R \'{NAME}\'] \\\n" \ " [--on-destroy|-D \'{NAME}\'] \\\n" \ " [--on-timeout|-T \'{NAME}\'] \\\n" \ " [--print-name|-p] \\\n" \ " [--print-code|-o]\\\n" \ " [--screen-center|-c | --near-mouse|-n] \\\n" \ " [--grab-focus|-g] \\\n" \ " [--version|-v]\\\n" \ " [--help|-h] \\\n" \ " [--info|-i]" #define PROG_INFO \ "NAME\n" \ " " PROG_NAME " - " PROG_DESC "\n" \ "\n" \ "SYNOPSIS\n" \ PROG_HELP "\n" \ "\n" \ "DESCRIPTION\n" \ " Displays message and row of buttons, waits for user to click one.\n" \ " Returns button number as exit status, optionally prints button name.\n" \ "\n" \ "OPTIONS\n" \ " --message=|-m \'{STRING}\' Message to display inside window.\n" \ " --message-file=|-f FILENAME File where to read the message from.\n" \ " --title=|-t \'{STRING}\' Window title.\n" \ " --buttons=|-b \'{NAME}[:{CODE}],..\' Comma-separated button names and exit codes.\n" \ " --timeout=|-w SECS Exit automatically after SECS seconds.\n" \ " --on-return-key=|-R \'{NAME}\' Button to be activated by RETURN key.\n" \ " --on-destroy=|-D \'{NAME}\' Button to be activated by destroying the window.\n" \ " --on-timeout=|-T \'{NAME}\' Button to be activated on timeout.\n" \ " --print-name|-p Print selected button name to stdout.\n" \ " --print-code|-o Print selected button code to stdout.\n" \ " --screen-center|-c Bring up window near center of screen\n" \ " --near-mouse|-n Bring up window near mouse\n" \ " --grab-focus|-g Grab the input focus on startup.\n" \ " --version|-v Print the program's version.\n" \ " --help|-h Print a short help message.\n" \ " --info|-i Print this message.\n" \ "\n" \ " A long-form option must be attached to its value by \'=\'.\n" \ " A short-form option and its value must be separate arguments.\n" \ " The default button codes are 101,102,..., overriden by \':{CODE}\'.\n" \ " If the button list is not specified, it defaults to \'OK:0\'.\n" \ " The RETURN-key is active only if the --on-return-key is given.\n" \ " The default for --on-timeout is the first button.\n" \ " The default for --on-destroy is the last button.\n" \ "\n" \ "SEE ALSO\n" \ " xmessage(1) -- the original X11 version.\n" \ "\n" \ "AUTHOR\n" \ " Original author of xmessage(1) unknown.\n" \ " Copyright (c) 1988, 1991, 1994 X Consortium" \ "\n" \ " Rewritten on 2005-01-29 by Jorge Stolfi, IC-UNICAMP." #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #define INF INFINITY #define bool_t Boolean /* Button description: */ typedef struct Button_Data_t { char *name; int code; bool_t print_code; Widget widget; struct Button_Data_t *next; } Button_Data_t; /* Arguments from command line: */ typedef struct C_Options_t { char *message_file; /* Name of file containing message, or NULL. */ char *message; /* Message to display, or NULL. */ char *title; /* Window title, or NULL. */ Button_Data_t *buttons; /* List of button data records. */ Button_Data_t *on_enter_key_btn; /* Button activated by ENTER key, or NULL. */ Button_Data_t *on_timeout_btn; /* Button activated on timeout, or NULL. */ Button_Data_t *on_destroy_btn; /* Button activated by window-kill, or NULL. */ bool_t screen_center; bool_t near_mouse; bool_t grab_focus; double timeout; /* Timeout in seconds. */ } C_Options_t; /* Arguments fetched from X resources: */ typedef struct X_Options_t { Dimension maxHeight; Dimension maxWidth; } X_Options_t; /* INTERNAL PROTOTYPES */ void init_application(int *argc, char **argv, XtAppContext *app_con, Widget *top); void setup_window(XtAppContext app_con, Widget top, C_Options_t *o, X_Options_t *xo); static void position_near(Widget shell, int x, int y); /* Assumes shell widget has already been realized */ static void position_near_mouse(Widget shell); static void position_screen_center(Widget shell); static void timeout_action(XtPointer client_data, XtIntervalId *iid); /* Called on timeout. */ X_Options_t *get_X_options(Widget top); C_Options_t *get_C_options(int argc, char **argv); char *get_message_from_file(char *fname); Button_Data_t *find_button(char *name, Button_Data_t *blist); Button_Data_t *parse_button_specs(char *optarg, bool_t print_code); /* Parses option argument {optarg} of form "yes:11,no:12, ...", returns list of button records. If the argument is an empty string, returns an empty list. The {print_code} flag is saved in each record. */ void print_code_and_exit(Button_Data_t *b); Widget make_queryform(Widget top, C_Options_t *co, X_Options_t *xo); /* GLOBAL VARIABLES */ /* Program options: */ static C_Options_t *co = NULL; /* From command line; */ static X_Options_t *xo = NULL; /* From command line; */ int main (int argc, char **argv) { /* Create root widget {top} and initialize the application context {app-con}: */ XtAppContext app_con; /* Application's context */ Widget top; /* Root widget. */ init_application(&argc, argv, &app_con, &top); /* Get arguments through X resources: */ xo = get_X_options(top); /* Get command-line arguments: */ co = get_C_options(argc, argv); if (co->message_file != NULL) { co->message = get_message_from_file(co->message_file); } /* Build query form and open it up on the screen: */ setup_window(app_con, top, co, xo); XtAppMainLoop(app_con); return 0; } /* X WINDOW GRUBBING */ /* Fallback X resources if resource file cannot be opened: */ static String fb_res[] = { "*baseTranslations: #override :Return: return-key()", "*message.Scroll: whenNeeded", NULL }; void init_application(int *argc, char **argv, XtAppContext *app_con, Widget *top) { (*top) = XtAppInitialize(app_con, PROG_CLASS, NULL, 0, argc, argv, fb_res, NULL, 0); } static void exit_action(Widget w, XEvent *event, String *params, Cardinal *num_params); /* Implements the button click action; perameter is the exit status. */ static void enter_key_action(Widget w, XEvent *event, String *params, Cardinal *num_params); /* Implements the RETURN key action. */ static XtActionsRec actions[] = { {"exit", exit_action}, {"return-key", enter_key_action}, {NULL, NULL}, }; static String top_trans = "WM_PROTOCOLS: exit(1)\n"; /* Used by {exit_action}: */ static Atom wm_delete_window; void setup_window(XtAppContext app_con, Widget top, C_Options_t *co, X_Options_t *xo) { XtAppAddActions(app_con, actions, XtNumber(actions)); XtOverrideTranslations(top, XtParseTranslationTable(top_trans)); Widget queryform = make_queryform(top, co, xo); if (queryform == NULL) { fprintf( stderr, "%s: unable to create query form\n", PROG_NAME); abort(); } XtSetMappedWhenManaged(top, FALSE); XtRealizeWidget(top); /* do WM_DELETE_WINDOW before map */ wm_delete_window = XInternAtom(XtDisplay(top), "WM_DELETE_WINDOW", False); XSetWMProtocols(XtDisplay(top), XtWindow(top), &wm_delete_window, 1); if (co->screen_center) { position_screen_center(top); } else if (co->near_mouse) { position_near_mouse(top); } XtMapWidget(top); if (co->timeout >= 0) { long int to = (long int)(1000*co->timeout); XtAppAddTimeOut(app_con, to, timeout_action, NULL); } } static void handle_button (Widget w, XtPointer closure, XtPointer client_data) { Button_Data_t *br = (Button_Data_t *)closure; if (br->print_code) { puts(br->name); } exit (br->code); } static void exit_action(Widget w, XEvent *event, String *params, Cardinal *num_params) { int code = 0; if((event->type == ClientMessage) && (event->xclient.data.l[0] != wm_delete_window)) { return; } if (*num_params == 1) { code = atoi(params[0]); } Button_Data_t *btn = co->buttons; while ((btn != NULL) && (btn->code != code)) { btn = btn->next; } print_code_and_exit(btn); } static void enter_key_action(Widget w, XEvent *event, String *params, Cardinal *num_params) { if (co->on_enter_key_btn >= 0) { print_code_and_exit(co->on_enter_key_btn); } } static void destroy_action(Widget w, XEvent *event, String *params, Cardinal *num_params) { print_code_and_exit(co->on_destroy_btn); } static void timeout_action(XtPointer client_data, XtIntervalId *iid) { print_code_and_exit(co->on_timeout_btn); } /* COMMAND-LINE OPTION PARSING */ static struct option long_options[] = { (struct option){ "message", required_argument, 0, 'm' }, (struct option){ "message-file", required_argument, 0, 'f' }, (struct option){ "title", required_argument, 0, 't' }, (struct option){ "buttons", required_argument, 0, 'b' }, (struct option){ "timeout", required_argument, 0, 'w' }, (struct option){ "on-return-key", required_argument, 0, 'R' }, (struct option){ "on-destroy", required_argument, 0, 'D' }, (struct option){ "on-timeout", required_argument, 0, 'T' }, (struct option){ "print-name", no_argument, 0, 'p' }, (struct option){ "print-code", no_argument, 0, 'o' }, (struct option){ "screen-center", no_argument, 0, 'c' }, (struct option){ "near-mouse", no_argument, 0, 'n' }, (struct option){ "grab-focus", no_argument, 0, 'g' }, (struct option){ "version", no_argument, 0, 'v' }, (struct option){ "help", no_argument, 0, 'h' }, (struct option){ "info", no_argument, 0, 'i' }, (struct option){ NULL, 0, 0, 0 } }; C_Options_t *get_C_options(int argc, char **argv) { C_Options_t *co = (C_Options_t *)malloc(sizeof(C_Options_t)); /* Initial default values: */ co->message_file = NULL; co->message = NULL; co->title = NULL; co->timeout = -INF; co->screen_center = FALSE; co->near_mouse = FALSE; co->grab_focus = FALSE; /* Will be stored into each button: */ bool_t print_code = FALSE; /* Specified option, or NULL if unspecified: */ char *buttons = NULL; char *on_return = NULL; char *on_destroy = NULL; char *on_timeout = NULL; /* Parse arguments. */ while (1) { int option_index = 0; int c = getopt_long (argc, argv, "m:f:t:b:w:RDTpcngvh", long_options, &option_index); if (c == -1) { break; } switch (c) { case 'm': if (co->message != NULL) { free(co->message); } co->message = strdup(optarg); break; case 'f': if (co->message_file != NULL) { free(co->message_file); } co->message_file = strdup(optarg); break; case 't': if (co->title != NULL) { free(co->title); } co->title = strdup(optarg); break; case 'b': if (buttons != NULL) { free(buttons); } buttons = strdup(optarg); break; case 'w': { char *end = NULL; co->timeout = strtod(optarg, &end); if (*end != '\000') { fprintf(stderr, "bad timeout value"); abort(); } } break; case 'R': if (on_return != NULL) { free(on_return); } on_return = strdup(optarg); break; case 'D': if (on_destroy != NULL) { free(on_destroy); } on_destroy = strdup(optarg); break; case 'T': if (on_timeout != NULL) { free(on_destroy); } on_timeout = strdup(optarg); break; case 'p': print_code = TRUE; break; case 'c': co->screen_center = TRUE; co->near_mouse = FALSE; break; case 'n': co->near_mouse = TRUE; co->screen_center = FALSE; break; case 'g': co->grab_focus = TRUE; break; case 'v': fprintf(stderr, "%s version %s\n", PROG_NAME, PROG_VERS); exit (0); break; case 'h': fprintf(stderr, "%s\n", PROG_HELP); exit (0); break; case 'i': fprintf(stderr, "%s\n", PROG_INFO); exit (0); break; default: fprintf (stderr, "invalid option \"%c\"", c); if (optarg) { printf (" with arg %s", optarg); } printf ("\n"); abort(); } } if (optind < argc) { fprintf (stderr, "excess argument \"%s\"\n", argv[optind]); abort(); } /* Now let's see what we have got about buttons: */ if (buttons == NULL) { buttons = strdup("OK:0"); } co->buttons = parse_button_specs(buttons, print_code); /* Set default button for RETURN key: */ if (on_return == NULL) { /* No default: */ co->on_enter_key_btn = NULL; } else { co->on_enter_key_btn = find_button(on_return, co->buttons); } /* Set default button for timeout key: */ if (on_timeout == NULL) { /* Use first button: */ co->on_timeout_btn = co->buttons; } else { co->on_timeout_btn = find_button(on_timeout, co->buttons); } /* Set default button for window-kill: */ if (on_timeout == NULL) { /* Use last button: */ Button_Data_t *b = co->buttons; if (b != NULL) { while(b->next != NULL) { b = b->next; } } co->on_destroy_btn = b; } else { co->on_destroy_btn = find_button(on_destroy, co->buttons); } /* Consistency checks: */ if ((co->message_file != NULL) && (co->message != NULL)) { fprintf(stderr, "cannot have both \"-message\" and \"--message-file\""); abort(); } return co; } Button_Data_t *parse_button_specs(char *optarg, bool_t print_code) { char *bspecs = strdup(optarg); /* Figure out how many matches we will find so that we can preallocate space for button structures. If you add stripping of white space, make sure that you update this as well as the walking algorithm below. */ Button_Data_t *b = NULL; /* List of button records. */ /* Count button specs, terminate each by '\000': */ int nb = 0; char *bp = bspecs; while ((bp != NULL) && ((*bp) != '\000')) { /* One more button spec: */ nb++; /* Allocate button record, prepend to {b}: */ { Button_Data_t *t = (Button_Data_t *)notnull(malloc(sizeof(Button_Data_t)), "no mem"); t->next = b; b = t; } /* Save beginning of next button spec: */ char *beg = bp; /* Scan next button spec, removing backslashes; note ':' in {mid}: */ char *mid = NULL, *end = beg, *nxt = beg; while (((*nxt) != '\000') && ((*nxt) != ',')) { /* Copy next character from {*nxt} to {*end}, honoring escapes: */ if ((*nxt) == '\\') { nxt++; if ((*nxt) == '\000') { break; } } else { if ((*nxt) == ':') { mid = end; } } (*end) = (*nxt); end++; nxt++; } /* Terminate button spec: */ (*end) = '\000'; /* Prepare {bp} for next iteration: */ bp = nxt+1; /* Get button code: */ if (mid == NULL) { /* Use default code: */ b->code = nb + 101; } else { /* Separate button name from code: */ *mid = '\000'; /* Parse user-specified code: */ char *rest = NULL; long int val = strtol(mid+1, &rest, 10); if ((rest != NULL) || (val < 0) || (val > 255)) { fprintf(stderr, "invalid button code \"%s\"\n", mid+1); abort(); } b->code = (int)val; } /* Save name: */ if (strlen(beg) == 0) { fprintf(stderr, "invalid button name \"%s\"\n", beg); abort(); } b->name = strdup(beg); /* Set printing flag: */ b->print_code = print_code; } free(bspecs); /* Reverse the list: */ { Button_Data_t *r = NULL; while (b != NULL) { Button_Data_t *t = b->next; b->next = r; r = b; b = t; } b = r; } return b; } Button_Data_t *find_button(char *name, Button_Data_t *blist) { Button_Data_t *b = blist; while(b != NULL) { if (strcmp(name, b->name) == 0) { return b; } } fprintf(stderr, "no button named \"%s\"\n", name); abort(); } /* X RESOURCE OPTION PARSING */ #define OFF(field) XtOffsetOf(struct X_Options_t, field) static XtResource resources[] = { { "maxHeight", "Maximum", XtRDimension, sizeof(Dimension), OFF(maxHeight), XtRDimension, 0 }, { "maxWidth", "Maximum", XtRDimension, sizeof(Dimension), OFF(maxWidth), XtRDimension, 0 }, }; #undef OFF X_Options_t *get_X_options(Widget top) { X_Options_t *xo = (X_Options_t *)notnull(malloc(sizeof(X_Options_t)), "no mem"); XtGetApplicationResources(top, (XtPointer)xo, resources, XtNumber(resources), NULL, 0); return xo; } /* * usage */ static void usage (FILE *outf) { static char *options[] = { NULL }; char **cpp; fprintf (outf, "usage: %s [-options] [message ...]\n\n", PROG_NAME); fprintf (outf, "where options include:\n"); for (cpp = options; *cpp; cpp++) { fprintf (outf, "%s\n", *cpp); } static char*id="$XConsortium: xmessage.c /main/7 1996/05/16 15:08:38 gildea $"; fprintf (outf, "%s\n", id+1); } static void position_near(Widget shell, int x, int y) { int max_x, max_y; Dimension width, height, border; int gravity; /* some of this is copied from CenterWidgetOnPoint in Xaw/TextPop.c */ XtVaGetValues(shell, XtNwidth, &width, XtNheight, &height, XtNborderWidth, &border, NULL); width = (Dimension)(width + 2 * border); height = (Dimension)(height + 2 * border); max_x = WidthOfScreen(XtScreen(shell)); max_y = HeightOfScreen(XtScreen(shell)); /* set gravity hint based on position on screen */ gravity = 1; if (x > max_x/3) gravity += 1; if (x > max_x*2/3) gravity += 1; if (y > max_y/3) gravity += 3; if (y > max_y*2/3) gravity += 3; max_x -= width; max_y -= height; x -= ( (Position) width/2 ); if (x < 0) x = 0; if (x > max_x) x = max_x; y -= ( (Position) height/2 ); if (y < 0) y = 0; if ( y > max_y ) y = max_y; XtVaSetValues(shell, XtNx, (Position)x, XtNy, (Position)y, XtNwinGravity, gravity, NULL); } static void position_near_mouse(Widget shell) { int x, y; Window root, child; int winx, winy; unsigned int mask; XQueryPointer(XtDisplay(shell), XtWindow(shell), &root, &child, &x, &y, &winx, &winy, &mask); position_near(shell, x, y); } static void position_screen_center(Widget shell) { int x = WidthOfScreen(XtScreen(shell))/2; int y = HeightOfScreen(XtScreen(shell))/2; position_near(shell, x, y); } #define MAX_MESSAGE_LENGTH 100000 #define TRUNC_FLAG "[TRUNCATED]" char *get_message_from_file(char *filename) { /* Alloc space for MAX_MESSAGE_LENGTH bytes plus TRUNC_FLAG and final '\000': */ int maxlen = MAX_MESSAGE_LENGTH + strlen(TRUNC_FLAG); char *msg = (char *)notnull(malloc(maxlen + 1), "no mem"); /* Open file: */ FILE *fp = fopen (filename, "r"); if (!fp) { perror(filename); free (msg); return NULL; } char *cp = msg; long unsigned int len = 0, spc = MAX_MESSAGE_LENGTH+1; long unsigned int nread; while ((spc > 0) && ((nread = fread(cp, 1, spc, fp)) > 0)) { if ((nread == 0) && (spc > 0)) { perror(filename); free(msg); msg = NULL; break; } len = len + nread; spc -= nread; } fclose (fp); if (msg == NULL) { return NULL; } /* Truncate message if too long: */ if (len > MAX_MESSAGE_LENGTH) { cp = msg + MAX_MESSAGE_LENGTH; strcpy(cp, TRUNC_FLAG); len = maxlen; } /* Make sure that there is a terminating zero: */ if (len < maxlen) { msg = (char *)notnull(realloc(msg, len+1), "no mem"); } msg[len] = '\000'; return msg; } static void unquote_pairs (Button_Data_t *br, int n) { int i; for (i = 0; i < n; i++) { register char *dst, *src; register int quoted = 0; for (src = dst = br->name; *src; src++) { if (quoted) { *dst++ = *src; quoted = 0; } else if (src[0] == '\\') { quoted = 1; } else { *dst++ = *src; } } *dst = '\0'; } return; } Widget make_queryform(Widget top, C_Options_t *co, X_Options_t *xo) { Button_Data_t *br; int nb, i; Widget form, text, prev; Arg args[10]; Cardinal n, thisn; char *shell_geom; int x, y, geom_flags; unsigned int shell_w, shell_h; Button_Data_t *b = co->buttons; form = XtCreateManagedWidget ("form", coreWidgetClass, top, NULL, 0); text = XtVaCreateManagedWidget ("message", textWidgetClass, form, XtNleft, XtChainLeft, XtNright, XtChainRight, XtNtop, XtChainTop, XtNbottom, XtChainBottom, XtNdisplayCaret, False, XtNuseStringInPlace, True, XtNlength, msglen, XtNstring, msgstr, NULL); /* * Did the user specify our geometry? * If so, don't bother computing it ourselves, since we will be overridden. */ XtVaGetValues(top, XtNgeometry, &shell_geom, NULL); geom_flags = XParseGeometry(shell_geom, &x, &y, &shell_w, &shell_h); if (!(geom_flags & WidthValue && geom_flags & HeightValue)) { Dimension width, height, height_addons = 0; Dimension scroll_size, border_width; Widget label, scroll; Position left, right, top, bottom; /* * A Text widget is used for the automatic scroll bars. * But Text widget doesn't automatically compute its size. * The Label widget does that nicely, so we create one and examine it. * This widget is never visible. */ XtVaGetValues(text, XtNtopMargin, &top, XtNbottomMargin, &bottom, XtNleftMargin, &left, XtNrightMargin, &right, NULL); label = XtVaCreateWidget("message", labelWidgetClass, form, XtNlabel, msgstr, XtNinternalWidth, (left+right+1)/2, XtNinternalHeight, (top+bottom+1)/2, NULL); XtVaGetValues(label, XtNwidth, &width, XtNheight, &height, NULL); XtDestroyWidget(label); if (max_width == 0) max_width = .7 * WidthOfScreen(XtScreen(text)); if (max_height == 0) max_height = .7 * HeightOfScreen(XtScreen(text)); if (width > max_width) { width = max_width; /* add in the height of any horizontal scroll bar */ scroll = XtVaCreateWidget("hScrollbar", scrollbarWidgetClass, text, XtNorientation, XtorientHorizontal, NULL); XtVaGetValues(scroll, XtNheight, &scroll_size, XtNborderWidth, &border_width, NULL); XtDestroyWidget(scroll); height_addons = scroll_size + border_width; } if (height > max_height) { height = max_height; /* add in the width of any vertical scroll bar */ scroll = XtVaCreateWidget("vScrollbar", scrollbarWidgetClass, text, XtNorientation, XtorientVertical, NULL); XtVaGetValues(scroll, XtNwidth, &scroll_size, XtNborderWidth, &border_width, NULL); XtDestroyWidget(scroll); width += scroll_size + border_width; } height += height_addons; XtVaSetValues(text, XtNwidth, width, XtNheight, height, NULL); } /* * Create the buttons */ n = 0; XtSetArg (args[n], XtNleft, XtChainLeft); n++; XtSetArg (args[n], XtNright, XtChainLeft); n++; XtSetArg (args[n], XtNtop, XtChainBottom); n++; XtSetArg (args[n], XtNbottom, XtChainBottom); n++; XtSetArg (args[n], XtNfromVert, text); n++; XtSetArg (args[n], XtNvertDistance, 5); n++; prev = NULL; for (i = 0; i < nb; i++) { thisn = n; XtSetArg (args[thisn], XtNfromHoriz, prev); thisn++; prev = XtCreateManagedWidget (br[i].name, commandWidgetClass, form, args, thisn); br[i].widget = prev; br[i].print_code = print; XtAddCallback (prev, XtNcallback, handle_button, (XtPointer) &br[i]); if (default_button && !strcmp(default_button, br[i].name)) { Dimension border; default_code = br[i].code; XtVaGetValues(br[i].widget, XtNborderWidth, &border, NULL); border *= 2; XtVaSetValues(br[i].widget, XtNborderWidth, border, NULL); } } return form; } /* Copyright (c) 1988, 1991, 1994 X Consortium Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Except as contained in this notice, the name of the X Consortium shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from the X Consortium. */