~ubuntu-branches/ubuntu/gutsy/flwm/gutsy

1 by Tommi Virtanen
Import upstream version 1.00
1
// Frame.C
2
3
#include "config.h"
4
#include "Frame.H"
5
#include "Desktop.H"
6
#include <string.h>
7
#include <stdio.h>
8
#include <FL/fl_draw.H>
9
#include "Rotated.H"
10
11
static Atom wm_state = 0;
12
static Atom wm_change_state;
13
static Atom wm_protocols;
14
static Atom wm_delete_window;
15
static Atom wm_take_focus;
16
static Atom wm_save_yourself;
17
static Atom wm_colormap_windows;
18
static Atom _motif_wm_hints;
19
static Atom kwm_win_decoration;
20
#if DESKTOPS
21
static Atom kwm_win_desktop;
22
static Atom kwm_win_sticky;
23
#endif
24
//static Atom wm_client_leader;
25
static Atom _wm_quit_app;
26
27
// these are set by initialize in main.C:
28
Atom _win_hints;
29
Atom _win_state;
30
#if DESKTOPS
31
extern Atom _win_workspace;
32
#endif
33
34
#ifdef SHOW_CLOCK
35
extern char clock_buf[];
36
extern int clock_alarm_on;
37
#endif
38
39
static const int XEventMask =
40
ExposureMask|StructureNotifyMask
41
|KeyPressMask|KeyReleaseMask|KeymapStateMask|FocusChangeMask
42
|ButtonPressMask|ButtonReleaseMask
43
|EnterWindowMask|LeaveWindowMask
44
|PointerMotionMask|SubstructureRedirectMask|SubstructureNotifyMask;
45
46
extern Fl_Window* Root;
47
48
Frame* Frame::active_;
49
Frame* Frame::first;
50
51
static inline int max(int a, int b) {return a > b ? a : b;}
52
static inline int min(int a, int b) {return a < b ? a : b;}
53
54
////////////////////////////////////////////////////////////////
55
// The constructor is by far the most complex part, as it collects
56
// all the scattered pieces of information about the window that
57
// X has and uses them to initialize the structure, position the
58
// window, and then finally create it.
59
60
int dont_set_event_mask = 0; // used by FrameWindow
61
62
// "existing" is a pointer to an XWindowAttributes structure that is
63
// passed for an already-existing window when the window manager is
64
// starting up.  If so we don't want to alter the state, size, or
65
// position.  If null than this is a MapRequest of a new window.
66
Frame::Frame(Window window, XWindowAttributes* existing) :
67
  Fl_Window(0,0),
68
  window_(window),
69
  state_flags_(0),
70
  flags_(0),
71
  transient_for_xid(None),
72
  transient_for_(0),
73
  revert_to(active_),
74
  colormapWinCount(0),
75
  close_button(BUTTON_LEFT,BUTTON_TOP,BUTTON_W,BUTTON_H,"X"),
76
  iconize_button(BUTTON_LEFT,BUTTON_TOP,BUTTON_W,BUTTON_H,"i"),
77
  max_h_button(BUTTON_LEFT,BUTTON_TOP+3*BUTTON_H,BUTTON_W,BUTTON_H,"h"),
78
  max_w_button(BUTTON_LEFT,BUTTON_TOP+BUTTON_H,BUTTON_W,BUTTON_H,"w"),
79
  min_w_button(BUTTON_LEFT,BUTTON_TOP+2*BUTTON_H,BUTTON_W,BUTTON_H,"W")
80
{
81
  close_button.callback(button_cb_static);
82
  iconize_button.callback(button_cb_static);
83
  max_h_button.type(FL_TOGGLE_BUTTON);
84
  max_h_button.callback(button_cb_static);
85
  max_w_button.type(FL_TOGGLE_BUTTON);
86
  max_w_button.callback(button_cb_static);
87
  min_w_button.type(FL_TOGGLE_BUTTON);
88
  min_w_button.callback(button_cb_static);
89
  end();
90
  box(FL_NO_BOX); // relies on background color erasing interior
91
  next = first;
92
  first = this;
93
94
  // do this asap so we don't miss any events...
95
  if (!dont_set_event_mask)
96
    XSelectInput(fl_display, window_,
97
		 ColormapChangeMask | PropertyChangeMask | FocusChangeMask
98
		 );
99
100
  if (!wm_state) {
101
    // allocate all the atoms if this is the first time
102
    wm_state		= XInternAtom(fl_display, "WM_STATE",		0);
103
    wm_change_state	= XInternAtom(fl_display, "WM_CHANGE_STATE",	0);
104
    wm_protocols	= XInternAtom(fl_display, "WM_PROTOCOLS",	0);
105
    wm_delete_window	= XInternAtom(fl_display, "WM_DELETE_WINDOW",	0);
106
    wm_take_focus	= XInternAtom(fl_display, "WM_TAKE_FOCUS",	0);
107
    wm_save_yourself	= XInternAtom(fl_display, "WM_SAVE_YOURSELF",	0);
108
    wm_colormap_windows	= XInternAtom(fl_display, "WM_COLORMAP_WINDOWS",0);
109
    _motif_wm_hints	= XInternAtom(fl_display, "_MOTIF_WM_HINTS",	0);
110
    kwm_win_decoration	= XInternAtom(fl_display, "KWM_WIN_DECORATION",	0);
111
#if DESKTOPS
112
    kwm_win_desktop	= XInternAtom(fl_display, "KWM_WIN_DESKTOP",	0);
113
    kwm_win_sticky	= XInternAtom(fl_display, "KWM_WIN_STICKY",	0);
114
#endif
115
//  wm_client_leader	= XInternAtom(fl_display, "WM_CLIENT_LEADER",	0);
116
    _wm_quit_app	= XInternAtom(fl_display, "_WM_QUIT_APP",	0);
117
  }
118
119
  label_y = label_h = label_w = 0;
120
  getLabel();
121
  // getIconLabel();
122
123
  {XWindowAttributes attr;
124
  if (existing) attr = *existing;
125
  else {
126
    // put in some legal values in case XGetWindowAttributes fails:
127
    attr.x = attr.y = 0; attr.width = attr.height = 100;
128
    attr.colormap = fl_colormap;
129
    attr.border_width = 0;
130
    XGetWindowAttributes(fl_display, window, &attr);
131
  }
132
  left = top = dwidth = dheight = 0; // pretend border is zero-width for now
133
  app_border_width = attr.border_width;
134
  x(attr.x+app_border_width); restore_x = x();
135
  y(attr.y+app_border_width); restore_y = y();
136
  w(attr.width); restore_w = w();
137
  h(attr.height); restore_h = h();
138
  colormap = attr.colormap;}
139
140
  getColormaps();
141
142
  //group_ = 0;
143
  {XWMHints* hints = XGetWMHints(fl_display, window_);
144
  if (hints) {
145
    if ((hints->flags & InputHint) && !hints->input) set_flag(NO_FOCUS);
146
    //if (hints && hints->flags&WindowGroupHint) group_ = hints->window_group;
147
  }
148
  switch (getIntProperty(wm_state, wm_state, 0)) {
149
  case NormalState:
150
    state_ = NORMAL; break;
151
  case IconicState:
152
    state_ = ICONIC; break;
153
  // X also defines obsolete values ZoomState and InactiveState
154
  default:
155
    if (hints && (hints->flags&StateHint) && hints->initial_state==IconicState)
156
      state_ = ICONIC;
157
    else
158
      state_ = NORMAL;
159
  }
160
  if (hints) XFree(hints);}
161
  // Maya sets this, seems to mean the same as group:
162
  // if (!group_) group_ = getIntProperty(wm_client_leader, XA_WINDOW);
163
164
  XGetTransientForHint(fl_display, window_, &transient_for_xid);
165
166
  getProtocols();
167
168
  getMotifHints();
169
170
  // get Gnome hints:
171
  int p = getIntProperty(_win_hints, XA_CARDINAL);
172
  if (p&1) set_flag(NO_FOCUS);		// WIN_HINTS_SKIP_FOCUS
173
  // if (p&2)				// WIN_HINTS_SKIP_WINLIST
174
  // if (p&4)				// WIN_HINTS_SKIP_TASKBAR
175
  // if (p&8) ...			// WIN_HINTS_GROUP_TRANSIENT
176
  if (p&16) set_flag(CLICK_TO_FOCUS);	// WIN_HINTS_FOCUS_ON_CLICK
177
178
  // get KDE hints:
179
  p = getIntProperty(kwm_win_decoration, kwm_win_decoration, 1);
180
  if (!(p&3)) set_flag(NO_BORDER);
181
  else if (p & 2) set_flag(THIN_BORDER);
182
  if (p & 256) set_flag(NO_FOCUS);
183
184
  fix_transient_for();
185
186
  if (transient_for()) {
187
    if (state_ == NORMAL) state_ = transient_for()->state_;
188
#if DESKTOPS
189
    desktop_ = transient_for()->desktop_;
190
#endif
191
  }
192
#if DESKTOPS
193
  // see if anybody thinks window is "sticky:"
194
  else if ((getIntProperty(_win_state, XA_CARDINAL) & 1) // WIN_STATE_STICKY
195
      || getIntProperty(kwm_win_sticky, kwm_win_sticky)) {
196
    desktop_ = 0;
197
  } else {
198
    // get the desktop from either Gnome or KDE (Gnome takes precedence):
199
    p = getIntProperty(_win_workspace, XA_CARDINAL, -1) + 1; // Gnome desktop
200
    if (p <= 0) p = getIntProperty(kwm_win_desktop, kwm_win_desktop);
201
    if (p > 0 && p < 25)
202
      desktop_ = Desktop::number(p, 1);
203
    else
204
      desktop_ = Desktop::current();
205
  }
206
  if (desktop_ && desktop_ != Desktop::current())
207
    if (state_ == NORMAL) state_ = OTHER_DESKTOP;
208
#endif
209
210
  int autoplace = getSizes();
211
  // some Motif programs assumme this will force the size to conform :-(
212
  if (w() < min_w || h() < min_h) {
213
    if (w() < min_w) w(min_w);
214
    if (h() < min_h) h(min_h);
215
    XResizeWindow(fl_display, window_, w(), h());
216
  }
217
218
  // try to detect programs that think "transient_for" means "no border":
219
  if (transient_for_xid && !label() && !flag(NO_BORDER)) {
220
    set_flag(THIN_BORDER);
221
  }
222
  updateBorder();
223
  show_hide_buttons();
224
225
  if (autoplace && !existing && !(transient_for() && (x() || y()))) {
226
    // autoplacement (stupid version for now)
227
    x(Root->x()+(Root->w()-w())/2);
228
    y(Root->y()+(Root->h()-h())/2);
229
    // move it until it does not hide any existing windows:
230
    const int delta = TITLE_WIDTH+LEFT;
231
    for (Frame* f = next; f; f = f->next) {
232
      if (f->x()+delta > x() && f->y()+delta > y() &&
233
	  f->x()+f->w()-delta < x()+w() && f->y()+f->h()-delta < y()+h()) {
234
	x(max(x(),f->x()+delta));
235
	y(max(y(),f->y()+delta));
236
	f = this;
237
      }
238
    }
239
  }
240
  // move window so contents and border are visible:
241
  x(force_x_onscreen(x(), w()));
242
  y(force_y_onscreen(y(), h()));
243
244
  // guess some values for the "restore" fields, if already maximized:
245
  if (max_w_button.value()) {
246
    restore_w = min_w + ((w()-dwidth-min_w)/2/inc_w) * inc_w;
247
    restore_x = x()+left + (w()-dwidth-restore_w)/2;
248
  }
249
  if (max_h_button.value()) {
250
    restore_h = min_h + ((h()-dheight-min_h)/2/inc_h) * inc_h;
251
    restore_y = y()+top + (h()-dheight-restore_h)/2;
252
  }
253
254
  const int mask = CWBorderPixel | CWColormap | CWEventMask | CWBitGravity
255
    | CWBackPixel | CWOverrideRedirect;
256
  XSetWindowAttributes sattr;
257
  sattr.event_mask = XEventMask;
258
  sattr.colormap = fl_colormap;
259
  sattr.border_pixel = fl_xpixel(FL_GRAY0);
260
  sattr.bit_gravity = NorthWestGravity;
261
  sattr.override_redirect = 1;
262
  sattr.background_pixel = fl_xpixel(FL_GRAY);
263
  Fl_X::set_xid(this, XCreateWindow(fl_display, fl_xid(Root),
264
			     x(), y(), w(), h(), 0,
265
			     fl_visual->depth,
266
			     InputOutput,
267
			     fl_visual->visual,
268
			     mask, &sattr));
269
270
  setStateProperty();
271
272
  if (!dont_set_event_mask) XAddToSaveSet(fl_display, window_);
273
  if (existing) set_state_flag(IGNORE_UNMAP);
274
  XReparentWindow(fl_display, window_, fl_xid(this), left, top);
275
  XSetWindowBorderWidth(fl_display, window_, 0);
276
  if (state_ == NORMAL) XMapWindow(fl_display, window_);
277
  sendConfigureNotify(); // many apps expect this even if window size unchanged
278
279
#if CLICK_RAISES || CLICK_TO_TYPE
280
  XGrabButton(fl_display, AnyButton, AnyModifier, window, False,
281
	      ButtonPressMask, GrabModeSync, GrabModeAsync, None, None);
282
#endif
283
284
  if (state_ == NORMAL) {
285
    XMapWindow(fl_display, fl_xid(this));
286
    if (!existing) activate_if_transient();
287
  }
288
}
289
290
// modify the passed X & W to a legal horizontal window position
291
int Frame::force_x_onscreen(int X, int W) {
292
  // force all except the black border on-screen:
293
  X = min(X, Root->x()+Root->w()+1-W);
294
  X = max(X, Root->x()-1);
295
  // force the contents on-screen:
296
  X = min(X, Root->x()+Root->w()-W+dwidth-left);
297
  if (W-dwidth > Root->w() || h()-dheight > Root->h())
298
    // windows bigger than the screen need title bar so they can move
299
    X = max(X, Root->x()-LEFT);
300
  else
301
    X = max(X, Root->x()-left);
302
  return X;
303
}
304
305
// modify the passed Y & H to a legal vertical window position:
306
int Frame::force_y_onscreen(int Y, int H) {
307
  // force border (except black edge) to be on-screen:
308
  Y = min(Y, Root->y()+Root->h()+1-H);
309
  Y = max(Y, Root->y()-1);
310
  // force contents to be on-screen:
311
  Y = min(Y, Root->y()+Root->h()-H+dheight-top);
312
  Y = max(Y, Root->y()-top);
313
  return Y;
314
}
315
316
////////////////////////////////////////////////////////////////
317
// destructor
318
// The destructor is called on DestroyNotify, so I don't have to do anything
319
// to the contained window, which is already been destroyed.
320
321
// fltk bug: it does not clear these pointers when window is deleted,
322
// causing flwm to crash on window close sometimes:
323
extern Fl_Window *fl_xfocus;
324
extern Fl_Window *fl_xmousewin;
325
326
Frame::~Frame() {
327
328
  // It is possible for the frame to be destroyed while the menu is
329
  // popped-up, and the menu will still contain a pointer to it.  To
330
  // fix this the menu checks the state_ location for a legal and
331
  // non-withdrawn state value before doing anything.  This should
332
  // be reliable unless something reallocates the memory and writes
333
  // a legal state value to this location:
334
  state_ = UNMAPPED;
335
336
  // fix fltk bug:
337
  fl_xfocus = 0;
338
  fl_xmousewin = 0;
339
  Fl::focus_ = 0;
340
341
  // remove any pointers to this:
342
  Frame** cp; for (cp = &first; *cp; cp = &((*cp)->next))
343
    if (*cp == this) {*cp = next; break;}
344
  for (Frame* f = first; f; f = f->next) {
345
    if (f->transient_for_ == this) f->transient_for_ = transient_for_;
346
    if (f->revert_to == this) f->revert_to = revert_to;
347
  }
348
  throw_focus(1);
349
350
  if (colormapWinCount) {
351
    XFree((char *)colormapWindows);
352
    delete[] window_Colormaps;
353
  }
354
  //if (iconlabel()) XFree((char*)iconlabel());
355
  if (label())     XFree((char*)label());
356
}
357
358
////////////////////////////////////////////////////////////////
359
360
void Frame::getLabel(int del) {
361
  char* old = (char*)label();
362
  char* nu = del ? 0 : (char*)getProperty(XA_WM_NAME);
363
  if (nu) {
364
    // since many window managers print a default label when none is
365
    // given, many programs send spaces to make a blank label.  Detect
366
    // this and make it really be blank:
367
    char* c = nu; while (*c == ' ') c++;
368
    if (!*c) {XFree(nu); nu = 0;}
369
  }
370
  if (old) {
371
    if (nu && !strcmp(old,nu)) {XFree(nu); return;}
372
    XFree(old);
373
  } else {
374
    if (!nu) return;
375
  }
376
  Fl_Widget::label(nu);
377
  if (nu) {
378
    fl_font(TITLE_FONT_SLOT, TITLE_FONT_SIZE);
379
    label_w = int(fl_width(nu))+6;
380
  } else
381
    label_w = 0;
382
  if (shown() && label_h > 3 && left > 3)
383
    XClearArea(fl_display, fl_xid(this), 1, label_y+3, left-1, label_h-3, 1);
384
}
385
386
////////////////////////////////////////////////////////////////
387
388
int Frame::getGnomeState(int &) {
389
// values for _WIN_STATE property are from Gnome WM compliance docs:
390
#define WIN_STATE_STICKY          (1<<0) /*everyone knows sticky*/
391
#define WIN_STATE_MINIMIZED       (1<<1) /*Reserved - definition is unclear*/
392
#define WIN_STATE_MAXIMIZED_VERT  (1<<2) /*window in maximized V state*/
393
#define WIN_STATE_MAXIMIZED_HORIZ (1<<3) /*window in maximized H state*/
394
#define WIN_STATE_HIDDEN          (1<<4) /*not on taskbar but window visible*/
395
#define WIN_STATE_SHADED          (1<<5) /*shaded (MacOS / Afterstep style)*/
396
#define WIN_STATE_HID_WORKSPACE   (1<<6) /*not on current desktop*/
397
#define WIN_STATE_HID_TRANSIENT   (1<<7) /*owner of transient is hidden*/
398
#define WIN_STATE_FIXED_POSITION  (1<<8) /*window is fixed in position even*/
399
#define WIN_STATE_ARRANGE_IGNORE  (1<<9) /*ignore for auto arranging*/
400
  // nyi
401
  return 0;
402
}
403
404
////////////////////////////////////////////////////////////////
405
406
// Read the sizeHints, and try to remove the vast number of mistakes
407
// that some applications seem to do writing them.
408
// Returns true if autoplace should be done.
409
410
int Frame::getSizes() {
411
412
  XSizeHints sizeHints;
413
  long junk;
414
  if (!XGetWMNormalHints(fl_display, window_, &sizeHints, &junk))
415
    sizeHints.flags = 0;
416
417
  // get the increment, use 1 if none or illegal values:
418
  if (sizeHints.flags & PResizeInc) {
419
    inc_w = sizeHints.width_inc; if (inc_w < 1) inc_w = 1;
420
    inc_h = sizeHints.height_inc; if (inc_h < 1) inc_h = 1;
421
  } else {
422
    inc_w = inc_h = 1;
423
  }
424
425
  // get the current size of the window:
426
  int W = w()-dwidth;
427
  int H = h()-dheight;
428
  // I try a lot of places to get a good minimum size value.  Lots of
429
  // programs set illegal or junk values, so getting this correct is
430
  // difficult:
431
  min_w = W;
432
  min_h = H;
433
434
  // guess a value for minimum size in case it is not set anywhere:
435
  min_w = min(min_w, 4*BUTTON_H);
436
  min_w = ((min_w+inc_w-1)/inc_w) * inc_w;
437
  min_h = min(min_h, 4*BUTTON_H);
438
  min_h = ((min_h+inc_h-1)/inc_h) * inc_h;
439
  // some programs put the minimum size here:
440
  if (sizeHints.flags & PBaseSize) {
441
    junk = sizeHints.base_width; if (junk > 0) min_w = junk;
442
    junk = sizeHints.base_height; if (junk > 0) min_h = junk;
443
  }
444
  // finally, try the actual place the minimum size should be:
445
  if (sizeHints.flags & PMinSize) {
446
    junk = sizeHints.min_width; if (junk > 0) min_w = junk;
447
    junk = sizeHints.min_height; if (junk > 0) min_h = junk;
448
  }
449
450
  max_w = max_h = 0; // default maximum size is "infinity"
451
  if (sizeHints.flags & PMaxSize) {
452
    // Though not defined by ICCCM standard, I interpret any maximum
453
    // size that is less than the minimum to mean "infinity".  This
454
    // allows the maximum to be set in one direction only:
455
    junk = sizeHints.max_width;
456
    if (junk >= min_w && junk <= W) max_w = junk;
457
    junk = sizeHints.max_height;
458
    if (junk >= min_h && junk <= H) max_h = junk;
459
  }
460
461
  // set the maximize buttons according to current size:
462
  max_w_button.value(W == maximize_width());
463
  max_h_button.value(H == maximize_height());
464
465
  // Currently only 1x1 aspect works:
466
  if (sizeHints.flags & PAspect
467
      && sizeHints.min_aspect.x == sizeHints.min_aspect.y)
468
    set_flag(KEEP_ASPECT);
469
470
  // another fix for gimp, which sets PPosition to 0,0:
471
  if (x() <= 0 && y() <= 0) sizeHints.flags &= ~PPosition;
472
473
  return !(sizeHints.flags & (USPosition|PPosition));
474
}
475
476
int max_w_switch;
477
// return width of contents when maximize button pressed:
478
int Frame::maximize_width() {
479
  int W = max_w_switch; if (!W) W = Root->w();
480
  return ((W-TITLE_WIDTH-min_w)/inc_w) * inc_w + min_w;
481
}
482
483
int max_h_switch;
484
int Frame::maximize_height() {
485
  int H = max_h_switch; if (!H) H = Root->h();
486
  return ((H-min_h)/inc_h) * inc_h + min_h;
487
}
488
489
////////////////////////////////////////////////////////////////
490
491
void Frame::getProtocols() {
492
  int n; Atom* p = (Atom*)getProperty(wm_protocols, XA_ATOM, &n);
493
  if (p) {
494
    clear_flag(DELETE_WINDOW_PROTOCOL|TAKE_FOCUS_PROTOCOL|QUIT_PROTOCOL);
495
    for (int i = 0; i < n; ++i) {
496
      if (p[i] == wm_delete_window) {
497
	set_flag(DELETE_WINDOW_PROTOCOL);
498
      } else if (p[i] == wm_take_focus) {
499
	set_flag(TAKE_FOCUS_PROTOCOL);
500
      } else if (p[i] == wm_save_yourself) {
501
	set_flag(SAVE_PROTOCOL);
502
      } else if (p[i] == _wm_quit_app) {
503
	set_flag(QUIT_PROTOCOL);
504
      }
505
    }
506
  }
507
  XFree((char*)p);
508
}
509
510
////////////////////////////////////////////////////////////////
511
512
int Frame::getMotifHints() {
513
  long* prop = (long*)getProperty(_motif_wm_hints, _motif_wm_hints);
514
  if (!prop) return 0;
515
516
  // see /usr/include/X11/Xm/MwmUtil.h for meaning of these bits...
517
  // prop[0] = flags (what props are specified)
518
  // prop[1] = functions (all, resize, move, minimize, maximize, close, quit)
519
  // prop[2] = decorations (all, border, resize, title, menu, minimize,
520
  //                        maximize)
521
  // prop[3] = input_mode (modeless, primary application modal, system modal,
522
  //                       full application modal)
523
  // prop[4] = status (tear-off window)
524
525
  // Fill in the default value for missing fields:
526
  if (!(prop[0]&1)) prop[1] = 1;
527
  if (!(prop[0]&2)) prop[2] = 1;
528
529
  // The low bit means "turn the marked items off", invert this.
530
  // Transient windows already have size & iconize buttons turned off:
531
  if (prop[1]&1) prop[1] = ~prop[1] & (transient_for_xid ? ~0x58 : -1);
532
  if (prop[2]&1) prop[2] = ~prop[2] & (transient_for_xid ? ~0x60 : -1);
533
534
  int old_flags = flags();
535
536
  // see if they are trying to turn off border:
537
  if (!(prop[2])) set_flag(NO_BORDER); else clear_flag(NO_BORDER);
538
539
  // see if they are trying to turn off title & close box:
540
  if (!(prop[2]&0x18)) set_flag(THIN_BORDER); else clear_flag(THIN_BORDER);
541
542
  // some Motif programs use this to disable resize :-(
543
  // and some programs change this after the window is shown (*&%$#%)
544
  if (!(prop[1]&2) || !(prop[2]&4))
545
    set_flag(NO_RESIZE); else clear_flag(NO_RESIZE);
546
547
  // and some use this to disable the Close function.  The commented
548
  // out test is it trying to turn off the mwm menu button: it appears
549
  // programs that do that still expect Alt+F4 to close them, so I
550
  // leave the close on then:
551
  if (!(prop[1]&0x20) /*|| !(prop[2]&0x10)*/)
552
    set_flag(NO_CLOSE); else clear_flag(NO_CLOSE);
553
554
  // see if they set "input hint" to non-zero:
555
  // prop[3] should be nonzero but the only example of this I have
556
  // found is Netscape 3.0 and it sets it to zero...
557
  if (!shown() && (prop[0]&4) /*&& prop[3]*/) set_flag(MODAL);
558
559
  // see if it is forcing the iconize button back on.  This makes
560
  // transient_for act like group instead...
561
  if ((prop[1]&0x8) || (prop[2]&0x20)) set_flag(ICONIZE);
562
563
  // Silly 'ol Amazon paint ignores WM_DELETE_WINDOW and expects to
564
  // get the SGI-specific "_WM_QUIT_APP".  It indicates this by trying
565
  // to turn off the close box. SIGH!!!
566
  if (flag(QUIT_PROTOCOL) && !(prop[1]&0x20))
567
    clear_flag(DELETE_WINDOW_PROTOCOL);
568
569
  XFree((char*)prop);
570
  return (flags() ^ old_flags);
571
}
572
573
////////////////////////////////////////////////////////////////
574
575
void Frame::getColormaps(void) {
576
  if (colormapWinCount) {
577
    XFree((char *)colormapWindows);
578
    delete[] window_Colormaps;
579
  }
580
  int n;
581
  Window* cw = (Window*)getProperty(wm_colormap_windows, XA_WINDOW, &n);
582
  if (cw) {
583
    colormapWinCount = n;
584
    colormapWindows = cw;
585
    window_Colormaps = new Colormap[n];
586
    for (int i = 0; i < n; ++i) {
587
      if (cw[i] == window_) {
588
	window_Colormaps[i] = colormap;
589
      } else {
590
	XWindowAttributes attr;
591
	XSelectInput(fl_display, cw[i], ColormapChangeMask);
592
	XGetWindowAttributes(fl_display, cw[i], &attr);
593
	window_Colormaps[i] = attr.colormap;
594
      }
595
    }
596
  } else {
597
    colormapWinCount = 0;
598
  }
599
}
600
601
void Frame::installColormap() const {
602
  for (int i = colormapWinCount; i--;)
603
    if (colormapWindows[i] != window_ && window_Colormaps[i])
604
      XInstallColormap(fl_display, window_Colormaps[i]);
605
  if (colormap)
606
    XInstallColormap(fl_display, colormap);
607
}
608
609
////////////////////////////////////////////////////////////////
610
611
// figure out transient_for(), based on the windows that exist, the
612
// transient_for and group attributes, etc:
613
void Frame::fix_transient_for() {
614
  Frame* p = 0;
615
  if (transient_for_xid && !flag(ICONIZE)) {
616
    for (Frame* f = first; f; f = f->next) {
617
      if (f != this && f->window_ == transient_for_xid) {p = f; break;}
618
    }
619
    // loops are illegal:
620
    for (Frame* q = p; q; q = q->transient_for_) if (q == this) {p = 0; break;}
621
  }
622
  transient_for_ = p;
623
}
624
625
int Frame::is_transient_for(const Frame* f) const {
626
  if (f)
627
    for (Frame* p = transient_for(); p; p = p->transient_for())
628
      if (p == f) return 1;
629
  return 0;
630
}
631
632
// When a program maps or raises a window, this is called.  It guesses
633
// if this window is in fact a modal window for the currently active
634
// window and if so transfers the active state to this:
635
// This also activates new main windows automatically
636
int Frame::activate_if_transient() {
637
  if (!Fl::pushed())
638
    if (!transient_for() || is_transient_for(active_)) return activate(1);
639
  return 0;
640
}
641
642
////////////////////////////////////////////////////////////////
643
644
int Frame::activate(int warp) {
645
  // see if a modal & newer window is up:
646
  for (Frame* c = first; c && c != this; c = c->next)
647
    if (c->flag(MODAL) && c->transient_for() == this)
648
      if (c->activate(warp)) return 1;
649
  // ignore invisible windows:
650
  if (state() != NORMAL || w() <= dwidth) return 0;
651
  // always put in the colormap:
652
  installColormap();
653
  // move the pointer if desired:
654
  // (note that moving the pointer is pretty much required for point-to-type
655
  // unless you know the pointer is already in the window):
656
  if (!warp || Fl::event_state() & (FL_BUTTON1|FL_BUTTON2|FL_BUTTON3)) {
657
    ;
658
  } else if (warp==2) {
659
    // warp to point at title:
660
    XWarpPointer(fl_display, None, fl_xid(this), 0,0,0,0, left/2+1,
661
                 min(label_y+label_w/2+1,h()/2));
662
  } else {
663
    warp_pointer();
664
  }
665
  // skip windows that don't want focus:
666
  if (flag(NO_FOCUS)) return 0;
667
  // set this even if we think it already has it, this seems to fix
668
  // bugs with Motif popups:
669
  XSetInputFocus(fl_display, window_, RevertToPointerRoot, fl_event_time);
670
  if (active_ != this) {
671
    if (active_) active_->deactivate();
672
    active_ = this;
673
#if defined(ACTIVE_COLOR)
674
    XSetWindowAttributes a;
675
    a.background_pixel = fl_xpixel(FL_SELECTION_COLOR);
676
    XChangeWindowAttributes(fl_display, fl_xid(this), CWBackPixel, &a);
677
    labelcolor(contrast(FL_BLACK, FL_SELECTION_COLOR));
678
    XClearArea(fl_display, fl_xid(this), 2, 2, w()-4, h()-4, 1);
679
#else
680
#if defined(SHOW_CLOCK)
681
    redraw();
682
#endif
683
#endif
684
    if (flag(TAKE_FOCUS_PROTOCOL))
685
      sendMessage(wm_protocols, wm_take_focus);
686
  }
687
  return 1;
688
}
689
690
// this private function should only be called by constructor and if
691
// the window is active():
692
void Frame::deactivate() {
693
#if defined(ACTIVE_COLOR)
694
    XSetWindowAttributes a;
695
    a.background_pixel = fl_xpixel(FL_GRAY);
696
    XChangeWindowAttributes(fl_display, fl_xid(this), CWBackPixel, &a);
697
    labelcolor(FL_BLACK);
698
    XClearArea(fl_display, fl_xid(this), 2, 2, w()-4, h()-4, 1);
699
#else
700
#if defined(SHOW_CLOCK)
701
    redraw();
702
#endif
703
#endif
704
}
705
706
#if CLICK_RAISES || CLICK_TO_TYPE
707
// After the XGrabButton, the main loop will get the mouse clicks, and
708
// it will call here when it gets them:
709
void click_raise(Frame* f) {
710
  f->activate();
711
#if CLICK_RAISES
712
  if (fl_xevent->xbutton.button <= 1) f->raise();
713
#endif
714
  XAllowEvents(fl_display, ReplayPointer, CurrentTime);
715
}
716
#endif
717
718
// get rid of the focus by giving it to somebody, if possible:
719
void Frame::throw_focus(int destructor) {
720
  if (!active()) return;
721
  if (!destructor) deactivate();
722
  active_ = 0;
723
  if (revert_to && revert_to->activate()) return;
724
  for (Frame* f = first; f; f = f->next)
725
    if (f != this && f->activate()) return;
726
}
727
728
////////////////////////////////////////////////////////////////
729
730
// change the state of the window (this is a private function and
731
// it ignores the transient-for or desktop information):
732
733
void Frame::state(short newstate) {
734
  short oldstate = state();
735
  if (newstate == oldstate) return;
736
  state_ = newstate;
737
  switch (newstate) {
738
  case UNMAPPED:
739
    throw_focus();
740
    set_state_flag(IGNORE_UNMAP);
741
    XUnmapWindow(fl_display, fl_xid(this));
742
    XUnmapWindow(fl_display, window_);
743
    XRemoveFromSaveSet(fl_display, window_);
744
    break;
745
  case NORMAL:
746
    if (oldstate == UNMAPPED) XAddToSaveSet(fl_display, window_);
747
    if (w() > dwidth) XMapWindow(fl_display, window_);
748
    XMapWindow(fl_display, fl_xid(this));
749
    clear_state_flag(IGNORE_UNMAP);
750
    break;
751
  default:
752
    if (oldstate == UNMAPPED) {
753
      XAddToSaveSet(fl_display, window_);
754
    } else if (oldstate == NORMAL) {
755
      throw_focus();
756
      set_state_flag(IGNORE_UNMAP);
757
      XUnmapWindow(fl_display, fl_xid(this));
758
      XUnmapWindow(fl_display, window_);
759
    } else {
760
      return; // don't setStateProperty IconicState multiple times
761
    }
762
    break;
763
  }
764
  setStateProperty();
765
}
766
767
void Frame::setStateProperty() const {
768
  long data[2];
769
  switch (state()) {
770
  case UNMAPPED :
771
    data[0] = WithdrawnState; break;
772
  case NORMAL :
773
  case OTHER_DESKTOP :
774
    data[0] = NormalState; break;
775
  default :
776
    data[0] = IconicState; break;
777
  }
778
  data[1] = (long)None;
779
  XChangeProperty(fl_display, window_, wm_state, wm_state,
780
		  32, PropModeReplace, (unsigned char *)data, 2);
781
}
782
783
////////////////////////////////////////////////////////////////
784
// Public state modifiers that move all transient_for(this) children
785
// with the frame and do the desktops right:
786
787
void Frame::raise() {
788
  Frame* newtop = 0;
789
  Frame* previous = 0;
790
  int previous_state = state_;
791
  Frame** p;
792
  // Find all the transient-for windows and this one, and raise them,
793
  // preserving stacking order:
794
  for (p = &first; *p;) {
795
    Frame* f = *p;
796
    if (f == this || f->is_transient_for(this) && f->state() != UNMAPPED) {
797
      *p = f->next; // remove it from list
798
      if (previous) {
799
	XWindowChanges w;
800
	w.sibling = fl_xid(previous);
801
	w.stack_mode = Below;
802
	XConfigureWindow(fl_display, fl_xid(f), CWSibling|CWStackMode, &w);
803
	previous->next = f;
804
      } else {
805
	XRaiseWindow(fl_display, fl_xid(f));
806
	newtop = f;
807
      }
808
#if DESKTOPS
809
      if (f->desktop_ && f->desktop_ != Desktop::current())
810
       f->state(OTHER_DESKTOP);
811
      else
812
#endif
813
	f->state(NORMAL);
814
      previous = f;
815
    } else {
816
      p = &((*p)->next);
817
    }
818
  }
819
  previous->next = first;
820
  first = newtop;
821
#if DESKTOPS
822
  if (!transient_for() && desktop_ && desktop_ != Desktop::current()) {
823
    // for main windows we also must move to the current desktop
824
    desktop(Desktop::current());
825
  }
826
#endif
827
  if (previous_state != NORMAL && newtop->state_==NORMAL)
828
    newtop->activate_if_transient();
829
}
830
831
void Frame::lower() {
832
  Frame* t = transient_for(); if (t) t->lower();
833
  if (!next || next == t) return; // already on bottom
834
  // pull it out of the list:
835
  Frame** p = &first;
836
  for (; *p != this; p = &((*p)->next)) {}
837
  *p = next;
838
  // find end of list:
839
  Frame* f = next; while (f->next != t) f = f->next;
840
  // insert it after that:
841
  f->next = this; next = t;
842
  // and move the X window:
843
  XWindowChanges w;
844
  w.sibling = fl_xid(f);
845
  w.stack_mode = Below;
846
  XConfigureWindow(fl_display, fl_xid(this), CWSibling|CWStackMode, &w);
847
}
848
849
void Frame::iconize() {
850
  for (Frame* c = first; c; c = c->next) {
851
    if (c == this || c->is_transient_for(this) && c->state() != UNMAPPED)
852
      c->state(ICONIC);
853
  }
854
}
855
856
#if DESKTOPS
857
void Frame::desktop(Desktop* d) {
858
  if (d == desktop_) return;
859
  // Put all the relatives onto the desktop as well:
860
  for (Frame* c = first; c; c = c->next) {
861
    if (c == this || c->is_transient_for(this)) {
862
      c->desktop_ = d;
863
      c->setProperty(_win_state, XA_CARDINAL, !d);
864
      c->setProperty(kwm_win_sticky, kwm_win_sticky, !d);
865
      if (d) {
866
	c->setProperty(kwm_win_desktop, kwm_win_desktop, d->number());
867
	c->setProperty(_win_workspace, XA_CARDINAL, d->number()-1);
868
      }
869
      if (!d || d == Desktop::current()) {
870
	if (c->state() == OTHER_DESKTOP) c->state(NORMAL);
871
      } else {
872
	if (c->state() == NORMAL) c->state(OTHER_DESKTOP);
873
      }
874
    }
875
  }
876
}
877
#endif
878
879
////////////////////////////////////////////////////////////////
880
881
// Resize and/or move the window.  The size is given for the frame, not
882
// the contents.  This also sets the buttons on/off as needed:
883
884
void Frame::set_size(int nx, int ny, int nw, int nh, int warp) {
885
  int dx = nx-x(); x(nx);
886
  int dy = ny-y(); y(ny);
887
  if (!dx && !dy && nw == w() && nh == h()) return;
888
  int unmap = 0;
889
  int remap = 0;
890
  // use XClearArea to cause correct damage events:
891
  if (nw != w()) {
892
    max_w_button.value(nw-dwidth == maximize_width());
893
    min_w_button.value(nw <= dwidth);
894
    if (nw <= dwidth) {
895
      unmap = 1;
896
    } else {
897
      if (w() <= dwidth) remap = 1;
898
    }
899
    int minw = (nw < w()) ? nw : w();
900
    XClearArea(fl_display, fl_xid(this), minw-RIGHT, 0, RIGHT, nh, 1);
901
    w(nw);
902
  }
903
  if (nh != h()) {
904
    max_h_button.value(nh-dheight == maximize_height());
905
    int minh = (nh < h()) ? nh : h();
906
    XClearArea(fl_display, fl_xid(this), 0, minh-BOTTOM, w(), BOTTOM, 1);
907
    // see if label or close box moved, erase the minimum area:
908
    int old_label_y = label_y;
909
    int old_label_h = label_h;
910
    h(nh); show_hide_buttons();
911
#ifdef SHOW_CLOCK
912
    int t = label_y + 3; // we have to clear the entire label area
913
#else
914
    int t = nh;
915
    if (label_y != old_label_y) {
916
      t = label_y; if (old_label_y < t) t = old_label_y;
917
    } else if (label_y+label_h != old_label_y+old_label_h) {
918
      t = label_y+label_h;
919
      if (old_label_y+old_label_h < t) t = old_label_y+old_label_h;
920
    }
921
#endif
922
    if (t < nh && left>LEFT)
923
      XClearArea(fl_display,fl_xid(this), 1, t, left-1, nh-t, 1);
924
  }
925
  // for maximize button move the cursor first if window gets smaller
926
  if (warp == 1 && (dx || dy))
927
    XWarpPointer(fl_display, None,None,0,0,0,0, dx, dy);
928
  // for configure request, move the cursor first
929
  if (warp == 2 && active() && !Fl::pushed()) warp_pointer();
930
  XMoveResizeWindow(fl_display, fl_xid(this), nx, ny, nw, nh);
931
  if (nw <= dwidth) {
932
    if (unmap) {
933
      set_state_flag(IGNORE_UNMAP);
934
      XUnmapWindow(fl_display, window_);
935
    }
936
  } else {
937
    XResizeWindow(fl_display, window_, nw-dwidth, nh-dheight);
938
    if (remap) {
939
      XMapWindow(fl_display, window_);
940
#if CLICK_TO_TYPE
941
      if (active()) activate();
942
#else
943
      activate();
944
#endif
945
    }
946
  }
947
  // for maximize button move the cursor second if window gets bigger:
948
  if (warp == 3 && (dx || dy))
949
    XWarpPointer(fl_display, None,None,0,0,0,0, dx, dy);
950
  if (nw > dwidth) sendConfigureNotify();
951
  XSync(fl_display,0);
952
}
953
954
void Frame::sendConfigureNotify() const {
955
  XConfigureEvent ce;
956
  ce.type   = ConfigureNotify;
957
  ce.event  = window_;
958
  ce.window = window_;
959
  ce.x = x()+left-app_border_width;
960
  ce.y = y()+top-app_border_width;
961
  ce.width  = w()-dwidth;
962
  ce.height = h()-dheight;
963
  ce.border_width = app_border_width;
964
  ce.above = None;
965
  ce.override_redirect = 0;
966
  XSendEvent(fl_display, window_, False, StructureNotifyMask, (XEvent*)&ce);
967
}
968
969
// move the pointer inside the window:
970
void Frame::warp_pointer() {
971
  int X,Y; Fl::get_mouse(X,Y);
972
  X -= x();
973
  int Xi = X;
974
  if (X <= 0) X = left/2+1;
975
  if (X >= w()) X = w()-(RIGHT/2+1);
976
  Y -= y();
977
  int Yi = Y;
978
  if (Y < 0) Y = TOP/2+1;
979
  if (Y >= h()) Y = h()-(BOTTOM/2+1);
980
  if (X != Xi || Y != Yi)
981
    XWarpPointer(fl_display, None, fl_xid(this), 0,0,0,0, X, Y);
982
}
983
984
// Resize the frame to match the current border type:
985
void Frame::updateBorder() {
986
  int nx = x()+left;
987
  int ny = y()+top;
988
  int nw = w()-dwidth;
989
  int nh = h()-dheight;
990
  if (flag(NO_BORDER)) {
991
    left = top = dwidth = dheight = 0;
992
  } else {
993
    left = flag(THIN_BORDER) ? LEFT : LEFT+TITLE_WIDTH;
994
    dwidth = left+RIGHT;
995
    top = TOP;
996
    dheight = TOP+BOTTOM;
997
  }
998
  nx -= left;
999
  ny -= top;
1000
  nw += dwidth;
1001
  nh += dheight;
1002
  if (x()==nx && y()==ny && w()==nw && h()==nh) return;
1003
  x(nx); y(ny); w(nw); h(nh);
1004
  if (!shown()) return; // this is so constructor can call this
1005
  // try to make the contents not move while the border changes around it:
1006
  XSetWindowAttributes a;
1007
  a.win_gravity = StaticGravity;
1008
  XChangeWindowAttributes(fl_display, window_, CWWinGravity, &a);
1009
  XMoveResizeWindow(fl_display, fl_xid(this), nx, ny, nw, nh);
1010
  a.win_gravity = NorthWestGravity;
1011
  XChangeWindowAttributes(fl_display, window_, CWWinGravity, &a);
1012
  // fix the window position if the X server didn't do the gravity:
1013
  XMoveWindow(fl_display, window_, left, top);
1014
}
1015
1016
// position and show the buttons according to current border, size,
1017
// and other state information:
1018
void Frame::show_hide_buttons() {
1019
  if (flag(THIN_BORDER|NO_BORDER)) {
1020
    iconize_button.hide();
1021
    max_w_button.hide();
1022
    min_w_button.hide();
1023
    max_h_button.hide();
1024
    close_button.hide();
1025
    return;
1026
  }
1027
  int by = BUTTON_TOP;
1028
  if (transient_for()) {
1029
    iconize_button.hide();
1030
    min_w_button.hide();
1031
  } else {
1032
    iconize_button.position(BUTTON_LEFT,by);
1033
    iconize_button.show();
1034
    by += BUTTON_H;
1035
#if MINIMIZE_BOX
1036
    min_w_button.position(BUTTON_LEFT,by);
1037
    min_w_button.show();
1038
    by += BUTTON_H;
1039
#else
1040
    min_w_button.hide();
1041
#endif
1042
  }
1043
  if (min_h == max_h || flag(KEEP_ASPECT|NO_RESIZE) ||
1044
      !max_h_button.value() && by+label_w+2*BUTTON_H > h()-BUTTON_BOTTOM) {
1045
    max_h_button.hide();
1046
  } else {
1047
    max_h_button.position(BUTTON_LEFT,by);
1048
    max_h_button.show();
1049
    by += BUTTON_H;
1050
  }
1051
  if (min_w == max_w || flag(KEEP_ASPECT|NO_RESIZE) ||
1052
      !max_w_button.value() && by+label_w+2*BUTTON_H > h()-BUTTON_BOTTOM) {
1053
    max_w_button.hide();
1054
  } else {
1055
    max_w_button.position(BUTTON_LEFT,by);
1056
    max_w_button.show();
1057
    by += BUTTON_H;
1058
  }
1059
  if (label_y != by && shown())
1060
    XClearArea(fl_display,fl_xid(this), 1, by, left-1, label_h+label_y-by, 1);
1061
  label_y = by;
1062
#if CLOSE_BOX
1063
  if (by+BUTTON_H > h()-BUTTON_BOTTOM || flag(NO_CLOSE)) {
1064
#endif
1065
    label_h = h()-BOTTOM-by;
1066
    close_button.hide();
1067
#if CLOSE_BOX
1068
  } else {
1069
    close_button.show();
1070
    close_button.position(BUTTON_LEFT,h()-(BUTTON_BOTTOM+BUTTON_H));
1071
    label_h = close_button.y()-by;
1072
  }
1073
#endif
1074
}
1075
1076
// make sure fltk does not try to set the window size:
1077
void Frame::resize(int, int, int, int) {}
1078
1079
////////////////////////////////////////////////////////////////
1080
1081
void Frame::close() {
1082
  if (flag(DELETE_WINDOW_PROTOCOL))
1083
    sendMessage(wm_protocols, wm_delete_window);
1084
  else if (flag(QUIT_PROTOCOL))
1085
    sendMessage(wm_protocols, _wm_quit_app);
1086
  else
1087
    kill();
1088
}
1089
1090
void Frame::kill() {
1091
  XKillClient(fl_display, window_);
1092
}
1093
1094
// this is called when window manager exits:
1095
void Frame::save_protocol() {
1096
  Frame* f;
1097
  for (f = first; f; f = f->next) if (f->flag(SAVE_PROTOCOL)) {
1098
    f->set_state_flag(SAVE_PROTOCOL_WAIT);
1099
    f->sendMessage(wm_protocols, wm_save_yourself);
1100
  }
1101
  double t = 10.0; // number of seconds to wait before giving up
1102
  while (t > 0.0) {
1103
    for (f = first; ; f = f->next) {
1104
      if (!f) return;
1105
      if (f->flag(SAVE_PROTOCOL) && f->state_flags_&SAVE_PROTOCOL_WAIT) break;
1106
    }
1107
    t = Fl::wait(t);
1108
  }
1109
}
1110
1111
////////////////////////////////////////////////////////////////
1112
// Drawing code:
1113
1114
void Frame::draw() {
1115
  if (flag(NO_BORDER)) return;
1116
  if (!flag(THIN_BORDER)) Fl_Window::draw();
1117
  if (damage() != FL_DAMAGE_CHILD) {
1118
#if ACTIVE_COLOR
1119
    fl_frame2(active() ? "AAAAJJWW" : "AAAAJJWWNNTT",0,0,w(),h());
1120
    if (active()) {
1121
      fl_color(FL_GRAY_RAMP+('N'-'A'));
1122
      fl_xyline(2, h()-3, w()-3, 2);
1123
    }
1124
#else
1125
    fl_frame("AAAAWWJJTTNN",0,0,w(),h());
1126
#endif
1127
    if (!flag(THIN_BORDER) && label_h > 3) {
1128
#ifdef SHOW_CLOCK
1129
      if (active()) {
1130
	  int clkw = int(fl_width(clock_buf));
1131
	  if (clock_alarm_on) {
1132
	      fl_font(TITLE_FONT_SLOT, TITLE_FONT_SIZE);
1133
	      fl_rectf(LEFT-1, label_y + label_h - 3 - clkw, TITLE_WIDTH, clkw,
1134
		       (ALARM_BG_COLOR>>16)&0xff,
1135
		       (ALARM_BG_COLOR>>8)&0xff,
1136
		       ALARM_BG_COLOR&0xff);
1137
	      fl_color((ALARM_FG_COLOR>>16)&0xff,
1138
		       (ALARM_FG_COLOR>>8)&0xff,
1139
		       ALARM_FG_COLOR&0xff);
1140
	  } else
1141
	      fl_font(MENU_FONT_SLOT, TITLE_FONT_SIZE);
1142
	  // This might overlay the label if the label is long enough
1143
	  // and the window height is short enough.  For now, we'll
1144
	  // assume this is not enough of a problem to be concerned
1145
	  // about.
1146
	  draw_rotated90(clock_buf, 1, label_y+3, left-1, label_h-6,
1147
			 Fl_Align(FL_ALIGN_BOTTOM|FL_ALIGN_CLIP));
1148
      } else
1149
	  // Only show the clock on the active frame.
1150
	  XClearArea(fl_display, fl_xid(this), 1, label_y+3,
1151
		     left-1, label_h-3, 0);
1152
#endif      
1153
      fl_color(labelcolor());
1154
      fl_font(TITLE_FONT_SLOT, TITLE_FONT_SIZE);
1155
      draw_rotated90(label(), 1, label_y+3, left-1, label_h-3,
1156
		     Fl_Align(FL_ALIGN_TOP|FL_ALIGN_CLIP));
1157
    }
1158
  }
1159
}
1160
1161
#ifdef SHOW_CLOCK
1162
void Frame::redraw_clock() {
1163
    double clkw = fl_width(clock_buf);
1164
    XClearArea(fl_display, fl_xid(this),
1165
	       1, label_y+label_h-3-(int)clkw,
1166
	       left-1, (int)clkw, 1);
1167
}
1168
#endif
1169
1170
void FrameButton::draw() {
1171
  Fl_Widget::draw_box(value() ? FL_DOWN_FRAME : FL_UP_FRAME, FL_GRAY);
1172
  fl_color(parent()->labelcolor());
1173
  switch (label()[0]) {
1174
  case 'W':
1175
#if MINIMIZE_ARROW
1176
    fl_line (x()+2,y()+(h())/2,x()+w()-4,y()+h()/2);
1177
    fl_line (x()+2,y()+(h())/2,x()+2+4,y()+h()/2+4);
1178
    fl_line (x()+2,y()+(h())/2,x()+2+4,y()+h()/2-4);
1179
#else
1180
    fl_rect(x()+(h()-7)/2,y()+3,2,h()-6);
1181
#endif
1182
    return;
1183
  case 'w':
1184
    fl_rect(x()+2,y()+(h()-7)/2,w()-4,7);
1185
    return;
1186
  case 'h':
1187
    fl_rect(x()+(h()-7)/2,y()+2,7,h()-4);
1188
    return;
1189
  case 'X':
1190
#if CLOSE_X
1191
    fl_line(x()+2,y()+3,x()+w()-5,y()+h()-4);
1192
    fl_line(x()+3,y()+3,x()+w()-4,y()+h()-4);
1193
    fl_line(x()+2,y()+h()-4,x()+w()-5,y()+3);
1194
    fl_line(x()+3,y()+h()-4,x()+w()-4,y()+3);
1195
#endif
1196
#if CLOSE_HITTITE_LIGHTNING
1197
    fl_arc(x()+3,y()+3,w()-6,h()-6,0,360);
1198
    fl_line(x()+7,y()+3, x()+7,y()+11);
1199
#endif
1200
    return;
1201
  case 'i':
1202
#if ICONIZE_BOX
1203
    fl_rect(x()+w()/2-1,y()+h()/2-1,3,3);
1204
#endif
1205
    return;
1206
  }
1207
}
1208
1209
////////////////////////////////////////////////////////////////
1210
// User interface code:
1211
1212
// this is called when user clicks the buttons:
1213
void Frame::button_cb(Fl_Button* b) {
1214
  switch (b->label()[0]) {
1215
  case 'W':	// minimize button
1216
    if (b->value()) {
1217
      if (!max_w_button.value()) {
1218
        restore_x = x()+left;
1219
	restore_y = y()+top;
1220
#if MINIMIZE_HEIGHT
1221
	restore_w=w()-dwidth;
1222
	restore_h = h()-dwidth;
1223
#endif
1224
      }
1225
#if MINIMIZE_HEIGHT
1226
      set_size(x(), y(), dwidth-1,
1227
	       min(h(),min(350,label_w+3*BUTTON_H+BUTTON_TOP+BUTTON_BOTTOM)),
1228
	       1);
1229
#else
1230
      set_size(x(), y(), dwidth-1, h(), 1);
1231
#endif
1232
    } else {
1233
#if MINIMIZE_HEIGHT
1234
      set_size(x(), y(), restore_w+dwidth, restore_h+dwidth, 1);
1235
#else
1236
      set_size(x(), y(), restore_w+dwidth, h(), 1);
1237
#endif
1238
    }
1239
    show_hide_buttons();
1240
    break;
1241
  case 'w':	// max-width button
1242
    if (b->value()) {
1243
      if (!min_w_button.value()) {restore_x=x()+left; restore_w=w()-dwidth;}
1244
      int W = maximize_width()+dwidth;
1245
      int X = force_x_onscreen(x() + (w()-W)/2, W);
1246
      set_size(X, y(), W, h(), 3);
1247
    } else {
1248
      set_size(restore_x-left, y(), restore_w+dwidth, h(), 1);
1249
    }
1250
    show_hide_buttons();
1251
    break;
1252
  case 'h':	// max-height button
1253
    if (b->value()) {
1254
      restore_y = y()+top;
1255
      restore_h = h()-dwidth;
1256
      int H = maximize_height()+dheight;
1257
      int Y = force_y_onscreen(y() + (h()-H)/2, H);
1258
      set_size(x(), Y, w(), H, 3);
1259
    } else {
1260
      set_size(x(), restore_y-top, w(), restore_h+dwidth, 1);
1261
    }
1262
    break;
1263
  case 'X':
1264
    close();
1265
    break;
1266
  default: // iconize button
1267
    iconize();
1268
    break;
1269
  }
1270
}
1271
1272
// static callback for fltk:
1273
void Frame::button_cb_static(Fl_Widget* w, void*) {
1274
  ((Frame*)(w->parent()))->button_cb((Fl_Button*)w);
1275
}
1276
1277
// This method figures out what way the mouse will resize the window.
1278
// It is used to set the cursor and to actually control what you grab.
1279
// If the window cannot be resized in some direction this should not
1280
// return that direction.
1281
int Frame::mouse_location() {
1282
  int x = Fl::event_x();
1283
  int y = Fl::event_y();
1284
  int r = 0;
1285
  if (flag(NO_RESIZE)) return 0;
1286
  if (min_h != max_h) {
1287
    if (y < RESIZE_EDGE) r |= FL_ALIGN_TOP;
1288
    else if (y >= h()-RESIZE_EDGE) r |= FL_ALIGN_BOTTOM;
1289
  }
1290
  if (min_w != max_w) {
1291
#if RESIZE_LEFT
1292
    if (x < RESIZE_EDGE) r |= FL_ALIGN_LEFT;
1293
#else
1294
    if (x < RESIZE_EDGE && r) r |= FL_ALIGN_LEFT;
1295
#endif
1296
    else if (x >= w()-RESIZE_EDGE) r |= FL_ALIGN_RIGHT;
1297
  }
1298
  return r;
1299
}
1300
1301
// set the cursor correctly for a return value from mouse_location():
1302
void Frame::set_cursor(int r) {
1303
  Fl_Cursor c = r ? FL_CURSOR_ARROW : FL_CURSOR_MOVE;
1304
  switch (r) {
1305
  case FL_ALIGN_TOP:
1306
  case FL_ALIGN_BOTTOM:
1307
    c = FL_CURSOR_NS;
1308
    break;
1309
  case FL_ALIGN_LEFT:
1310
  case FL_ALIGN_RIGHT:
1311
    c = FL_CURSOR_WE;
1312
    break;
1313
  case FL_ALIGN_LEFT|FL_ALIGN_TOP:
1314
  case FL_ALIGN_RIGHT|FL_ALIGN_BOTTOM:
1315
    c = FL_CURSOR_NWSE;
1316
    break;
1317
  case FL_ALIGN_LEFT|FL_ALIGN_BOTTOM:
1318
  case FL_ALIGN_RIGHT|FL_ALIGN_TOP:
1319
    c = FL_CURSOR_NESW;
1320
    break;
1321
  }
1322
  static Frame* previous_frame;
1323
  static Fl_Cursor previous_cursor;
1324
  if (this != previous_frame || c != previous_cursor) {
1325
    previous_frame = this;
1326
    previous_cursor = c;
1327
    cursor(c, CURSOR_FG_SLOT, CURSOR_BG_SLOT);
1328
  }
1329
}
1330
1331
#ifdef AUTO_RAISE
1332
// timeout callback to cause autoraise:
1333
void auto_raise(void*) {
1334
  if (Frame::activeFrame() && !Fl::grab() && !Fl::pushed())
1335
    Frame::activeFrame()->raise();
1336
}
1337
#endif
1338
1339
extern void ShowMenu();
1340
1341
// If cursor is in the contents of a window this is set to that window.
1342
// This is only used to force the cursor to an arrow even though X keeps
1343
// sending mysterious erroneous move events:
1344
static Frame* cursor_inside = 0;
1345
1346
// Handle an fltk event.
1347
int Frame::handle(int e) {
1348
  static int what, dx, dy, ix, iy, iw, ih;
1349
  // see if child widget handles event:
1350
  if (Fl_Window::handle(e) && e != FL_ENTER && e != FL_MOVE) {
1351
    if (e == FL_PUSH) set_cursor(-1);
1352
    return 1;
1353
  }
1354
  switch (e) {
1355
1356
  case FL_SHOW:
1357
  case FL_HIDE:
1358
    return 0; // prevent fltk from messing things up
1359
1360
  case FL_ENTER:
1361
#if !CLICK_TO_TYPE
1362
    if (Fl::pushed() || Fl::grab()) return 1;
1363
    if (activate()) {
1364
#ifdef AUTO_RAISE
1365
      Fl::remove_timeout(auto_raise);
1366
      Fl::add_timeout(AUTO_RAISE, auto_raise);
1367
#endif
1368
    }
1369
#endif
1370
    goto GET_CROSSINGS;
1371
1372
  case FL_LEAVE:
1373
#if !CLICK_TO_TYPE && !STICKY_FOCUS
1374
    if (active()) {
1375
      deactivate();
1376
      XSetInputFocus(fl_display, PointerRoot, RevertToPointerRoot,
1377
		     fl_event_time);
1378
      active_ = 0;
1379
    }
1380
#endif
1381
    goto GET_CROSSINGS;
1382
1383
  case 0:
1384
  GET_CROSSINGS:
1385
    // set cursor_inside to true when the mouse is inside a window
1386
    // set it false when mouse is on a frame or outside a window.
1387
    // fltk mangles the X enter/leave events, we need the original ones:
1388
1389
    switch (fl_xevent->type) {
1390
    case EnterNotify:
1391
1392
      // see if cursor skipped over frame and directly to interior:
1393
      if (fl_xevent->xcrossing.detail == NotifyVirtual ||
1394
	  fl_xevent->xcrossing.detail == NotifyNonlinearVirtual)
1395
	cursor_inside = this;
1396
1397
      else {
1398
	// cursor is now pointing at frame:
1399
	cursor_inside = 0;
1400
      }
1401
1402
      // fall through to FL_MOVE:
1403
      break;
1404
1405
    case LeaveNotify:
1406
      if (fl_xevent->xcrossing.detail == NotifyInferior) {
1407
	// cursor moved from frame to interior
1408
	cursor_inside = this;
1409
	set_cursor(-1);
1410
	return 1;
1411
      }
1412
      return 1;
1413
1414
    default:
1415
      return 0; // other X event we don't understand
1416
    }
1417
1418
  case FL_MOVE:
1419
    if (Fl::belowmouse() != this || cursor_inside == this)
1420
      set_cursor(-1);
1421
    else
1422
      set_cursor(mouse_location());
1423
    return 1;
1424
1425
  case FL_PUSH:
1426
    if (Fl::event_button() > 2) {
1427
      set_cursor(-1);
1428
      ShowMenu();
1429
      return 1;
1430
    }
1431
    ix = x(); iy = y(); iw = w(); ih = h();
1432
    if (!max_w_button.value() && !min_w_button.value()) {
1433
      restore_x = ix+left; restore_w = iw-dwidth;
1434
    }
1435
#if MINIMIZE_HEIGHT
1436
    if (!min_w_button.value())
1437
#endif
1438
    if (!max_h_button.value()) {
1439
      restore_y = iy+top; restore_h = ih-dwidth;
1440
    }
1441
    what = mouse_location();
1442
    if (Fl::event_button() > 1) what = 0; // middle button does drag
1443
    dx = Fl::event_x_root()-ix;
1444
    if (what & FL_ALIGN_RIGHT) dx -= iw;
1445
    dy = Fl::event_y_root()-iy;
1446
    if (what & FL_ALIGN_BOTTOM) dy -= ih;
1447
    set_cursor(what);
1448
    return 1;
1449
  case FL_DRAG:
1450
    if (Fl::event_is_click()) return 1; // don't drag yet
1451
  case FL_RELEASE:
1452
    if (Fl::event_is_click()) {
1453
      if (Fl::grab()) return 1;
1454
#if CLICK_TO_TYPE
1455
      if (activate()) {
1456
	if (Fl::event_button() <= 1) raise();
1457
	return 1;
1458
      }
1459
#endif
1460
      if (Fl::event_button() > 1) lower(); else raise();
1461
    } else if (!what) {
1462
      int nx = Fl::event_x_root()-dx;
1463
      int W = Root->x()+Root->w();
1464
      if (nx+iw > W && nx+iw < W+SCREEN_SNAP) {
1465
	int t = W+1-iw;
1466
	if (iw >= Root->w() || x() > t || nx+iw >= W+EDGE_SNAP)
1467
	  t = W+(dwidth-left)-iw;
1468
	if (t >= x() && t < nx) nx = t;
1469
      }
1470
      int X = Root->x();
1471
      if (nx < X && nx > X-SCREEN_SNAP) {
1472
	int t = X-1;
1473
	if (iw >= Root->w() || x() < t || nx <= X-EDGE_SNAP) t = X-BUTTON_LEFT;
1474
	if (t <= x() && t > nx) nx = t;
1475
      }
1476
      int ny = Fl::event_y_root()-dy;
1477
      int H = Root->y()+Root->h();
1478
      if (ny+ih > H && ny+ih < H+SCREEN_SNAP) {
1479
	int t = H+1-ih;
1480
	if (ih >= Root->h() || y() > t || ny+ih >= H+EDGE_SNAP)
1481
	  t = H+(dheight-top)-ih;
1482
	if (t >= y() && t < ny) ny = t;
1483
      }
1484
      int Y = Root->y();
1485
      if (ny < Y && ny > Y-SCREEN_SNAP) {
1486
	int t = Y-1;
1487
	if (ih >= H || y() < t || ny <= Y-EDGE_SNAP) t = Y-top;
1488
	if (t <= y() && t > ny) ny = t;
1489
      }
1490
      set_size(nx, ny, iw, ih);
1491
    } else {
1492
      int nx = ix;
1493
      int ny = iy;
1494
      int nw = iw;
1495
      int nh = ih;
1496
      if (what & FL_ALIGN_RIGHT)
1497
	nw = Fl::event_x_root()-dx-nx;
1498
      else if (what & FL_ALIGN_LEFT)
1499
	nw = ix+iw-(Fl::event_x_root()-dx);
1500
      else {nx = x(); nw = w();}
1501
      if (what & FL_ALIGN_BOTTOM)
1502
	nh = Fl::event_y_root()-dy-ny;
1503
      else if (what & FL_ALIGN_TOP)
1504
	nh = iy+ih-(Fl::event_y_root()-dy);
1505
      else {ny = y(); nh = h();}
1506
      if (flag(KEEP_ASPECT)) {
1507
	if (nw-dwidth > nh-dwidth
1508
	    && (what&(FL_ALIGN_LEFT|FL_ALIGN_RIGHT))
1509
	    || !(what&(FL_ALIGN_TOP|FL_ALIGN_BOTTOM)))
1510
	  nh = nw-dwidth+dheight;
1511
	else
1512
	  nw = nh-dheight+dwidth;
1513
      }
1514
      int MINW = min_w+dwidth;
1515
      if (nw <= dwidth && dwidth > TITLE_WIDTH) {
1516
	nw = dwidth-1;
1517
#if MINIMIZE_HEIGHT
1518
	restore_h = nh;
1519
#endif
1520
      } else {
1521
	if (inc_w > 1) nw = ((nw-MINW+inc_w/2)/inc_w)*inc_w+MINW;
1522
	if (nw < MINW) nw = MINW;
1523
	else if (max_w && nw > max_w+dwidth) nw = max_w+dwidth;
1524
      }
1525
      int MINH = min_h+dheight;
1526
      const int MINH_B = BUTTON_H+BUTTON_TOP+BUTTON_BOTTOM;
1527
      if (MINH_B > MINH) MINH = MINH_B;
1528
      if (inc_h > 1) nh = ((nh-MINH+inc_h/2)/inc_h)*inc_h+MINH;
1529
      if (nh < MINH) nh = MINH;
1530
      else if (max_h && nh > max_h+dheight) nh = max_h+dheight;
1531
      if (what & FL_ALIGN_LEFT) nx = ix+iw-nw;
1532
      if (what & FL_ALIGN_TOP) ny = iy+ih-nh;
1533
      set_size(nx,ny,nw,nh);
1534
    }
1535
    return 1;
1536
  }
1537
  return 0;
1538
}
1539
1540
// Handle events that fltk did not recognize (mostly ones directed
1541
// at the desktop):
1542
1543
int Frame::handle(const XEvent* ei) {
1544
1545
  switch (ei->type) {
1546
1547
  case ConfigureRequest: {
1548
    const XConfigureRequestEvent* e = &(ei->xconfigurerequest);
1549
    unsigned long mask = e->value_mask;
1550
    if (mask & CWBorderWidth) app_border_width = e->border_width;
1551
    // Try to detect if the application is really trying to move the
1552
    // window, or is simply echoing it's postion, possibly with some
1553
    // variation (such as echoing the parent window position), and
1554
    // dont' move it in that case:
1555
    int X = (mask & CWX && e->x != x()) ? e->x+app_border_width-left : x();
1556
    int Y = (mask & CWY && e->y != y()) ? e->y+app_border_width-top : y();
1557
    int W = (mask & CWWidth) ? e->width+dwidth : w();
1558
    int H = (mask & CWHeight) ? e->height+dheight : h();
1559
    // Generally we want to obey any application positioning of the
1560
    // window, except when it appears the app is trying to position
1561
    // the window "at the edge".
1562
    if (!(mask & CWX) || (X >= -2*left && X < 0)) X = force_x_onscreen(X,W);
1563
    if (!(mask & CWY) || (Y >= -2*top && Y < 0)) Y = force_y_onscreen(Y,H);
1564
    // Fix Rick Sayre's program that resizes it's windows bigger than the
1565
    // maximum size:
1566
    if (W > max_w+dwidth) max_w = 0;
1567
    if (H > max_h+dheight) max_h = 0;
1568
    set_size(X, Y, W, H, 2);
1569
    if (e->value_mask & CWStackMode && e->detail == Above && state()==NORMAL)
1570
      raise();
1571
    return 1;}
1572
1573
  case MapRequest: {
1574
    //const XMapRequestEvent* e = &(ei->xmaprequest);
1575
    raise();
1576
    return 1;}
1577
1578
  case UnmapNotify: {
1579
    const XUnmapEvent* e = &(ei->xunmap);
1580
    if (e->from_configure);
1581
    else if (state_flags_&IGNORE_UNMAP) clear_state_flag(IGNORE_UNMAP);
1582
    else state(UNMAPPED);
1583
    return 1;}
1584
1585
  case DestroyNotify: {
1586
    //const XDestroyWindowEvent* e = &(ei->xdestroywindow);
1587
    delete this;
1588
    return 1;}
1589
1590
  case ReparentNotify: {
1591
    const XReparentEvent* e = &(ei->xreparent);
1592
    if (e->parent==fl_xid(this)) return 1; // echo
1593
    if (e->parent==fl_xid(Root)) return 1; // app is trying to tear-off again?
1594
    delete this; // guess they are trying to paste tear-off thing back?
1595
    return 1;}
1596
1597
  case ClientMessage: {
1598
    const XClientMessageEvent* e = &(ei->xclient);
1599
    if (e->message_type == wm_change_state && e->format == 32) {
1600
      if (e->data.l[0] == NormalState) raise();
1601
      else if (e->data.l[0] == IconicState) iconize();
1602
    } else
1603
      // we may want to ignore _WIN_LAYER from xmms?
1604
      Fl::warning("flwm: unexpected XClientMessageEvent, type 0x%lx, "
1605
	      "window 0x%lx\n", e->message_type, e->window);
1606
    return 1;}
1607
1608
  case ColormapNotify: {
1609
    const XColormapEvent* e = &(ei->xcolormap);
1610
    if (e->c_new) {  // this field is called "new" in the old C++-unaware Xlib
1611
      colormap = e->colormap;
1612
      if (active()) installColormap();
1613
    }
1614
    return 1;}
1615
1616
  case PropertyNotify: {
1617
    const XPropertyEvent* e = &(ei->xproperty);
1618
    Atom a = e->atom;
1619
1620
    // case XA_WM_ICON_NAME: (do something similar to name)
1621
    if (a == XA_WM_NAME) {
1622
      getLabel(e->state == PropertyDelete);
1623
1624
    } else if (a == wm_state) {
1625
      // it's not clear if I really need to look at this.  Need to make
1626
      // sure it is not seeing the state echoed by the application by
1627
      // checking for it being different...
1628
      switch (getIntProperty(wm_state, wm_state, state())) {
1629
      case IconicState:
1630
	if (state() == NORMAL || state() == OTHER_DESKTOP) iconize(); break;
1631
      case NormalState:
1632
	if (state() != NORMAL && state() != OTHER_DESKTOP) raise(); break;
1633
      }
1634
1635
    } else if (a == wm_colormap_windows) {
1636
      getColormaps();
1637
      if (active()) installColormap();
1638
1639
    } else if (a == _motif_wm_hints) {
1640
      // some #%&%$# SGI Motif programs change this after mapping the window!
1641
      // :-( :=( :-( :=( :-( :=( :-( :=( :-( :=( :-( :=(
1642
      if (getMotifHints()) { // returns true if any flags changed
1643
	fix_transient_for();
1644
	updateBorder();
1645
	show_hide_buttons();
1646
      }
1647
1648
    } else if (a == wm_protocols) {
1649
      getProtocols();
1650
      // get Motif hints since they may do something with QUIT:
1651
      getMotifHints();
1652
1653
    } else if (a == XA_WM_NORMAL_HINTS || a == XA_WM_SIZE_HINTS) {
1654
      getSizes();
1655
      show_hide_buttons();
1656
1657
    } else if (a == XA_WM_TRANSIENT_FOR) {
1658
      XGetTransientForHint(fl_display, window_, &transient_for_xid);
1659
      fix_transient_for();
1660
      show_hide_buttons();
1661
1662
    } else if (a == XA_WM_COMMAND) {
1663
      clear_state_flag(SAVE_PROTOCOL_WAIT);
1664
1665
    }
1666
    return 1;}
1667
1668
  }
1669
  return 0;
1670
}
1671
1672
////////////////////////////////////////////////////////////////
1673
// X utility routines:
1674
1675
void* Frame::getProperty(Atom a, Atom type, int* np) const {
1676
  return ::getProperty(window_, a, type, np);
1677
}
1678
1679
void* getProperty(Window w, Atom a, Atom type, int* np) {
1680
  Atom realType;
1681
  int format;
1682
  unsigned long n, extra;
1683
  int status;
1684
  void* prop;
1685
  status = XGetWindowProperty(fl_display, w,
1686
			      a, 0L, 256L, False, type, &realType,
1687
			      &format, &n, &extra, (uchar**)&prop);
1688
  if (status != Success) return 0;
1689
  if (!prop) return 0;
1690
  if (!n) {XFree(prop); return 0;}
1691
  if (np) *np = (int)n;
1692
  return prop;
1693
}
1694
1695
int Frame::getIntProperty(Atom a, Atom type, int deflt) const {
1696
  return ::getIntProperty(window_, a, type, deflt);
1697
}
1698
1699
int getIntProperty(Window w, Atom a, Atom type, int deflt) {
1700
  void* prop = getProperty(w, a, type);
1701
  if (!prop) return deflt;
1702
  int r = int(*(long*)prop);
1703
  XFree(prop);
1704
  return r;
1705
}
1706
1707
void setProperty(Window w, Atom a, Atom type, int v) {
1708
  long prop = v;
1709
  XChangeProperty(fl_display, w, a, type, 32, PropModeReplace, (uchar*)&prop,1);
1710
}
1711
1712
void Frame::setProperty(Atom a, Atom type, int v) const {
1713
  ::setProperty(window_, a, type, v);
1714
}
1715
1716
void Frame::sendMessage(Atom a, Atom l) const {
1717
  XEvent ev;
1718
  long mask;
1719
  memset(&ev, 0, sizeof(ev));
1720
  ev.xclient.type = ClientMessage;
1721
  ev.xclient.window = window_;
1722
  ev.xclient.message_type = a;
1723
  ev.xclient.format = 32;
1724
  ev.xclient.data.l[0] = long(l);
1725
  ev.xclient.data.l[1] = long(fl_event_time);
1726
  mask = 0L;
1727
  XSendEvent(fl_display, window_, False, mask, &ev);
1728
}