aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPo Lu2022-01-29 05:06:02 +0000
committerPo Lu2022-01-29 05:11:04 +0000
commit95ccd1ba47771349e23aedf0981861fd5074bd7e (patch)
tree564a39fc44516cc3f95ddc79ddc991dd25035e32 /src
parent70fc32f6ddef871dd2ae15a3333974f6d8231d6a (diff)
downloademacs-95ccd1ba47771349e23aedf0981861fd5074bd7e.tar.gz
emacs-95ccd1ba47771349e23aedf0981861fd5074bd7e.zip
Implement real menu help-echo text on Haiku
* lisp/tooltip.el (tooltip-show-help): Remove Haiku-specific conditional since that's now taken care of by C code. * src/haiku_io.c (haiku_read_size): (haiku_read_with_timeout): (haiku_write_without_signal): Add parameter `popup_p'. All callers changed. (port_popup_menu_to_emacs): New variable. * src/haiku_support.cc (struct be_popup_menu_data): New structure. (be_popup_menu_thread_entry): New function. (class EmacsMenuItem): New field `menu_ptr'. (Highlight): Send help text to the popup port if this item isn't for a menu bar. (BMenu_add_item): Set menu_ptr appropriately. (BMenu_run): Complete rewrite that allows to read help text from the menu bar port. * src/haiku_support.h (struct haiku_menu_bar_help_event): New fields for popup menus. * src/haikumenu.c (digest_menu_items): Only set help tooltip on popup menus when system tooltips are enabled. (haiku_menu_show_help): (haiku_process_pending_signals_for_menu): New functions. (haiku_menu_show): Pass new callbacks.
Diffstat (limited to 'src')
-rw-r--r--src/haiku_io.c24
-rw-r--r--src/haiku_support.cc152
-rw-r--r--src/haiku_support.h17
-rw-r--r--src/haikumenu.c47
-rw-r--r--src/haikuterm.c8
5 files changed, 216 insertions, 32 deletions
diff --git a/src/haiku_io.c b/src/haiku_io.c
index cb7750634cf..109aca782ad 100644
--- a/src/haiku_io.c
+++ b/src/haiku_io.c
@@ -36,6 +36,10 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
36 Emacs. */ 36 Emacs. */
37port_id port_application_to_emacs; 37port_id port_application_to_emacs;
38 38
39/* The port used to send popup menu messages from the application
40 thread to Emacs. */
41port_id port_popup_menu_to_emacs;
42
39void 43void
40haiku_io_init (void) 44haiku_io_init (void)
41{ 45{
@@ -98,9 +102,11 @@ haiku_len (enum haiku_event_type type)
98/* Read the size of the next message into len, returning -1 if the 102/* Read the size of the next message into len, returning -1 if the
99 query fails or there is no next message. */ 103 query fails or there is no next message. */
100void 104void
101haiku_read_size (ssize_t *len) 105haiku_read_size (ssize_t *len, bool popup_menu_p)
102{ 106{
103 port_id from = port_application_to_emacs; 107 port_id from = (popup_menu_p
108 ? port_popup_menu_to_emacs
109 : port_application_to_emacs);
104 ssize_t size; 110 ssize_t size;
105 111
106 size = port_buffer_size_etc (from, B_TIMEOUT, 0); 112 size = port_buffer_size_etc (from, B_TIMEOUT, 0);
@@ -129,13 +135,16 @@ haiku_read (enum haiku_event_type *type, void *buf, ssize_t len)
129} 135}
130 136
131/* The same as haiku_read, but time out after TIMEOUT microseconds. 137/* The same as haiku_read, but time out after TIMEOUT microseconds.
138 POPUP_MENU_P means to read from the popup menu port instead.
132 Input is blocked when an attempt to read is in progress. */ 139 Input is blocked when an attempt to read is in progress. */
133int 140int
134haiku_read_with_timeout (enum haiku_event_type *type, void *buf, ssize_t len, 141haiku_read_with_timeout (enum haiku_event_type *type, void *buf, ssize_t len,
135 time_t timeout) 142 time_t timeout, bool popup_menu_p)
136{ 143{
137 int32 typ; 144 int32 typ;
138 port_id from = port_application_to_emacs; 145 port_id from = (popup_menu_p
146 ? port_popup_menu_to_emacs
147 : port_application_to_emacs);
139 148
140 block_input (); 149 block_input ();
141 if (read_port_etc (from, &typ, buf, len, 150 if (read_port_etc (from, &typ, buf, len,
@@ -165,9 +174,12 @@ haiku_write (enum haiku_event_type type, void *buf)
165} 174}
166 175
167int 176int
168haiku_write_without_signal (enum haiku_event_type type, void *buf) 177haiku_write_without_signal (enum haiku_event_type type, void *buf,
178 bool popup_menu_p)
169{ 179{
170 port_id to = port_application_to_emacs; 180 port_id to = (popup_menu_p
181 ? port_popup_menu_to_emacs
182 : port_application_to_emacs);
171 183
172 if (write_port (to, (int32_t) type, buf, haiku_len (type)) < B_OK) 184 if (write_port (to, (int32_t) type, buf, haiku_len (type)) < B_OK)
173 return -1; 185 return -1;
diff --git a/src/haiku_support.cc b/src/haiku_support.cc
index 41e5b71182f..05bc410eb28 100644
--- a/src/haiku_support.cc
+++ b/src/haiku_support.cc
@@ -114,6 +114,8 @@ static BLocker child_frame_lock;
114 114
115static BLocker movement_locker; 115static BLocker movement_locker;
116 116
117static BMessage volatile *popup_track_message;
118
117/* This could be a private API, but it's used by (at least) the Qt 119/* This could be a private API, but it's used by (at least) the Qt
118 port, so it's probably here to stay. */ 120 port, so it's probably here to stay. */
119extern status_t get_subpixel_antialiasing (bool *); 121extern status_t get_subpixel_antialiasing (bool *);
@@ -137,6 +139,30 @@ gui_abort (const char *msg)
137 emacs_abort (); 139 emacs_abort ();
138} 140}
139 141
142struct be_popup_menu_data
143{
144 int x, y;
145 BPopUpMenu *menu;
146};
147
148static int32
149be_popup_menu_thread_entry (void *thread_data)
150{
151 struct be_popup_menu_data *data;
152 BMenuItem *it;
153
154 data = (struct be_popup_menu_data *) thread_data;
155
156 it = data->menu->Go (BPoint (data->x, data->y));
157
158 if (it)
159 popup_track_message = it->Message ();
160 else
161 popup_track_message = NULL;
162
163 return 0;
164}
165
140/* Convert a raw character RAW produced by the keycode KEY into a key 166/* Convert a raw character RAW produced by the keycode KEY into a key
141 symbol and place it in KEYSYM. 167 symbol and place it in KEYSYM.
142 168
@@ -656,8 +682,10 @@ public:
656 else if (msg->GetPointer ("menuptr")) 682 else if (msg->GetPointer ("menuptr"))
657 { 683 {
658 struct haiku_menu_bar_select_event rq; 684 struct haiku_menu_bar_select_event rq;
685
659 rq.window = this; 686 rq.window = this;
660 rq.ptr = (void *) msg->GetPointer ("menuptr"); 687 rq.ptr = (void *) msg->GetPointer ("menuptr");
688
661 haiku_write (MENU_BAR_SELECT_EVENT, &rq); 689 haiku_write (MENU_BAR_SELECT_EVENT, &rq);
662 } 690 }
663 else if (msg->what == 'FPSE' 691 else if (msg->what == 'FPSE'
@@ -1607,6 +1635,7 @@ class EmacsMenuItem : public BMenuItem
1607{ 1635{
1608public: 1636public:
1609 int menu_bar_id = -1; 1637 int menu_bar_id = -1;
1638 void *menu_ptr = NULL;
1610 void *wind_ptr = NULL; 1639 void *wind_ptr = NULL;
1611 char *key = NULL; 1640 char *key = NULL;
1612 char *help = NULL; 1641 char *help = NULL;
@@ -1675,16 +1704,23 @@ public:
1675 1704
1676 if (help) 1705 if (help)
1677 menu->SetToolTip (highlight_p ? help : NULL); 1706 menu->SetToolTip (highlight_p ? help : NULL);
1678 else if (menu_bar_id >= 0) 1707 else
1679 { 1708 {
1680 rq.window = wind_ptr; 1709 rq.window = wind_ptr;
1681 rq.mb_idx = highlight_p ? menu_bar_id : -1; 1710 rq.mb_idx = highlight_p ? menu_bar_id : -1;
1711 rq.highlight_p = highlight_p;
1712 rq.data = menu_ptr;
1682 1713
1683 r = Frame (); 1714 r = Frame ();
1684 menu->GetMouse (&pt, &buttons); 1715 menu->GetMouse (&pt, &buttons);
1685 1716
1686 if (!highlight_p || r.Contains (pt)) 1717 if (!highlight_p || r.Contains (pt))
1687 haiku_write (MENU_BAR_HELP_EVENT, &rq); 1718 {
1719 if (menu_bar_id > 0)
1720 haiku_write (MENU_BAR_HELP_EVENT, &rq);
1721 else
1722 haiku_write_without_signal (MENU_BAR_HELP_EVENT, &rq, true);
1723 }
1688 } 1724 }
1689 1725
1690 BMenuItem::Highlight (highlight_p); 1726 BMenuItem::Highlight (highlight_p);
@@ -2353,6 +2389,7 @@ BMenu_add_item (void *menu, const char *label, void *ptr, bool enabled_p,
2353 it->menu_bar_id = (intptr_t) ptr; 2389 it->menu_bar_id = (intptr_t) ptr;
2354 it->wind_ptr = mbw_ptr; 2390 it->wind_ptr = mbw_ptr;
2355 } 2391 }
2392 it->menu_ptr = ptr;
2356 if (ptr) 2393 if (ptr)
2357 msg->AddPointer ("menuptr", ptr); 2394 msg->AddPointer ("menuptr", ptr);
2358 m->AddItem (it); 2395 m->AddItem (it);
@@ -2397,20 +2434,109 @@ BMenu_new_menu_bar_submenu (void *menu, const char *label)
2397 data of the selected item (if one exists), or NULL. X, Y should 2434 data of the selected item (if one exists), or NULL. X, Y should
2398 be in the screen coordinate system. */ 2435 be in the screen coordinate system. */
2399void * 2436void *
2400BMenu_run (void *menu, int x, int y) 2437BMenu_run (void *menu, int x, int y,
2438 void (*run_help_callback) (void *, void *),
2439 void (*block_input_function) (void),
2440 void (*unblock_input_function) (void),
2441 void (*process_pending_signals_function) (void),
2442 void *run_help_callback_data)
2401{ 2443{
2402 BPopUpMenu *mn = (BPopUpMenu *) menu; 2444 BPopUpMenu *mn = (BPopUpMenu *) menu;
2445 enum haiku_event_type type;
2446 void *buf;
2447 void *ptr = NULL;
2448 struct be_popup_menu_data data;
2449 struct object_wait_info infos[2];
2450 struct haiku_menu_bar_help_event *event;
2451 BMessage *msg;
2452 ssize_t stat;
2453
2454 block_input_function ();
2455 port_popup_menu_to_emacs = create_port (1800, "popup menu port");
2456 data.x = x;
2457 data.y = y;
2458 data.menu = mn;
2459 unblock_input_function ();
2460
2461 if (port_popup_menu_to_emacs < B_OK)
2462 return NULL;
2463
2464 block_input_function ();
2403 mn->SetRadioMode (0); 2465 mn->SetRadioMode (0);
2404 BMenuItem *it = mn->Go (BPoint (x, y)); 2466 buf = alloca (200);
2405 if (it) 2467
2468 infos[0].object = port_popup_menu_to_emacs;
2469 infos[0].type = B_OBJECT_TYPE_PORT;
2470 infos[0].events = B_EVENT_READ;
2471
2472 infos[1].object = spawn_thread (be_popup_menu_thread_entry,
2473 "Menu tracker", B_DEFAULT_MEDIA_PRIORITY,
2474 (void *) &data);
2475 infos[1].type = B_OBJECT_TYPE_THREAD;
2476 infos[1].events = B_EVENT_INVALID;
2477 unblock_input_function ();
2478
2479 if (infos[1].object < B_OK)
2406 { 2480 {
2407 BMessage *mg = it->Message (); 2481 block_input_function ();
2408 if (mg) 2482 delete_port (port_popup_menu_to_emacs);
2409 return (void *) mg->GetPointer ("menuptr"); 2483 unblock_input_function ();
2410 else 2484 return NULL;
2411 return NULL; 2485 }
2486
2487 block_input_function ();
2488 resume_thread (infos[1].object);
2489 unblock_input_function ();
2490
2491 while (true)
2492 {
2493 if ((stat = wait_for_objects_etc ((object_wait_info *) &infos, 2,
2494 B_RELATIVE_TIMEOUT, 10000)) < B_OK)
2495 {
2496 if (stat == B_INTERRUPTED)
2497 continue;
2498 else if (stat == B_TIMED_OUT)
2499 {
2500 process_pending_signals_function ();
2501 continue;
2502 }
2503 else
2504 gui_abort ("Failed to wait for popup");
2505 }
2506
2507 if (infos[0].events & B_EVENT_READ)
2508 {
2509 if (!haiku_read_with_timeout (&type, buf, 200, 1000000, true))
2510 {
2511 switch (type)
2512 {
2513 case MENU_BAR_HELP_EVENT:
2514 event = (struct haiku_menu_bar_help_event *) buf;
2515 run_help_callback (event->highlight_p
2516 ? event->data
2517 : NULL, run_help_callback_data);
2518 break;
2519 default:
2520 gui_abort ("Unknown popup menu event");
2521 }
2522 }
2523 }
2524
2525 if (infos[1].events & B_EVENT_INVALID)
2526 {
2527 block_input_function ();
2528 msg = (BMessage *) popup_track_message;
2529 if (popup_track_message)
2530 ptr = (void *) msg->GetPointer ("menuptr");
2531
2532 delete_port (port_popup_menu_to_emacs);
2533 unblock_input_function ();
2534 return ptr;
2535 }
2536
2537 infos[0].events = B_EVENT_READ;
2538 infos[1].events = B_EVENT_INVALID;
2412 } 2539 }
2413 return NULL;
2414} 2540}
2415 2541
2416/* Delete the entire menu hierarchy of MENU, and then delete MENU 2542/* Delete the entire menu hierarchy of MENU, and then delete MENU
@@ -2864,7 +2990,7 @@ be_popup_file_dialog (int open_p, const char *default_dir, int must_match_p, int
2864 enum haiku_event_type type; 2990 enum haiku_event_type type;
2865 char *ptr = NULL; 2991 char *ptr = NULL;
2866 2992
2867 if (!haiku_read_with_timeout (&type, buf, 200, 1000000)) 2993 if (!haiku_read_with_timeout (&type, buf, 200, 1000000, false))
2868 { 2994 {
2869 block_input_function (); 2995 block_input_function ();
2870 if (type != FILE_PANEL_EVENT) 2996 if (type != FILE_PANEL_EVENT)
@@ -2878,7 +3004,7 @@ be_popup_file_dialog (int open_p, const char *default_dir, int must_match_p, int
2878 3004
2879 ssize_t b_s; 3005 ssize_t b_s;
2880 block_input_function (); 3006 block_input_function ();
2881 haiku_read_size (&b_s); 3007 haiku_read_size (&b_s, false);
2882 if (!b_s || ptr || panel->Window ()->IsHidden ()) 3008 if (!b_s || ptr || panel->Window ()->IsHidden ())
2883 { 3009 {
2884 c_unbind_to_nil_from_cxx (idx); 3010 c_unbind_to_nil_from_cxx (idx);
diff --git a/src/haiku_support.h b/src/haiku_support.h
index 8d4dddd90fa..4b0456168d0 100644
--- a/src/haiku_support.h
+++ b/src/haiku_support.h
@@ -200,6 +200,8 @@ struct haiku_menu_bar_help_event
200{ 200{
201 void *window; 201 void *window;
202 int mb_idx; 202 int mb_idx;
203 void *data;
204 bool highlight_p;
203}; 205};
204 206
205struct haiku_zoom_event 207struct haiku_zoom_event
@@ -358,25 +360,27 @@ extern "C"
358#endif 360#endif
359 361
360 extern port_id port_application_to_emacs; 362 extern port_id port_application_to_emacs;
363 extern port_id port_popup_menu_to_emacs;
361 364
362 extern void haiku_io_init (void); 365 extern void haiku_io_init (void);
363 extern void haiku_io_init_in_app_thread (void); 366 extern void haiku_io_init_in_app_thread (void);
364 367
365 extern void 368 extern void
366 haiku_read_size (ssize_t *len); 369 haiku_read_size (ssize_t *len, bool popup_menu_p);
367 370
368 extern int 371 extern int
369 haiku_read (enum haiku_event_type *type, void *buf, ssize_t len); 372 haiku_read (enum haiku_event_type *type, void *buf, ssize_t len);
370 373
371 extern int 374 extern int
372 haiku_read_with_timeout (enum haiku_event_type *type, void *buf, ssize_t len, 375 haiku_read_with_timeout (enum haiku_event_type *type, void *buf, ssize_t len,
373 time_t timeout); 376 time_t timeout, bool popup_menu_p);
374 377
375 extern int 378 extern int
376 haiku_write (enum haiku_event_type type, void *buf); 379 haiku_write (enum haiku_event_type type, void *buf);
377 380
378 extern int 381 extern int
379 haiku_write_without_signal (enum haiku_event_type type, void *buf); 382 haiku_write_without_signal (enum haiku_event_type type, void *buf,
383 bool popup_menu_p);
380 384
381 extern void 385 extern void
382 rgb_color_hsl (uint32_t rgb, double *h, double *s, double *l); 386 rgb_color_hsl (uint32_t rgb, double *h, double *s, double *l);
@@ -679,7 +683,12 @@ extern "C"
679 BMenu_item_at (void *menu, int idx); 683 BMenu_item_at (void *menu, int idx);
680 684
681 extern void * 685 extern void *
682 BMenu_run (void *menu, int x, int y); 686 BMenu_run (void *menu, int x, int y,
687 void (*run_help_callback) (void *, void *),
688 void (*block_input_function) (void),
689 void (*unblock_input_function) (void),
690 void (*process_pending_signals_function) (void),
691 void *run_help_callback_data);
683 692
684 extern void 693 extern void
685 BPopUpMenu_delete (void *menu); 694 BPopUpMenu_delete (void *menu);
diff --git a/src/haikumenu.c b/src/haikumenu.c
index 875f1afb6a2..26eb3dbfe13 100644
--- a/src/haikumenu.c
+++ b/src/haikumenu.c
@@ -150,11 +150,20 @@ digest_menu_items (void *first_menu, int start, int menu_items_used,
150 else if (NILP (def) && menu_separator_name_p (SSDATA (item_name))) 150 else if (NILP (def) && menu_separator_name_p (SSDATA (item_name)))
151 BMenu_add_separator (menu); 151 BMenu_add_separator (menu);
152 else if (!mbar_p) 152 else if (!mbar_p)
153 BMenu_add_item (menu, SSDATA (item_name), 153 {
154 !NILP (def) ? aref_addr (menu_items, i) : NULL, 154 if (!use_system_tooltips || NILP (Fsymbol_value (Qtooltip_mode)))
155 !NILP (enable), !NILP (selected), 0, window, 155 BMenu_add_item (menu, SSDATA (item_name),
156 !NILP (descrip) ? SSDATA (descrip) : NULL, 156 !NILP (def) ? aref_addr (menu_items, i) : NULL,
157 STRINGP (help) ? SSDATA (help) : NULL); 157 !NILP (enable), !NILP (selected), 0, window,
158 !NILP (descrip) ? SSDATA (descrip) : NULL,
159 NULL);
160 else
161 BMenu_add_item (menu, SSDATA (item_name),
162 !NILP (def) ? aref_addr (menu_items, i) : NULL,
163 !NILP (enable), !NILP (selected), 0, window,
164 !NILP (descrip) ? SSDATA (descrip) : NULL,
165 STRINGP (help) ? SSDATA (help) : NULL);
166 }
158 else if (!use_system_tooltips || NILP (Fsymbol_value (Qtooltip_mode))) 167 else if (!use_system_tooltips || NILP (Fsymbol_value (Qtooltip_mode)))
159 BMenu_add_item (menu, SSDATA (item_name), 168 BMenu_add_item (menu, SSDATA (item_name),
160 !NILP (def) ? (void *) (intptr_t) i : NULL, 169 !NILP (def) ? (void *) (intptr_t) i : NULL,
@@ -294,6 +303,27 @@ haiku_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents)
294 return selection; 303 return selection;
295} 304}
296 305
306static void
307haiku_menu_show_help (void *help, void *data)
308{
309 Lisp_Object *id = (Lisp_Object *) help;
310
311 if (help)
312 show_help_echo (id[MENU_ITEMS_ITEM_HELP],
313 Qnil, Qnil, Qnil);
314 else
315 show_help_echo (Qnil, Qnil, Qnil, Qnil);
316}
317
318static void
319haiku_process_pending_signals_for_menu (void)
320{
321 process_pending_signals ();
322
323 input_pending = false;
324 detect_input_pending_run_timers (true);
325}
326
297Lisp_Object 327Lisp_Object
298haiku_menu_show (struct frame *f, int x, int y, int menuflags, 328haiku_menu_show (struct frame *f, int x, int y, int menuflags,
299 Lisp_Object title, const char **error_name) 329 Lisp_Object title, const char **error_name)
@@ -327,9 +357,14 @@ haiku_menu_show (struct frame *f, int x, int y, int menuflags,
327 } 357 }
328 digest_menu_items (menu, 0, menu_items_used, 0); 358 digest_menu_items (menu, 0, menu_items_used, 0);
329 BView_convert_to_screen (view, &x, &y); 359 BView_convert_to_screen (view, &x, &y);
330 menu_item_selection = BMenu_run (menu, x, y);
331 unblock_input (); 360 unblock_input ();
332 361
362 popup_activated_p++;
363 menu_item_selection = BMenu_run (menu, x, y, haiku_menu_show_help,
364 block_input, unblock_input,
365 haiku_process_pending_signals_for_menu, NULL);
366 popup_activated_p--;
367
333 FRAME_DISPLAY_INFO (f)->grabbed = 0; 368 FRAME_DISPLAY_INFO (f)->grabbed = 0;
334 369
335 if (menu_item_selection) 370 if (menu_item_selection)
diff --git a/src/haikuterm.c b/src/haikuterm.c
index b9eb1d2fc5e..6a84e61add4 100644
--- a/src/haikuterm.c
+++ b/src/haikuterm.c
@@ -2559,7 +2559,7 @@ haiku_read_socket (struct terminal *terminal, struct input_event *hold_quit)
2559 2559
2560 if (!buf) 2560 if (!buf)
2561 buf = xmalloc (200); 2561 buf = xmalloc (200);
2562 haiku_read_size (&b_size); 2562 haiku_read_size (&b_size, false);
2563 while (b_size >= 0) 2563 while (b_size >= 0)
2564 { 2564 {
2565 enum haiku_event_type type; 2565 enum haiku_event_type type;
@@ -2831,6 +2831,8 @@ haiku_read_socket (struct terminal *terminal, struct input_event *hold_quit)
2831 || !NILP (previous_help_echo_string)) 2831 || !NILP (previous_help_echo_string))
2832 do_help = 1; 2832 do_help = 1;
2833 } 2833 }
2834
2835 need_flush = FRAME_DIRTY_P (f);
2834 break; 2836 break;
2835 } 2837 }
2836 case BUTTON_UP: 2838 case BUTTON_UP:
@@ -3260,7 +3262,7 @@ haiku_read_socket (struct terminal *terminal, struct input_event *hold_quit)
3260 break; 3262 break;
3261 } 3263 }
3262 3264
3263 haiku_read_size (&b_size); 3265 haiku_read_size (&b_size, false);
3264 3266
3265 if (inev.kind != NO_EVENT) 3267 if (inev.kind != NO_EVENT)
3266 { 3268 {
@@ -3285,7 +3287,7 @@ haiku_read_socket (struct terminal *terminal, struct input_event *hold_quit)
3285 3287
3286 for (struct unhandled_event *ev = unhandled_events; ev;) 3288 for (struct unhandled_event *ev = unhandled_events; ev;)
3287 { 3289 {
3288 haiku_write_without_signal (ev->type, &ev->buffer); 3290 haiku_write_without_signal (ev->type, &ev->buffer, false);
3289 struct unhandled_event *old = ev; 3291 struct unhandled_event *old = ev;
3290 ev = old->next; 3292 ev = old->next;
3291 xfree (old); 3293 xfree (old);