Made all calls to XGetAtomName() free their memory later on.

Removed function osTypeToX(). Extracted method
[getSelectionData:type:] from [xSelectionNotify:].
[xSelectionNotify:] now supports incremental data transfer and is
save against failing string conversions. Complete rewrite of
[xProvideSelection:] to better follow the ICCCM
specification. This now implements TIMESTAMP, MULTIPLE and
COMPOUND_TEXT (all untested!)


git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/back/trunk@17606 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Fred Kiefer 2003-09-02 22:17:40 +00:00
parent e45bf12265
commit a8d30770bb

View file

@ -31,8 +31,6 @@
#include <X11/Xutil.h> #include <X11/Xutil.h>
#include <x11/xdnd.h> #include <x11/xdnd.h>
static Atom osTypeToX(NSString *t);
/* /*
* Non-predefined atoms that are used in the X selection mechanism * Non-predefined atoms that are used in the X selection mechanism
*/ */
@ -53,7 +51,10 @@ static char* atom_names[] = {
"NULL", "NULL",
"FILE_NAME", "FILE_NAME",
"CLIPBOARD", "CLIPBOARD",
"UTF8_STRING" "UTF8_STRING",
"MULTIPLE",
"COMPOUND_TEXT",
"INCR"
}; };
static Atom atoms[sizeof(atom_names)/sizeof(char*)]; static Atom atoms[sizeof(atom_names)/sizeof(char*)];
@ -78,45 +79,9 @@ static Atom atoms[sizeof(atom_names)/sizeof(char*)];
#define XG_FILE_NAME atoms[14] #define XG_FILE_NAME atoms[14]
#define XA_CLIPBOARD atoms[15] #define XA_CLIPBOARD atoms[15]
#define XG_UTF8_STRING atoms[16] #define XG_UTF8_STRING atoms[16]
#define XG_MULTIPLE atoms[17]
#define XG_COMPOUND_TEXT atoms[18]
#define XG_INCR atoms[19]
static Atom
osTypeToX(NSString *t)
{
if ([t isEqualToString: NSStringPboardType] == YES)
#ifdef X_HAVE_UTF8_STRING
return XG_UTF8_STRING;
#else
return XA_STRING;
#endif
else if ([t isEqualToString: NSColorPboardType] == YES)
return XG_NULL;
else if ([t isEqualToString: NSFileContentsPboardType] == YES)
return XG_NULL;
else if ([t isEqualToString: NSFilenamesPboardType] == YES)
return XG_FILE_NAME;
else if ([t isEqualToString: NSFontPboardType] == YES)
return XG_NULL;
else if ([t isEqualToString: NSRulerPboardType] == YES)
return XG_NULL;
else if ([t isEqualToString: NSPostScriptPboardType] == YES)
return XG_NULL;
else if ([t isEqualToString: NSTabularTextPboardType] == YES)
return XG_NULL;
else if ([t isEqualToString: NSRTFPboardType] == YES)
return XG_NULL;
else if ([t isEqualToString: NSRTFDPboardType] == YES)
return XG_NULL;
else if ([t isEqualToString: NSTIFFPboardType] == YES)
return XG_NULL;
else if ([t isEqualToString: NSDataLinkPboardType] == YES)
return XG_NULL;
else if ([t isEqualToString: NSGeneralPboardType] == YES)
return XG_NULL;
else
return XG_NULL;
}
@ -128,6 +93,7 @@ osTypeToX(NSString *t)
Atom _xPb; Atom _xPb;
Time _waitingForSelection; Time _waitingForSelection;
Time _timeOfLastAppend; Time _timeOfLastAppend;
Time _timeOfSetSelectionOwner;
BOOL _ownedByOpenStep; BOOL _ownedByOpenStep;
} }
@ -162,7 +128,7 @@ osTypeToX(NSString *t)
- (void) xSelectionNotify: (XSelectionEvent*)xEvent; - (void) xSelectionNotify: (XSelectionEvent*)xEvent;
- (void) xSelectionRequest: (XSelectionRequestEvent*)xEvent; - (void) xSelectionRequest: (XSelectionRequestEvent*)xEvent;
- (BOOL) xProvideSelection: (XSelectionRequestEvent*)xEvent; - (BOOL) xProvideSelection: (XSelectionRequestEvent*)xEvent;
- (Time) xTimeByAppending: (Atom) defaultType; - (Time) xTimeByAppending;
- (BOOL) xSendData: (unsigned char*) data format: (int) format - (BOOL) xSendData: (unsigned char*) data format: (int) format
items: (int) numItems type: (Atom) xType items: (int) numItems type: (Atom) xType
to: (Window) window property: (Atom) property; to: (Window) window property: (Atom) property;
@ -244,7 +210,6 @@ static NSString *xWaitMode = @"XPasteboardWaitMode";
forMode: xWaitMode]; forMode: xWaitMode];
XSelectInput(xDisplay, xAppWin, PropertyChangeMask); XSelectInput(xDisplay, xAppWin, PropertyChangeMask);
XFlush(xDisplay); XFlush(xDisplay);
} }
@ -367,8 +332,11 @@ static NSString *xWaitMode = @"XPasteboardWaitMode";
o = [self ownerByXPb: xEvent->selection]; o = [self ownerByXPb: xEvent->selection];
if (o == nil) if (o == nil)
{ {
char *name = XGetAtomName(xDisplay, xEvent->selection);
NSDebugLLog(@"Pbs", @"Selection clear for unknown selection - '%s'.", NSDebugLLog(@"Pbs", @"Selection clear for unknown selection - '%s'.",
XGetAtomName(xDisplay, xEvent->selection)); name);
XFree(name);
return; return;
} }
@ -388,8 +356,10 @@ static NSString *xWaitMode = @"XPasteboardWaitMode";
o = [self ownerByXPb: xEvent->atom]; o = [self ownerByXPb: xEvent->atom];
if (o == nil) if (o == nil)
{ {
char *name = XGetAtomName(xDisplay, xEvent->atom);
NSDebugLLog(@"Pbs", @"Property notify for unknown property - '%s'.", NSDebugLLog(@"Pbs", @"Property notify for unknown property - '%s'.",
XGetAtomName(xDisplay, xEvent->atom)); name);
XFree(name);
return; return;
} }
@ -412,8 +382,10 @@ static NSString *xWaitMode = @"XPasteboardWaitMode";
o = [self ownerByXPb: xEvent->selection]; o = [self ownerByXPb: xEvent->selection];
if (o == nil) if (o == nil)
{ {
char *name = XGetAtomName(xDisplay, xEvent->selection);
NSDebugLLog(@"Pbs", @"Selection notify for unknown selection - '%s'.", NSDebugLLog(@"Pbs", @"Selection notify for unknown selection - '%s'.",
XGetAtomName(xDisplay, xEvent->selection)); name);
XFree(name);
return; return;
} }
@ -424,9 +396,13 @@ static NSString *xWaitMode = @"XPasteboardWaitMode";
} }
else else
{ {
NSDebugLLog(@"Pbs", @"Selection (%s) notify - '%s'.", char *sel_name = XGetAtomName(xDisplay, xEvent->selection);
XGetAtomName(xDisplay, xEvent->selection), char *pro_name = XGetAtomName(xDisplay, xEvent->property);
XGetAtomName(xDisplay, xEvent->property));
NSDebugLLog(@"Pbs", @"Selection (%s) notify - '%s'.", sel_name,
pro_name);
XFree(sel_name);
XFree(pro_name);
} }
[o xSelectionNotify: xEvent]; [o xSelectionNotify: xEvent];
@ -439,8 +415,10 @@ static NSString *xWaitMode = @"XPasteboardWaitMode";
o = [self ownerByXPb: xEvent->selection]; o = [self ownerByXPb: xEvent->selection];
if (o == nil) if (o == nil)
{ {
char *name = XGetAtomName(xDisplay, xEvent->selection);
NSDebugLLog(@"Pbs", @"Selection request for unknown selection - '%s'.", NSDebugLLog(@"Pbs", @"Selection request for unknown selection - '%s'.",
XGetAtomName(xDisplay, xEvent->selection)); name);
XFree(name);
return; return;
} }
@ -508,11 +486,9 @@ static NSString *xWaitMode = @"XPasteboardWaitMode";
* so we must tell the X server that we have the current selection. * so we must tell the X server that we have the current selection.
* To conform to ICCCM we need to specify an up-to-date timestamp. * To conform to ICCCM we need to specify an up-to-date timestamp.
*/ */
Atom defaultType = XG_UTF8_STRING; _timeOfSetSelectionOwner = [self xTimeByAppending];
//Atom defaultType = XA_STRING; XSetSelectionOwner(xDisplay, _xPb, xAppWin, _timeOfSetSelectionOwner);
XSetSelectionOwner(xDisplay, _xPb, xAppWin,
[self xTimeByAppending: defaultType]);
w = XGetSelectionOwner(xDisplay, _xPb); w = XGetSelectionOwner(xDisplay, _xPb);
if (w != xAppWin) if (w != xAppWin)
{ {
@ -532,7 +508,7 @@ static NSString *xWaitMode = @"XPasteboardWaitMode";
* Do a nul append to a property to get a timestamp, if it returns the * Do a nul append to a property to get a timestamp, if it returns the
* 'CurrentTime' constant then we haven't been able to get one. * 'CurrentTime' constant then we haven't been able to get one.
*/ */
whenRequested = [self xTimeByAppending: xType]; whenRequested = [self xTimeByAppending];
if (whenRequested != CurrentTime) if (whenRequested != CurrentTime)
{ {
NSDate *limit; NSDate *limit;
@ -562,9 +538,11 @@ static NSString *xWaitMode = @"XPasteboardWaitMode";
} }
if ([self waitingForSelection] != 0) if ([self waitingForSelection] != 0)
{ {
char *name = XGetAtomName(xDisplay, xType);
[self setWaitingForSelection: 0]; [self setWaitingForSelection: 0];
NSLog(@"Timed out waiting for X selection '%s'", NSLog(@"Timed out waiting for X selection '%s'", name);
XGetAtomName(xDisplay, xType)); XFree(name);
} }
} }
} }
@ -648,33 +626,20 @@ xErrorHandler(Display *d, XErrorEvent *e)
[self setOwnedByOpenStep: NO]; [self setOwnedByOpenStep: NO];
} }
- (void) xSelectionNotify: (XSelectionEvent*)xEvent - (NSMutableData*) getSelectionData: (XSelectionEvent*)xEvent
type: (Atom*)type
{ {
int status; int status;
unsigned char *data; unsigned char *data;
long long_offset = 0L; long long_offset = 0L;
long long_length = FULL_LENGTH; long long_length = FULL_LENGTH;
Atom req_type = AnyPropertyType; Atom req_type = AnyPropertyType;
Atom actual_type; Atom actual_type;
int actual_format; int actual_format;
unsigned long bytes_remaining; unsigned long bytes_remaining;
unsigned long number_items; unsigned long number_items;
NSMutableData *md = nil; NSMutableData *md = nil;
if (xEvent->property == (Atom)None)
{
NSDebugLLog(@"Pbs", @"Owning program failed to convert data.");
[self setWaitingForSelection: 0];
return;
}
if ([self waitingForSelection] > xEvent->time)
{
NSLog(@"Unexpected selection notify - time %u.", xEvent->time);
return;
}
[self setWaitingForSelection: 0];
/* /*
* Read data from property identified in SelectionNotify event. * Read data from property identified in SelectionNotify event.
*/ */
@ -707,9 +672,13 @@ xErrorHandler(Display *d, XErrorEvent *e)
{ {
if (req_type != actual_type) if (req_type != actual_type)
{ {
char *req_name = XGetAtomName(xDisplay, req_type);
char *act_name = XGetAtomName(xDisplay, actual_type);
NSLog(@"Selection changed type from %s to %s.", NSLog(@"Selection changed type from %s to %s.",
XGetAtomName(xDisplay, req_type), req_name, act_name);
XGetAtomName(xDisplay, actual_type)); XFree(req_name);
XFree(act_name);
RELEASE(md); RELEASE(md);
return; return;
} }
@ -726,6 +695,77 @@ xErrorHandler(Display *d, XErrorEvent *e)
while ((status == Success) && (bytes_remaining > 0)); while ((status == Success) && (bytes_remaining > 0));
if (status == Success) if (status == Success)
{
*type = actual_type;
return AUTORELEASE(md);
}
else
{
RELEASE(md);
return nil;
}
}
- (void) xSelectionNotify: (XSelectionEvent*)xEvent
{
Atom actual_type;
NSMutableData *md = nil;
if (xEvent->property == (Atom)None)
{
NSDebugLLog(@"Pbs", @"Owning program failed to convert data.");
[self setWaitingForSelection: 0];
return;
}
if ([self waitingForSelection] > xEvent->time)
{
NSDebugLLog(@"Pbs", @"Unexpected selection notify - time %u.", xEvent->time);
return;
}
[self setWaitingForSelection: 0];
md = [self getSelectionData: xEvent type: &actual_type];
if (md != nil)
{
if (actual_type == XG_INCR)
{
XEvent event;
NSMutableData *imd = nil;
BOOL wait = YES;
md = nil;
while (wait)
{
XNextEvent(xDisplay, &event);
if (event.type == PropertyNotify)
{
if (event.xproperty.state != PropertyNewValue) continue;
imd = [self getSelectionData: xEvent type: &actual_type];
if (imd != nil)
{
if (md == nil)
{
md = imd;
}
else
{
[md appendData: imd];
}
}
else
{
wait = NO;
}
}
}
}
}
if (md != nil)
{ {
// Convert data to text string. // Convert data to text string.
if (actual_type == XG_UTF8_STRING) if (actual_type == XG_UTF8_STRING)
@ -735,9 +775,12 @@ xErrorHandler(Display *d, XErrorEvent *e)
s = [[NSString alloc] initWithData: md s = [[NSString alloc] initWithData: md
encoding: NSUTF8StringEncoding]; encoding: NSUTF8StringEncoding];
d = [NSSerializer serializePropertyList: s]; if (s != nil)
RELEASE(s); {
[self setData: d]; d = [NSSerializer serializePropertyList: s];
RELEASE(s);
[self setData: d];
}
} }
else if (actual_type == XA_STRING) else if (actual_type == XA_STRING)
{ {
@ -746,18 +789,21 @@ xErrorHandler(Display *d, XErrorEvent *e)
s = [[NSString alloc] initWithData: md s = [[NSString alloc] initWithData: md
encoding: NSISOLatin1StringEncoding]; encoding: NSISOLatin1StringEncoding];
d = [NSSerializer serializePropertyList: s]; if (s != nil)
RELEASE(s); {
[self setData: d]; d = [NSSerializer serializePropertyList: s];
RELEASE(s);
[self setData: d];
}
} }
else else
{ {
NSLog(@"Unsupported data type '%s' from X selection.", char *name = XGetAtomName(xDisplay, actual_type);
XGetAtomName(xDisplay, actual_type));
NSLog(@"Unsupported data type '%s' from X selection.", name);
XFree(name);
} }
} }
RELEASE(md);
} }
- (void) xSelectionRequest: (XSelectionRequestEvent*)xEvent - (void) xSelectionRequest: (XSelectionRequestEvent*)xEvent
@ -777,7 +823,15 @@ xErrorHandler(Display *d, XErrorEvent *e)
notify_event.selection = xEvent->selection; notify_event.selection = xEvent->selection;
notify_event.target = xEvent->target; notify_event.target = xEvent->target;
notify_event.time = xEvent->time; notify_event.time = xEvent->time;
notify_event.property = xEvent->property; notify_event.send_event = True;
if (xEvent->property == None)
{
notify_event.property = xEvent->target;
}
else
{
notify_event.property = xEvent->property;
}
/* /*
* If for any reason we cannot provide the data to the requestor, we must * If for any reason we cannot provide the data to the requestor, we must
@ -805,138 +859,177 @@ xErrorHandler(Display *d, XErrorEvent *e)
if (xEvent->target == XG_TARGETS) if (xEvent->target == XG_TARGETS)
{ {
unsigned numTypes = 0; unsigned numTypes = 0;
Atom xTypes[numOsTypes]; Atom xTypes[10];
/* /*
* The requestor wants a list of the types we can supply it with. * The requestor wants a list of the types we can supply it with.
* We can supply one or more types of data to the requestor so
* we will give it a list of the types supported.
*/ */
for (i = 0; i < numOsTypes; i++) xTypes[numTypes++] = XG_TARGETS;
{ xTypes[numTypes++] = XG_TIMESTAMP;
NSString *type = [types objectAtIndex: i]; xTypes[numTypes++] = XG_MULTIPLE;
Atom t; // FIXME: ICCCM requires even more types from us.
t = osTypeToX(type); if ([types containsObject: NSStringPboardType])
if (t != XG_NULL) {
{ xTypes[numTypes++] = XG_UTF8_STRING;
unsigned j; xTypes[numTypes++] = XG_COMPOUND_TEXT;
xTypes[numTypes++] = XA_STRING;
xTypes[numTypes++] = XG_TEXT;
}
for (j = 0; j < numTypes; j++) if ([types containsObject: NSFilenamesPboardType])
{ {
if (xTypes[j] == t) xTypes[numTypes++] = XG_FILE_NAME;
break;
}
if (j == numTypes)
{
xTypes[numTypes++] = t;
}
}
} }
if (numTypes > 0)
{ xType = XA_ATOM;
/* format = 32;
* We can supply one or more types of data to the requestor so numItems = numTypes;
* we will give it a list of the types supported. data = malloc(numTypes*sizeof(Atom));
*/ memcpy(data, xTypes, numTypes*sizeof(Atom));
xType = XA_ATOM; }
data = malloc(numTypes*sizeof(Atom)); else if (xEvent->target == XG_TIMESTAMP)
memcpy(data, xTypes, numTypes*sizeof(Atom)); {
numItems = numTypes; xType = XA_INTEGER;
format = 32; format = 32;
numItems = 1;
data = malloc(sizeof(int));
memcpy(data, &_timeOfSetSelectionOwner, sizeof(int));
}
else if (xEvent->target == AnyPropertyType)
{
/*
* The requestor will accept any type of data - so we use the first
* OpenStep type that corresponds to a known X type.
*/
if ([types containsObject: NSStringPboardType])
{
xEvent->target = XG_UTF8_STRING;
[self xProvideSelection: xEvent];
} }
else else if ([types containsObject: NSFilenamesPboardType])
{ {
/* xEvent->target = XG_FILE_NAME;
* No OS types that are convertable to X types. [self xProvideSelection: xEvent];
*/
xType = XG_NULL;
} }
} }
else else if (xEvent->target == XG_MULTIPLE)
{ {
if (xEvent->target == AnyPropertyType) if (xEvent->property != None)
{ {
/* Atom *multipleAtoms= NULL;
* The requestor will accept any type of data - so we use the first int actual_format;
* OpenStep type that corresponds to a known X type. Atom actual_type;
*/ unsigned long number_items, bytes_remaining;
for (i = 0; i < numOsTypes; i++) int status;
{
NSString *type = [types objectAtIndex: i];
Atom t = osTypeToX(type);
if (t != XG_NULL) status = XGetWindowProperty(xDisplay,
{ xEvent->requestor,
osType = type; xEvent->property,
xType = t; 0,
break; 100,
} False,
AnyPropertyType,
&actual_type,
&actual_format,
&number_items,
&bytes_remaining,
(unsigned char **)&multipleAtoms);
if ((status == Success) && (bytes_remaining == 0) &&
(actual_format == 32) && (actual_type == XA_ATOM))
{
int i;
XSelectionRequestEvent requestEvent;
memcpy(&requestEvent, xEvent, sizeof(XSelectionRequestEvent));
for (i = 0; i < number_items; i += 2)
{
requestEvent.target= multipleAtoms[i];
requestEvent.property= multipleAtoms[i+1];
if (requestEvent.target != None)
{
// Recursive call to this method for each pair.
if (![self xProvideSelection: &requestEvent])
{
multipleAtoms[i+1]= None;
}
}
}
// FIXME: Should we call XChangeProperty to set the invalid types?
}
}
}
else if ((xEvent->target == XG_COMPOUND_TEXT) &&
[types containsObject: NSStringPboardType])
{
NSString *s = [_pb stringForType: NSStringPboardType];
const char *d;
int status;
xType = XG_COMPOUND_TEXT;
format = 8;
d = [s cString];
if (d)
{
char *list[]= {(char *)d, NULL};
XTextProperty textProperty;
status = XmbTextListToTextProperty(xEvent->display, list, 1,
XCompoundTextStyle, &textProperty);
if (status == Success)
{
numItems = textProperty.nitems;
data = malloc(numItems);
memcpy(data, textProperty.value, 1);
XFree((void *)textProperty.value);
} }
} }
else }
{ else if (((xEvent->target == XG_UTF8_STRING) ||
// A fixed type was requested. (xEvent->target == XA_STRING) ||
xType = xEvent->target; (xEvent->target == XG_TEXT)) &&
/* [types containsObject: NSStringPboardType])
* Find an available OpenStep pasteboard type that corresponds {
* to the requested X type. NSString *s = [_pb stringForType: NSStringPboardType];
*/ NSData *d;
for (i = 0; i < numOsTypes; i++)
{
NSString *type = [types objectAtIndex: i];
Atom t = osTypeToX(type);
if (t == xType) xType = xEvent->target;
{ format = 8;
osType = type;
xType = t;
break;
}
}
// If UTF8 is the default we have to handle the case where the target
// applciaiton cannot handle this.
if ((osType == nil) &&
[types containsObject: NSStringPboardType] &&
((xType == XA_STRING) || (xType == XG_TEXT)))
{
osType = NSStringPboardType;
}
}
/* /*
* Now we know what type of data is required - so get it from the * Now we know what type of data is required - so get it from the
* pasteboard and convert to a format X can understand. * pasteboard and convert to a format X can understand.
*/ */
if (osType != nil) if (xType == XG_UTF8_STRING)
{ {
if ([osType isEqualToString: NSStringPboardType]) d = [s dataUsingEncoding: NSUTF8StringEncoding];
{
NSString *s = [_pb stringForType: NSStringPboardType];
NSData *d;
if (xType == XG_UTF8_STRING)
{
d = [s dataUsingEncoding: NSUTF8StringEncoding];
}
else
{
d = [s dataUsingEncoding: NSISOLatin1StringEncoding];
}
format = 8;
if (d != nil)
{
numItems = [d length];
data = malloc(numItems + 1);
if (data)
[d getBytes: data];
}
}
else
{
NSLog(@"Trying to convert from unsupported type - '%@'", osType);
}
} }
else if ((xType == XA_STRING) || (xType == XG_TEXT))
{
d = [s dataUsingEncoding: NSISOLatin1StringEncoding];
}
if (d != nil)
{
numItems = [d length];
data = malloc(numItems + 1);
if (data)
[d getBytes: data];
}
}
else if ((xEvent->target == XG_FILE_NAME) &&
[types containsObject: NSFilenamesPboardType])
{
}
else
{
char *name = XGetAtomName(xDisplay, xEvent->target);
NSLog(@"Request for unsupported data type '%s'.", name);
XFree(name);
return NO;
} }
return [self xSendData: data format: format items: numItems type: xType return [self xSendData: data format: format items: numItems type: xType
@ -978,7 +1071,7 @@ xErrorHandler(Display *d, XErrorEvent *e)
pos += maxItems; pos += maxItems;
XSync(xDisplay, False); XSync(xDisplay, False);
} }
XFree(data); free(data);
XSetErrorHandler(oldHandler); XSetErrorHandler(oldHandler);
if (appendFailure == NO) if (appendFailure == NO)
{ {
@ -988,7 +1081,7 @@ xErrorHandler(Display *d, XErrorEvent *e)
return status; return status;
} }
- (Time) xTimeByAppending: (Atom)defaultType - (Time) xTimeByAppending
{ {
NSDate *limit; NSDate *limit;
Time whenRequested; Time whenRequested;
@ -1013,8 +1106,8 @@ xErrorHandler(Display *d, XErrorEvent *e)
* The property doesn't exist - so we will be creating a new (empty) * The property doesn't exist - so we will be creating a new (empty)
* property. * property.
*/ */
actualType = defaultType; actualType = XA_ATOM;
actualFormat = 8; actualFormat = 32;
} }
XChangeProperty(xDisplay, xAppWin, [self xPb], actualType, actualFormat, XChangeProperty(xDisplay, xAppWin, [self xPb], actualType, actualFormat,
@ -1082,6 +1175,7 @@ pasteboardTypeForMimeType(Display *xDisplay, NSZone *zone, Atom *typelist)
{ {
[newTypes addObject: [NSPasteboard pasteboardTypeForMimeType: [newTypes addObject: [NSPasteboard pasteboardTypeForMimeType:
[NSString stringWithCString: s]]]; [NSString stringWithCString: s]]];
XFree(s);
} }
} }