From fd0e9884cb88ed8e9ec58e30f6a92413bc0e7106 Mon Sep 17 00:00:00 2001 From: Bill Spitzak Date: Tue, 18 Jan 2000 01:05:49 +0000 Subject: [PATCH] Initial checkin of the code to sourceforge. --- Desktop.C | 149 +++++ Desktop.H | 22 + Frame.C | 1726 ++++++++++++++++++++++++++++++++++++++++++++++++ Frame.H | 192 ++++++ FrameWindow.C | 42 ++ FrameWindow.H | 31 + Hotkeys.C | 181 +++++ Makefile | 84 +++ Menu.C | 627 ++++++++++++++++++ README | 121 ++++ Rotated.C | 427 ++++++++++++ Rotated.H | 18 + configure.in | 60 ++ flwm.1 | 274 ++++++++ flwm_wmconfig | 65 ++ main.C | 374 +++++++++++ makeinclude.in | 26 + rotated_test.C | 118 ++++ 18 files changed, 4537 insertions(+) create mode 100644 Desktop.C create mode 100644 Desktop.H create mode 100644 Frame.C create mode 100644 Frame.H create mode 100644 FrameWindow.C create mode 100644 FrameWindow.H create mode 100644 Hotkeys.C create mode 100644 Makefile create mode 100644 Menu.C create mode 100644 README create mode 100644 Rotated.C create mode 100644 Rotated.H create mode 100644 configure.in create mode 100644 flwm.1 create mode 100755 flwm_wmconfig create mode 100644 main.C create mode 100644 makeinclude.in create mode 100644 rotated_test.C diff --git a/Desktop.C b/Desktop.C new file mode 100644 index 0000000..19ff4bd --- /dev/null +++ b/Desktop.C @@ -0,0 +1,149 @@ +// Desktop.C + +#include "config.h" + +#if DESKTOPS + +#include "Frame.H" +#include "Desktop.H" +#include +#include +#include + +Desktop* Desktop::current_ = 0; +Desktop* Desktop::first = 0; + +// return the highest desktop number: +int Desktop::max_number() { + int n = 0; + for (Desktop* d = first; d; d = d->next) + if (d->number_ > n) n = d->number_; + return n; +} + +// return an empty slot number: +int Desktop::available_number() { + int n = 1; + for (Desktop* d = first; d;) { + if (d->number_ == n) {n++; d = first;} + else d = d->next; + } + return n; +} + +// these are set by main.C: +Atom _win_workspace; +Atom _win_workspace_count; +Atom _win_workspace_names; +#ifndef __sgi +static Atom kwm_current_desktop; +#endif +extern Fl_Window* Root; + +static int dont_send; +static void send_desktops() { + if (dont_send) return; + int n = Desktop::max_number(); + setProperty(fl_xid(Root), _win_workspace_count, XA_CARDINAL, n); + char buffer[1025]; + char* p = buffer; + for (int i = 1; i <= n; i++) { + Desktop* d = Desktop::number(i); + const char* name = d ? d->name() : ""; + while (p < buffer+1024 && *name) *p++ = *name++; + *p++ = 0; + if (p >= buffer+1024) break; + } + XChangeProperty(fl_display, fl_xid(Root), _win_workspace_names, XA_STRING, + 8, PropModeReplace, (unsigned char *)buffer, p-buffer-1); +} + +Desktop::Desktop(const char* n, int num) { + next = first; + first = this; + name_ = strdup(n); + number_ = num; + send_desktops(); +} + +Desktop::~Desktop() { + // remove from list: + for (Desktop** p = &first; *p; p = &((*p)->next)) + if (*p == this) {*p = next; break;} + send_desktops(); + if (current_ == this || !first->next) current(first); + // put any clients onto another desktop: + for (Frame* c = Frame::first; c; c = c->next) + if (c->desktop() == this) c->desktop(first); + free((void*)name_); +} + +void Desktop::name(const char* l) { + free((void*)name_); + name_ = strdup(l); +} + +void Desktop::current(Desktop* n) { + if (n == current_) return; + current_ = n; + for (Frame* c = Frame::first; c; c = c->next) { + if (c->desktop() == n) { + if (c->state() == OTHER_DESKTOP) c->state(NORMAL); + } else if (c->desktop()) { + if (c->state() == NORMAL) c->state(OTHER_DESKTOP); + } + } + if (n && !dont_send) { +#ifndef __sgi + setProperty(fl_xid(Root), kwm_current_desktop, kwm_current_desktop, n->number()); +#endif + setProperty(fl_xid(Root), _win_workspace, XA_CARDINAL, n->number()-1); + } +} + +// return desktop with given number, create it if necessary: +Desktop* Desktop::number(int n, int create) { + if (!n) return 0; + Desktop* d; + for (d = first; d; d = d->next) if (d->number() == n) return d; + if (create) { + char buf[20]; sprintf(buf, "Desktop %d", n); + d = new Desktop(buf,n); + } + return d; +} + +// called at startup, read the list of desktops from the root +// window properties, or on failure make some default desktops. +void init_desktops() { + dont_send = 1; + int length; + char* buffer = + (char*)getProperty(fl_xid(Root), _win_workspace_names, XA_STRING, &length); + if (buffer) { + char* c = buffer; + for (int i = 1; c < buffer+length; i++) { + char* d = c; while (*d) d++; + if (*c != '<') new Desktop(c,i); + c = d+1; + } + XFree(buffer); + } + int current_num = 0; + int p = getIntProperty(fl_xid(Root), _win_workspace, XA_CARDINAL, -1); + if (p >= 0 && p < 25) current_num = p+1; +#ifndef __sgi + // SGI's Xlib barfs when you try to do this XInternAtom! + // Maybe somebody there does not like KDE? + kwm_current_desktop = XInternAtom(fl_display, "KWM_CURRENT_DESKTOP", 0); + if (!current_num) { + p = getIntProperty(fl_xid(Root), kwm_current_desktop, kwm_current_desktop); + if (p > 0 && p < 25) current_num = p; + } +#endif + if (!current_num) current_num = 1; + Desktop::current(Desktop::number(current_num, 1)); + dont_send = 0; +} + +#endif diff --git a/Desktop.H b/Desktop.H new file mode 100644 index 0000000..43fca25 --- /dev/null +++ b/Desktop.H @@ -0,0 +1,22 @@ +// Desktop.H + +class Desktop { + const char* name_; + int number_; + static Desktop* current_; +public: + static Desktop* first; + Desktop* next; + const char* name() const {return name_;} + void name(const char*); + int number() const {return number_;} + static Desktop* current() {return current_;} + static Desktop* number(int, int create = 0); + static void current(Desktop*); + static int available_number(); + static int max_number(); + Desktop(const char*, int); + ~Desktop(); + int junk; // for temporary storage by menu builder +}; + diff --git a/Frame.C b/Frame.C new file mode 100644 index 0000000..50feddc --- /dev/null +++ b/Frame.C @@ -0,0 +1,1726 @@ +// Frame.C + +#include "config.h" +#include "Frame.H" +#include "Desktop.H" +#include +#include +#include +#include "Rotated.H" + +static Atom wm_state = 0; +static Atom wm_change_state; +static Atom wm_protocols; +static Atom wm_delete_window; +static Atom wm_take_focus; +static Atom wm_save_yourself; +static Atom wm_colormap_windows; +static Atom _motif_wm_hints; +static Atom kwm_win_decoration; +#if DESKTOPS +static Atom kwm_win_desktop; +static Atom kwm_win_sticky; +#endif +//static Atom wm_client_leader; +static Atom _wm_quit_app; + +// these are set by initialize in main.C: +Atom _win_hints; +Atom _win_state; +#if DESKTOPS +extern Atom _win_workspace; +#endif + +#ifdef SHOW_CLOCK +extern char clock_buf[]; +extern int clock_alarm_on; +#endif + +static const int XEventMask = +ExposureMask|StructureNotifyMask +|KeyPressMask|KeyReleaseMask|KeymapStateMask|FocusChangeMask +|ButtonPressMask|ButtonReleaseMask +|EnterWindowMask|LeaveWindowMask +|PointerMotionMask|SubstructureRedirectMask|SubstructureNotifyMask; + +extern Fl_Window* Root; + +Frame* Frame::active_; +Frame* Frame::first; + +static inline int max(int a, int b) {return a > b ? a : b;} +static inline int min(int a, int b) {return a < b ? a : b;} + +//////////////////////////////////////////////////////////////// +// The constructor is by far the most complex part, as it collects +// all the scattered pieces of information about the window that +// X has and uses them to initialize the structure, position the +// window, and then finally create it. + +int dont_set_event_mask = 0; // used by FrameWindow + +// "existing" is a pointer to an XWindowAttributes structure that is +// passed for an already-existing window when the window manager is +// starting up. If so we don't want to alter the state, size, or +// position. If null than this is a MapRequest of a new window. +Frame::Frame(Window window, XWindowAttributes* existing) : + Fl_Window(0,0), + window_(window), + state_flags_(0), + flags_(0), + transient_for_xid(None), + transient_for_(0), + revert_to(active_), + colormapWinCount(0), + close_button(BUTTON_LEFT,BUTTON_TOP,BUTTON_W,BUTTON_H,"X"), + iconize_button(BUTTON_LEFT,BUTTON_TOP,BUTTON_W,BUTTON_H,"i"), + max_h_button(BUTTON_LEFT,BUTTON_TOP+3*BUTTON_H,BUTTON_W,BUTTON_H,"h"), + max_w_button(BUTTON_LEFT,BUTTON_TOP+BUTTON_H,BUTTON_W,BUTTON_H,"w"), + min_w_button(BUTTON_LEFT,BUTTON_TOP+2*BUTTON_H,BUTTON_W,BUTTON_H,"W") +{ + close_button.callback(button_cb_static); + iconize_button.callback(button_cb_static); + max_h_button.type(FL_TOGGLE_BUTTON); + max_h_button.callback(button_cb_static); + max_w_button.type(FL_TOGGLE_BUTTON); + max_w_button.callback(button_cb_static); + min_w_button.type(FL_TOGGLE_BUTTON); + min_w_button.callback(button_cb_static); + end(); + box(FL_NO_BOX); // relies on background color erasing interior + next = first; + first = this; + + // do this asap so we don't miss any events... + if (!dont_set_event_mask) + XSelectInput(fl_display, window_, + ColormapChangeMask | PropertyChangeMask | FocusChangeMask + ); + + if (!wm_state) { + // allocate all the atoms if this is the first time + wm_state = XInternAtom(fl_display, "WM_STATE", 0); + wm_change_state = XInternAtom(fl_display, "WM_CHANGE_STATE", 0); + wm_protocols = XInternAtom(fl_display, "WM_PROTOCOLS", 0); + wm_delete_window = XInternAtom(fl_display, "WM_DELETE_WINDOW", 0); + wm_take_focus = XInternAtom(fl_display, "WM_TAKE_FOCUS", 0); + wm_save_yourself = XInternAtom(fl_display, "WM_SAVE_YOURSELF", 0); + wm_colormap_windows = XInternAtom(fl_display, "WM_COLORMAP_WINDOWS",0); + _motif_wm_hints = XInternAtom(fl_display, "_MOTIF_WM_HINTS", 0); + kwm_win_decoration = XInternAtom(fl_display, "KWM_WIN_DECORATION", 0); +#if DESKTOPS + kwm_win_desktop = XInternAtom(fl_display, "KWM_WIN_DESKTOP", 0); + kwm_win_sticky = XInternAtom(fl_display, "KWM_WIN_STICKY", 0); +#endif +// wm_client_leader = XInternAtom(fl_display, "WM_CLIENT_LEADER", 0); + _wm_quit_app = XInternAtom(fl_display, "_WM_QUIT_APP", 0); + } + + label_y = label_h = label_w = 0; + getLabel(); + // getIconLabel(); + + {XWindowAttributes attr; + if (existing) attr = *existing; + else { + // put in some legal values in case XGetWindowAttributes fails: + attr.x = attr.y = 0; attr.width = attr.height = 100; + attr.colormap = fl_colormap; + attr.border_width = 0; + XGetWindowAttributes(fl_display, window, &attr); + } + left = top = dwidth = dheight = 0; // pretend border is zero-width for now + app_border_width = attr.border_width; + x(attr.x+app_border_width); restore_x = x(); + y(attr.y+app_border_width); restore_y = y(); + w(attr.width); restore_w = w(); + h(attr.height); restore_h = h(); + colormap = attr.colormap;} + + getColormaps(); + + //group_ = 0; + {XWMHints* hints = XGetWMHints(fl_display, window_); + if (hints) { + if ((hints->flags & InputHint) && !hints->input) set_flag(NO_FOCUS); + //if (hints && hints->flags&WindowGroupHint) group_ = hints->window_group; + } + switch (getIntProperty(wm_state, wm_state, 0)) { + case NormalState: + state_ = NORMAL; break; + case IconicState: + state_ = ICONIC; break; + // X also defines obsolete values ZoomState and InactiveState + default: + if (hints && (hints->flags&StateHint) && hints->initial_state==IconicState) + state_ = ICONIC; + else + state_ = NORMAL; + } + if (hints) XFree(hints);} + // Maya sets this, seems to mean the same as group: + // if (!group_) group_ = getIntProperty(wm_client_leader, XA_WINDOW); + + XGetTransientForHint(fl_display, window_, &transient_for_xid); + + getProtocols(); + + getMotifHints(); + + // get Gnome hints: + int p = getIntProperty(_win_hints, XA_CARDINAL); + if (p&1) set_flag(NO_FOCUS); // WIN_HINTS_SKIP_FOCUS + // if (p&2) // WIN_HINTS_SKIP_WINLIST + // if (p&4) // WIN_HINTS_SKIP_TASKBAR + // if (p&8) ... // WIN_HINTS_GROUP_TRANSIENT + if (p&16) set_flag(CLICK_TO_FOCUS); // WIN_HINTS_FOCUS_ON_CLICK + + // get KDE hints: + p = getIntProperty(kwm_win_decoration, kwm_win_decoration, 1); + if (!(p&3)) set_flag(NO_BORDER); + else if (p & 2) set_flag(THIN_BORDER); + if (p & 256) set_flag(NO_FOCUS); + + fix_transient_for(); + + if (transient_for()) { + if (state_ == NORMAL) state_ = transient_for()->state_; +#if DESKTOPS + desktop_ = transient_for()->desktop_; +#endif + } +#if DESKTOPS + // see if anybody thinks window is "sticky:" + else if ((getIntProperty(_win_state, XA_CARDINAL) & 1) // WIN_STATE_STICKY + || getIntProperty(kwm_win_sticky, kwm_win_sticky)) { + desktop_ = 0; + } else { + // get the desktop from either Gnome or KDE (Gnome takes precedence): + p = getIntProperty(_win_workspace, XA_CARDINAL, -1) + 1; // Gnome desktop + if (p <= 0) p = getIntProperty(kwm_win_desktop, kwm_win_desktop); + if (p > 0 && p < 25) + desktop_ = Desktop::number(p, 1); + else + desktop_ = Desktop::current(); + } + if (desktop_ && desktop_ != Desktop::current()) + if (state_ == NORMAL) state_ = OTHER_DESKTOP; +#endif + + int autoplace = getSizes(); + // some Motif programs assumme this will force the size to conform :-( + if (w() < min_w || h() < min_h) { + if (w() < min_w) w(min_w); + if (h() < min_h) h(min_h); + XResizeWindow(fl_display, window_, w(), h()); + } + + // try to detect programs that think "transient_for" means "no border": + if (transient_for_xid && !label() && !flag(NO_BORDER)) { + set_flag(THIN_BORDER); + } + updateBorder(); + show_hide_buttons(); + + if (autoplace && !existing && !(transient_for() && (x() || y()))) { + // autoplacement (stupid version for now) + x(Root->x()+(Root->w()-w())/2); + y(Root->y()+(Root->h()-h())/2); + // move it until it does not hide any existing windows: + const int delta = TITLE_WIDTH+LEFT; + for (Frame* f = next; f; f = f->next) { + if (f->x()+delta > x() && f->y()+delta > y() && + f->x()+f->w()-delta < x()+w() && f->y()+f->h()-delta < y()+h()) { + x(max(x(),f->x()+delta)); + y(max(y(),f->y()+delta)); + f = this; + } + } + } + // move window so contents and border are visible: + x(force_x_onscreen(x(), w())); + y(force_y_onscreen(y(), h())); + + // guess some values for the "restore" fields, if already maximized: + if (max_w_button.value()) { + restore_w = min_w + ((w()-dwidth-min_w)/2/inc_w) * inc_w; + restore_x = x()+left + (w()-dwidth-restore_w)/2; + } + if (max_h_button.value()) { + restore_h = min_h + ((h()-dheight-min_h)/2/inc_h) * inc_h; + restore_y = y()+top + (h()-dheight-restore_h)/2; + } + + const int mask = CWBorderPixel | CWColormap | CWEventMask | CWBitGravity + | CWBackPixel | CWOverrideRedirect; + XSetWindowAttributes sattr; + sattr.event_mask = XEventMask; + sattr.colormap = fl_colormap; + sattr.border_pixel = fl_xpixel(FL_GRAY0); + sattr.bit_gravity = NorthWestGravity; + sattr.override_redirect = 1; + sattr.background_pixel = fl_xpixel(FL_GRAY); + Fl_X::set_xid(this, XCreateWindow(fl_display, fl_xid(Root), + x(), y(), w(), h(), 0, + fl_visual->depth, + InputOutput, + fl_visual->visual, + mask, &sattr)); + + setStateProperty(); + + if (!dont_set_event_mask) XAddToSaveSet(fl_display, window_); + if (existing) set_state_flag(IGNORE_UNMAP); + XReparentWindow(fl_display, window_, fl_xid(this), left, top); + XSetWindowBorderWidth(fl_display, window_, 0); + if (state_ == NORMAL) XMapWindow(fl_display, window_); + sendConfigureNotify(); // many apps expect this even if window size unchanged + +#if CLICK_RAISES || CLICK_TO_TYPE + XGrabButton(fl_display, AnyButton, AnyModifier, window, False, + ButtonPressMask, GrabModeSync, GrabModeAsync, None, None); +#endif + + if (state_ == NORMAL) { + XMapWindow(fl_display, fl_xid(this)); + if (!existing) activate_if_transient(); + } +} + +// modify the passed X & W to a legal horizontal window position +int Frame::force_x_onscreen(int X, int W) { + // force all except the black border on-screen: + X = min(X, Root->x()+Root->w()+1-W); + X = max(X, Root->x()-1); + // force the contents on-screen: + X = min(X, Root->x()+Root->w()-W+dwidth-left); + if (W-dwidth > Root->w() || h()-dheight > Root->h()) + // windows bigger than the screen need title bar so they can move + X = max(X, Root->x()-LEFT); + else + X = max(X, Root->x()-left); + return X; +} + +// modify the passed Y & H to a legal vertical window position: +int Frame::force_y_onscreen(int Y, int H) { + // force border (except black edge) to be on-screen: + Y = min(Y, Root->y()+Root->h()+1-H); + Y = max(Y, Root->y()-1); + // force contents to be on-screen: + Y = min(Y, Root->y()+Root->h()-H+dheight-top); + Y = max(Y, Root->y()-top); + return Y; +} + +//////////////////////////////////////////////////////////////// +// destructor +// The destructor is called on DestroyNotify, so I don't have to do anything +// to the contained window, which is already been destroyed. + +// fltk bug: it does not clear these pointers when window is deleted, +// causing flwm to crash on window close sometimes: +extern Fl_Window *fl_xfocus; +extern Fl_Window *fl_xmousewin; + +Frame::~Frame() { + + // It is possible for the frame to be destroyed while the menu is + // popped-up, and the menu will still contain a pointer to it. To + // fix this the menu checks the state_ location for a legal and + // non-withdrawn state value before doing anything. This should + // be reliable unless something reallocates the memory and writes + // a legal state value to this location: + state_ = UNMAPPED; + + // fix fltk bug: + fl_xfocus = 0; + fl_xmousewin = 0; + Fl::focus_ = 0; + + // remove any pointers to this: + Frame** cp; for (cp = &first; *cp; cp = &((*cp)->next)) + if (*cp == this) {*cp = next; break;} + for (Frame* f = first; f; f = f->next) { + if (f->transient_for_ == this) f->transient_for_ = transient_for_; + if (f->revert_to == this) f->revert_to = revert_to; + } + throw_focus(1); + + if (colormapWinCount) { + XFree((char *)colormapWindows); + delete[] window_Colormaps; + } + //if (iconlabel()) XFree((char*)iconlabel()); + if (label()) XFree((char*)label()); +} + +//////////////////////////////////////////////////////////////// + +void Frame::getLabel(int del) { + char* old = (char*)label(); + char* nu = del ? 0 : (char*)getProperty(XA_WM_NAME); + if (nu) { + // since many window managers print a default label when none is + // given, many programs send spaces to make a blank label. Detect + // this and make it really be blank: + char* c = nu; while (*c == ' ') c++; + if (!*c) {XFree(nu); nu = 0;} + } + if (old) { + if (nu && !strcmp(old,nu)) {XFree(nu); return;} + XFree(old); + } else { + if (!nu) return; + } + Fl_Widget::label(nu); + if (nu) { + fl_font(TITLE_FONT_SLOT, TITLE_FONT_SIZE); + label_w = int(fl_width(nu))+6; + } else + label_w = 0; + if (shown() && label_h > 3 && left > 3) + XClearArea(fl_display, fl_xid(this), 1, label_y+3, left-1, label_h-3, 1); +} + +//////////////////////////////////////////////////////////////// + +int Frame::getGnomeState(int &) { +// values for _WIN_STATE property are from Gnome WM compliance docs: +#define WIN_STATE_STICKY (1<<0) /*everyone knows sticky*/ +#define WIN_STATE_MINIMIZED (1<<1) /*Reserved - definition is unclear*/ +#define WIN_STATE_MAXIMIZED_VERT (1<<2) /*window in maximized V state*/ +#define WIN_STATE_MAXIMIZED_HORIZ (1<<3) /*window in maximized H state*/ +#define WIN_STATE_HIDDEN (1<<4) /*not on taskbar but window visible*/ +#define WIN_STATE_SHADED (1<<5) /*shaded (MacOS / Afterstep style)*/ +#define WIN_STATE_HID_WORKSPACE (1<<6) /*not on current desktop*/ +#define WIN_STATE_HID_TRANSIENT (1<<7) /*owner of transient is hidden*/ +#define WIN_STATE_FIXED_POSITION (1<<8) /*window is fixed in position even*/ +#define WIN_STATE_ARRANGE_IGNORE (1<<9) /*ignore for auto arranging*/ + // nyi + return 0; +} + +//////////////////////////////////////////////////////////////// + +// Read the sizeHints, and try to remove the vast number of mistakes +// that some applications seem to do writing them. +// Returns true if autoplace should be done. + +int Frame::getSizes() { + + XSizeHints sizeHints; + long junk; + if (!XGetWMNormalHints(fl_display, window_, &sizeHints, &junk)) + sizeHints.flags = 0; + + // get the increment, use 1 if none or illegal values: + if (sizeHints.flags & PResizeInc) { + inc_w = sizeHints.width_inc; if (inc_w < 1) inc_w = 1; + inc_h = sizeHints.height_inc; if (inc_h < 1) inc_h = 1; + } else { + inc_w = inc_h = 1; + } + + // get the current size of the window: + int W = w()-dwidth; + int H = h()-dheight; + // I try a lot of places to get a good minimum size value. Lots of + // programs set illegal or junk values, so getting this correct is + // difficult: + min_w = W; + min_h = H; + + // guess a value for minimum size in case it is not set anywhere: + min_w = min(min_w, 4*BUTTON_H); + min_w = ((min_w+inc_w-1)/inc_w) * inc_w; + min_h = min(min_h, 4*BUTTON_H); + min_h = ((min_h+inc_h-1)/inc_h) * inc_h; + // some programs put the minimum size here: + if (sizeHints.flags & PBaseSize) { + junk = sizeHints.base_width; if (junk > 0) min_w = junk; + junk = sizeHints.base_height; if (junk > 0) min_h = junk; + } + // finally, try the actual place the minimum size should be: + if (sizeHints.flags & PMinSize) { + junk = sizeHints.min_width; if (junk > 0) min_w = junk; + junk = sizeHints.min_height; if (junk > 0) min_h = junk; + } + + max_w = max_h = 0; // default maximum size is "infinity" + if (sizeHints.flags & PMaxSize) { + // Though not defined by ICCCM standard, I interpret any maximum + // size that is less than the minimum to mean "infinity". This + // allows the maximum to be set in one direction only: + junk = sizeHints.max_width; + if (junk >= min_w && junk <= W) max_w = junk; + junk = sizeHints.max_height; + if (junk >= min_h && junk <= H) max_h = junk; + } + + // set the maximize buttons according to current size: + max_w_button.value(W == maximize_width()); + max_h_button.value(H == maximize_height()); + + // Currently only 1x1 aspect works: + if (sizeHints.flags & PAspect + && sizeHints.min_aspect.x == sizeHints.min_aspect.y) + set_flag(KEEP_ASPECT); + + // another fix for gimp, which sets PPosition to 0,0: + if (x() <= 0 && y() <= 0) sizeHints.flags &= ~PPosition; + + return !(sizeHints.flags & (USPosition|PPosition)); +} + +int max_w_switch; +// return width of contents when maximize button pressed: +int Frame::maximize_width() { + int W = max_w_switch; if (!W) W = Root->w(); + return ((W-TITLE_WIDTH-min_w)/inc_w) * inc_w + min_w; +} + +int max_h_switch; +int Frame::maximize_height() { + int H = max_h_switch; if (!H) H = Root->h(); + return ((H-min_h)/inc_h) * inc_h + min_h; +} + +//////////////////////////////////////////////////////////////// + +void Frame::getProtocols() { + int n; Atom* p = (Atom*)getProperty(wm_protocols, XA_ATOM, &n); + if (p) { + clear_flag(DELETE_WINDOW_PROTOCOL|TAKE_FOCUS_PROTOCOL|QUIT_PROTOCOL); + for (int i = 0; i < n; ++i) { + if (p[i] == wm_delete_window) { + set_flag(DELETE_WINDOW_PROTOCOL); + } else if (p[i] == wm_take_focus) { + set_flag(TAKE_FOCUS_PROTOCOL); + } else if (p[i] == wm_save_yourself) { + set_flag(SAVE_PROTOCOL); + } else if (p[i] == _wm_quit_app) { + set_flag(QUIT_PROTOCOL); + } + } + } + XFree((char*)p); +} + +//////////////////////////////////////////////////////////////// + +int Frame::getMotifHints() { + long* prop = (long*)getProperty(_motif_wm_hints, _motif_wm_hints); + if (!prop) return 0; + + // see /usr/include/X11/Xm/MwmUtil.h for meaning of these bits... + // prop[0] = flags (what props are specified) + // prop[1] = functions (all, resize, move, minimize, maximize, close, quit) + // prop[2] = decorations (all, border, resize, title, menu, minimize, + // maximize) + // prop[3] = input_mode (modeless, primary application modal, system modal, + // full application modal) + // prop[4] = status (tear-off window) + + // Fill in the default value for missing fields: + if (!(prop[0]&1)) prop[1] = 1; + if (!(prop[0]&2)) prop[2] = 1; + + // The low bit means "turn the marked items off", invert this. + // Transient windows already have size & iconize buttons turned off: + if (prop[1]&1) prop[1] = ~prop[1] & (transient_for_xid ? ~0x58 : -1); + if (prop[2]&1) prop[2] = ~prop[2] & (transient_for_xid ? ~0x60 : -1); + + int old_flags = flags(); + + // see if they are trying to turn off border: + if (!(prop[2])) set_flag(NO_BORDER); else clear_flag(NO_BORDER); + + // see if they are trying to turn off title & close box: + if (!(prop[2]&0x18)) set_flag(THIN_BORDER); else clear_flag(THIN_BORDER); + + // some Motif programs use this to disable resize :-( + // and some programs change this after the window is shown (*&%$#%) + if (!(prop[1]&2) || !(prop[2]&4)) + set_flag(NO_RESIZE); else clear_flag(NO_RESIZE); + + // and some use this to disable the Close function. The commented + // out test is it trying to turn off the mwm menu button: it appears + // programs that do that still expect Alt+F4 to close them, so I + // leave the close on then: + if (!(prop[1]&0x20) /*|| !(prop[2]&0x10)*/) + set_flag(NO_CLOSE); else clear_flag(NO_CLOSE); + + // see if they set "input hint" to non-zero: + // prop[3] should be nonzero but the only example of this I have + // found is Netscape 3.0 and it sets it to zero... + if (!shown() && (prop[0]&4) /*&& prop[3]*/) set_flag(MODAL); + + // see if it is forcing the iconize button back on. This makes + // transient_for act like group instead... + if ((prop[1]&0x8) || (prop[2]&0x20)) set_flag(ICONIZE); + + // Silly 'ol Amazon paint ignores WM_DELETE_WINDOW and expects to + // get the SGI-specific "_WM_QUIT_APP". It indicates this by trying + // to turn off the close box. SIGH!!! + if (flag(QUIT_PROTOCOL) && !(prop[1]&0x20)) + clear_flag(DELETE_WINDOW_PROTOCOL); + + XFree((char*)prop); + return (flags() ^ old_flags); +} + +//////////////////////////////////////////////////////////////// + +void Frame::getColormaps(void) { + if (colormapWinCount) { + XFree((char *)colormapWindows); + delete[] window_Colormaps; + } + int n; + Window* cw = (Window*)getProperty(wm_colormap_windows, XA_WINDOW, &n); + if (cw) { + colormapWinCount = n; + colormapWindows = cw; + window_Colormaps = new Colormap[n]; + for (int i = 0; i < n; ++i) { + if (cw[i] == window_) { + window_Colormaps[i] = colormap; + } else { + XWindowAttributes attr; + XSelectInput(fl_display, cw[i], ColormapChangeMask); + XGetWindowAttributes(fl_display, cw[i], &attr); + window_Colormaps[i] = attr.colormap; + } + } + } else { + colormapWinCount = 0; + } +} + +void Frame::installColormap() const { + for (int i = colormapWinCount; i--;) + if (colormapWindows[i] != window_ && window_Colormaps[i]) + XInstallColormap(fl_display, window_Colormaps[i]); + if (colormap) + XInstallColormap(fl_display, colormap); +} + +//////////////////////////////////////////////////////////////// + +// figure out transient_for(), based on the windows that exist, the +// transient_for and group attributes, etc: +void Frame::fix_transient_for() { + Frame* p = 0; + if (transient_for_xid && !flag(ICONIZE)) { + for (Frame* f = first; f; f = f->next) { + if (f != this && f->window_ == transient_for_xid) {p = f; break;} + } + // loops are illegal: + for (Frame* q = p; q; q = q->transient_for_) if (q == this) {p = 0; break;} + } + transient_for_ = p; +} + +int Frame::is_transient_for(const Frame* f) const { + if (f) + for (Frame* p = transient_for(); p; p = p->transient_for()) + if (p == f) return 1; + return 0; +} + +// When a program maps or raises a window, this is called. It guesses +// if this window is in fact a modal window for the currently active +// window and if so transfers the active state to this: +// This also activates new main windows automatically +int Frame::activate_if_transient() { + if (!Fl::pushed()) + if (!transient_for() || is_transient_for(active_)) return activate(1); + return 0; +} + +//////////////////////////////////////////////////////////////// + +int Frame::activate(int warp) { + // see if a modal & newer window is up: + for (Frame* c = first; c && c != this; c = c->next) + if (c->flag(MODAL) && c->transient_for() == this) + if (c->activate(warp)) return 1; + // ignore invisible windows: + if (state() != NORMAL || w() <= dwidth) return 0; + // always put in the colormap: + installColormap(); + // move the pointer if desired: + // (note that moving the pointer is pretty much required for point-to-type + // unless you know the pointer is already in the window): + if (!warp || Fl::event_state() & (FL_BUTTON1|FL_BUTTON2|FL_BUTTON3)) { + ; + } else if (warp==2) { + // warp to point at title: + XWarpPointer(fl_display, None, fl_xid(this), 0,0,0,0, left/2+1, + min(label_y+label_w/2+1,h()/2)); + } else { + warp_pointer(); + } + // skip windows that don't want focus: + if (flag(NO_FOCUS)) return 0; + // set this even if we think it already has it, this seems to fix + // bugs with Motif popups: + XSetInputFocus(fl_display, window_, RevertToPointerRoot, fl_event_time); + if (active_ != this) { + if (active_) active_->deactivate(); + active_ = this; +#if defined(ACTIVE_COLOR) + XSetWindowAttributes a; + a.background_pixel = fl_xpixel(FL_SELECTION_COLOR); + XChangeWindowAttributes(fl_display, fl_xid(this), CWBackPixel, &a); + labelcolor(contrast(FL_BLACK, FL_SELECTION_COLOR)); + XClearArea(fl_display, fl_xid(this), 2, 2, w()-4, h()-4, 1); +#else +#if defined(SHOW_CLOCK) + redraw(); +#endif +#endif + if (flag(TAKE_FOCUS_PROTOCOL)) + sendMessage(wm_protocols, wm_take_focus); + } + return 1; +} + +// this private function should only be called by constructor and if +// the window is active(): +void Frame::deactivate() { +#if defined(ACTIVE_COLOR) + XSetWindowAttributes a; + a.background_pixel = fl_xpixel(FL_GRAY); + XChangeWindowAttributes(fl_display, fl_xid(this), CWBackPixel, &a); + labelcolor(FL_BLACK); + XClearArea(fl_display, fl_xid(this), 2, 2, w()-4, h()-4, 1); +#else +#if defined(SHOW_CLOCK) + redraw(); +#endif +#endif +} + +#if CLICK_RAISES || CLICK_TO_TYPE +// After the XGrabButton, the main loop will get the mouse clicks, and +// it will call here when it gets them: +void click_raise(Frame* f) { + f->activate(); +#if CLICK_RAISES + if (fl_xevent->xbutton.button <= 1) f->raise(); +#endif + XAllowEvents(fl_display, ReplayPointer, CurrentTime); +} +#endif + +// get rid of the focus by giving it to somebody, if possible: +void Frame::throw_focus(int destructor) { + if (!active()) return; + if (!destructor) deactivate(); + active_ = 0; + if (revert_to && revert_to->activate()) return; + for (Frame* f = first; f; f = f->next) + if (f != this && f->activate()) return; +} + +//////////////////////////////////////////////////////////////// + +// change the state of the window (this is a private function and +// it ignores the transient-for or desktop information): + +void Frame::state(short newstate) { + short oldstate = state(); + if (newstate == oldstate) return; + state_ = newstate; + switch (newstate) { + case UNMAPPED: + throw_focus(); + set_state_flag(IGNORE_UNMAP); + XUnmapWindow(fl_display, fl_xid(this)); + XUnmapWindow(fl_display, window_); + XRemoveFromSaveSet(fl_display, window_); + break; + case NORMAL: + if (oldstate == UNMAPPED) XAddToSaveSet(fl_display, window_); + if (w() > dwidth) XMapWindow(fl_display, window_); + XMapWindow(fl_display, fl_xid(this)); + clear_state_flag(IGNORE_UNMAP); + break; + default: + if (oldstate == UNMAPPED) { + XAddToSaveSet(fl_display, window_); + } else if (oldstate == NORMAL) { + throw_focus(); + set_state_flag(IGNORE_UNMAP); + XUnmapWindow(fl_display, fl_xid(this)); + } else { + return; // don't setStateProperty IconicState multiple times + } + break; + } + setStateProperty(); +} + +void Frame::setStateProperty() const { + long data[2]; + switch (state()) { + case UNMAPPED : + data[0] = WithdrawnState; break; + case NORMAL : + case OTHER_DESKTOP : + data[0] = NormalState; break; + default : + data[0] = IconicState; break; + } + data[1] = (long)None; + XChangeProperty(fl_display, window_, wm_state, wm_state, + 32, PropModeReplace, (unsigned char *)data, 2); +} + +//////////////////////////////////////////////////////////////// +// Public state modifiers that move all transient_for(this) children +// with the frame and do the desktops right: + +void Frame::raise() { + Frame* newtop = 0; + Frame* previous = 0; + int previous_state = state_; + Frame** p; + // Find all the transient-for windows and this one, and raise them, + // preserving stacking order: + for (p = &first; *p;) { + Frame* f = *p; + if (f == this || f->is_transient_for(this) && f->state() != UNMAPPED) { + *p = f->next; // remove it from list + if (previous) { + XWindowChanges w; + w.sibling = fl_xid(previous); + w.stack_mode = Below; + XConfigureWindow(fl_display, fl_xid(f), CWSibling|CWStackMode, &w); + previous->next = f; + } else { + XRaiseWindow(fl_display, fl_xid(f)); + newtop = f; + } +#if DESKTOPS + if (f->desktop_ && f->desktop_ != Desktop::current()) + f->state(OTHER_DESKTOP); + else +#endif + f->state(NORMAL); + previous = f; + } else { + p = &((*p)->next); + } + } + previous->next = first; + first = newtop; +#if DESKTOPS + if (!transient_for() && desktop_ && desktop_ != Desktop::current()) { + // for main windows we also must move to the current desktop + desktop(Desktop::current()); + } +#endif + if (previous_state != NORMAL && newtop->state_==NORMAL) + newtop->activate_if_transient(); +} + +void Frame::lower() { + Frame* t = transient_for(); if (t) t->lower(); + if (!next || next == t) return; // already on bottom + // pull it out of the list: + Frame** p = &first; + for (; *p != this; p = &((*p)->next)) {} + *p = next; + // find end of list: + Frame* f = next; while (f->next != t) f = f->next; + // insert it after that: + f->next = this; next = t; + // and move the X window: + XWindowChanges w; + w.sibling = fl_xid(f); + w.stack_mode = Below; + XConfigureWindow(fl_display, fl_xid(this), CWSibling|CWStackMode, &w); +} + +void Frame::iconize() { + for (Frame* c = first; c; c = c->next) { + if (c == this || c->is_transient_for(this) && c->state() != UNMAPPED) + c->state(ICONIC); + } +} + +#if DESKTOPS +void Frame::desktop(Desktop* d) { + if (d == desktop_) return; + // Put all the relatives onto the desktop as well: + for (Frame* c = first; c; c = c->next) { + if (c == this || c->is_transient_for(this)) { + c->desktop_ = d; + c->setProperty(_win_state, XA_CARDINAL, !d); + c->setProperty(kwm_win_sticky, kwm_win_sticky, !d); + if (d) { + c->setProperty(kwm_win_desktop, kwm_win_desktop, d->number()); + c->setProperty(_win_workspace, XA_CARDINAL, d->number()-1); + } + if (!d || d == Desktop::current()) { + if (c->state() == OTHER_DESKTOP) c->state(NORMAL); + } else { + if (c->state() == NORMAL) c->state(OTHER_DESKTOP); + } + } + } +} +#endif + +//////////////////////////////////////////////////////////////// + +// Resize and/or move the window. The size is given for the frame, not +// the contents. This also sets the buttons on/off as needed: + +void Frame::set_size(int nx, int ny, int nw, int nh, int warp) { + int dx = nx-x(); x(nx); + int dy = ny-y(); y(ny); + if (!dx && !dy && nw == w() && nh == h()) return; + int unmap = 0; + int remap = 0; + // use XClearArea to cause correct damage events: + if (nw != w()) { + max_w_button.value(nw-dwidth == maximize_width()); + min_w_button.value(nw <= dwidth); + if (nw <= dwidth) { + unmap = 1; + } else { + if (w() <= dwidth) remap = 1; + } + int minw = (nw < w()) ? nw : w(); + XClearArea(fl_display, fl_xid(this), minw-RIGHT, 0, RIGHT, nh, 1); + w(nw); + } + if (nh != h()) { + max_h_button.value(nh-dheight == maximize_height()); + int minh = (nh < h()) ? nh : h(); + XClearArea(fl_display, fl_xid(this), 0, minh-BOTTOM, w(), BOTTOM, 1); + // see if label or close box moved, erase the minimum area: + int old_label_y = label_y; + int old_label_h = label_h; + h(nh); show_hide_buttons(); +#ifdef SHOW_CLOCK + int t = label_y + 3; // we have to clear the entire label area +#else + int t = nh; + if (label_y != old_label_y) { + t = label_y; if (old_label_y < t) t = old_label_y; + } else if (label_y+label_h != old_label_y+old_label_h) { + t = label_y+label_h; + if (old_label_y+old_label_h < t) t = old_label_y+old_label_h; + } +#endif + if (t < nh && left>LEFT) + XClearArea(fl_display,fl_xid(this), 1, t, left-1, nh-t, 1); + } + // for maximize button move the cursor first if window gets smaller + if (warp == 1 && (dx || dy)) + XWarpPointer(fl_display, None,None,0,0,0,0, dx, dy); + // for configure request, move the cursor first + if (warp == 2 && active() && !Fl::pushed()) warp_pointer(); + XMoveResizeWindow(fl_display, fl_xid(this), nx, ny, nw, nh); + if (nw <= dwidth) { + if (unmap) { + set_state_flag(IGNORE_UNMAP); + XUnmapWindow(fl_display, window_); + } + } else { + XResizeWindow(fl_display, window_, nw-dwidth, nh-dheight); + if (remap) { + XMapWindow(fl_display, window_); +#if CLICK_TO_TYPE + if (active()) activate(); +#else + activate(); +#endif + } + } + // for maximize button move the cursor second if window gets bigger: + if (warp == 3 && (dx || dy)) + XWarpPointer(fl_display, None,None,0,0,0,0, dx, dy); + if (nw > dwidth) sendConfigureNotify(); + XSync(fl_display,0); +} + +void Frame::sendConfigureNotify() const { + XConfigureEvent ce; + ce.type = ConfigureNotify; + ce.event = window_; + ce.window = window_; + ce.x = x()+left-app_border_width; + ce.y = y()+top-app_border_width; + ce.width = w()-dwidth; + ce.height = h()-dheight; + ce.border_width = app_border_width; + ce.above = None; + ce.override_redirect = 0; + XSendEvent(fl_display, window_, False, StructureNotifyMask, (XEvent*)&ce); +} + +// move the pointer inside the window: +void Frame::warp_pointer() { + int X,Y; Fl::get_mouse(X,Y); + X -= x(); + int Xi = X; + if (X <= 0) X = left/2+1; + if (X >= w()) X = w()-(RIGHT/2+1); + Y -= y(); + int Yi = Y; + if (Y < 0) Y = TOP/2+1; + if (Y >= h()) Y = h()-(BOTTOM/2+1); + if (X != Xi || Y != Yi) + XWarpPointer(fl_display, None, fl_xid(this), 0,0,0,0, X, Y); +} + +// Resize the frame to match the current border type: +void Frame::updateBorder() { + int nx = x()+left; + int ny = y()+top; + int nw = w()-dwidth; + int nh = h()-dheight; + if (flag(NO_BORDER)) { + left = top = dwidth = dheight = 0; + } else { + left = flag(THIN_BORDER) ? LEFT : LEFT+TITLE_WIDTH; + dwidth = left+RIGHT; + top = TOP; + dheight = TOP+BOTTOM; + } + nx -= left; + ny -= top; + nw += dwidth; + nh += dheight; + if (x()==nx && y()==ny && w()==nw && h()==nh) return; + x(nx); y(ny); w(nw); h(nh); + if (!shown()) return; // this is so constructor can call this + // try to make the contents not move while the border changes around it: + XSetWindowAttributes a; + a.win_gravity = StaticGravity; + XChangeWindowAttributes(fl_display, window_, CWWinGravity, &a); + XMoveResizeWindow(fl_display, fl_xid(this), nx, ny, nw, nh); + a.win_gravity = NorthWestGravity; + XChangeWindowAttributes(fl_display, window_, CWWinGravity, &a); + // fix the window position if the X server didn't do the gravity: + XMoveWindow(fl_display, window_, left, top); +} + +// position and show the buttons according to current border, size, +// and other state information: +void Frame::show_hide_buttons() { + if (flag(THIN_BORDER|NO_BORDER)) { + iconize_button.hide(); + max_w_button.hide(); + min_w_button.hide(); + max_h_button.hide(); + close_button.hide(); + return; + } + int by = BUTTON_TOP; + if (transient_for()) { + iconize_button.hide(); + min_w_button.hide(); + } else { + iconize_button.position(BUTTON_LEFT,by); + iconize_button.show(); + by += BUTTON_H; +#if MINIMIZE_BOX + min_w_button.position(BUTTON_LEFT,by); + min_w_button.show(); + by += BUTTON_H; +#else + min_w_button.hide(); +#endif + } + if (min_h == max_h || flag(KEEP_ASPECT|NO_RESIZE) || + !max_h_button.value() && by+label_w+2*BUTTON_H > h()-BUTTON_BOTTOM) { + max_h_button.hide(); + } else { + max_h_button.position(BUTTON_LEFT,by); + max_h_button.show(); + by += BUTTON_H; + } + if (min_w == max_w || flag(KEEP_ASPECT|NO_RESIZE) || + !max_w_button.value() && by+label_w+2*BUTTON_H > h()-BUTTON_BOTTOM) { + max_w_button.hide(); + } else { + max_w_button.position(BUTTON_LEFT,by); + max_w_button.show(); + by += BUTTON_H; + } + if (label_y != by && shown()) + XClearArea(fl_display,fl_xid(this), 1, by, left-1, label_h+label_y-by, 1); + label_y = by; +#if CLOSE_BOX + if (by+BUTTON_H > h()-BUTTON_BOTTOM || flag(NO_CLOSE)) { +#endif + label_h = h()-BOTTOM-by; + close_button.hide(); +#if CLOSE_BOX + } else { + close_button.show(); + close_button.position(BUTTON_LEFT,h()-(BUTTON_BOTTOM+BUTTON_H)); + label_h = close_button.y()-by; + } +#endif +} + +// make sure fltk does not try to set the window size: +void Frame::resize(int, int, int, int) {} + +//////////////////////////////////////////////////////////////// + +void Frame::close() { + if (flag(DELETE_WINDOW_PROTOCOL)) + sendMessage(wm_protocols, wm_delete_window); + else if (flag(QUIT_PROTOCOL)) + sendMessage(wm_protocols, _wm_quit_app); + else + kill(); +} + +void Frame::kill() { + XKillClient(fl_display, window_); +} + +// this is called when window manager exits: +void Frame::save_protocol() { + Frame* f; + for (f = first; f; f = f->next) if (f->flag(SAVE_PROTOCOL)) { + f->set_state_flag(SAVE_PROTOCOL_WAIT); + f->sendMessage(wm_protocols, wm_save_yourself); + } + double t = 10.0; // number of seconds to wait before giving up + while (t > 0.0) { + for (f = first; ; f = f->next) { + if (!f) return; + if (f->flag(SAVE_PROTOCOL) && f->state_flags_&SAVE_PROTOCOL_WAIT) break; + } + t = Fl::wait(t); + } +} + +//////////////////////////////////////////////////////////////// +// Drawing code: + +void Frame::draw() { + if (flag(NO_BORDER)) return; + if (!flag(THIN_BORDER)) Fl_Window::draw(); + if (damage() != FL_DAMAGE_CHILD) { +#if ACTIVE_COLOR + fl_frame2(active() ? "AAAAJJWW" : "AAAAJJWWNNTT",0,0,w(),h()); + if (active()) { + fl_color(FL_GRAY_RAMP+('N'-'A')); + fl_xyline(2, h()-3, w()-3, 2); + } +#else + fl_frame("AAAAWWJJTTNN",0,0,w(),h()); +#endif + if (!flag(THIN_BORDER) && label_h > 3) { +#ifdef SHOW_CLOCK + if (active()) { + int clkw = int(fl_width(clock_buf)); + if (clock_alarm_on) { + fl_font(TITLE_FONT_SLOT, TITLE_FONT_SIZE); + fl_rectf(LEFT-1, label_y + label_h - 3 - clkw, TITLE_WIDTH, clkw, + (ALARM_BG_COLOR>>16)&0xff, + (ALARM_BG_COLOR>>8)&0xff, + ALARM_BG_COLOR&0xff); + fl_color((ALARM_FG_COLOR>>16)&0xff, + (ALARM_FG_COLOR>>8)&0xff, + ALARM_FG_COLOR&0xff); + } else + fl_font(MENU_FONT_SLOT, TITLE_FONT_SIZE); + // This might overlay the label if the label is long enough + // and the window height is short enough. For now, we'll + // assume this is not enough of a problem to be concerned + // about. + draw_rotated90(clock_buf, 1, label_y+3, left-1, label_h-6, + Fl_Align(FL_ALIGN_BOTTOM|FL_ALIGN_CLIP)); + } else + // Only show the clock on the active frame. + XClearArea(fl_display, fl_xid(this), 1, label_y+3, + left-1, label_h-3, 0); +#endif + fl_color(labelcolor()); + fl_font(TITLE_FONT_SLOT, TITLE_FONT_SIZE); + draw_rotated90(label(), 1, label_y+3, left-1, label_h-3, + Fl_Align(FL_ALIGN_TOP|FL_ALIGN_CLIP)); + } + } +} + +#ifdef SHOW_CLOCK +void Frame::redraw_clock() { + double clkw = fl_width(clock_buf); + XClearArea(fl_display, fl_xid(this), + 1, label_y+label_h-3-(int)clkw, + left-1, (int)clkw, 1); +} +#endif + +void FrameButton::draw() { + Fl_Widget::draw_box(value() ? FL_DOWN_FRAME : FL_UP_FRAME, FL_GRAY); + fl_color(parent()->labelcolor()); + switch (label()[0]) { + case 'W': +#if MINIMIZE_ARROW + fl_line (x()+2,y()+(h())/2,x()+w()-4,y()+h()/2); + fl_line (x()+2,y()+(h())/2,x()+2+4,y()+h()/2+4); + fl_line (x()+2,y()+(h())/2,x()+2+4,y()+h()/2-4); +#else + fl_rect(x()+(h()-7)/2,y()+3,2,h()-6); +#endif + return; + case 'w': + fl_rect(x()+2,y()+(h()-7)/2,w()-4,7); + return; + case 'h': + fl_rect(x()+(h()-7)/2,y()+2,7,h()-4); + return; + case 'X': +#if CLOSE_X + fl_line(x()+2,y()+3,x()+w()-5,y()+h()-4); + fl_line(x()+3,y()+3,x()+w()-4,y()+h()-4); + fl_line(x()+2,y()+h()-4,x()+w()-5,y()+3); + fl_line(x()+3,y()+h()-4,x()+w()-4,y()+3); +#endif +#if CLOSE_HITTITE_LIGHTNING + fl_arc(x()+3,y()+3,w()-6,h()-6,0,360); + fl_line(x()+7,y()+3, x()+7,y()+11); +#endif + return; + case 'i': +#if ICONIZE_BOX + fl_rect(x()+w()/2-1,y()+h()/2-1,3,3); +#endif + return; + } +} + +//////////////////////////////////////////////////////////////// +// User interface code: + +// this is called when user clicks the buttons: +void Frame::button_cb(Fl_Button* b) { + switch (b->label()[0]) { + case 'W': // minimize button + if (b->value()) { + if (!max_w_button.value()) { + restore_x = x()+left; + restore_y = y()+top; +#if MINIMIZE_HEIGHT + restore_w=w()-dwidth; + restore_h = h()-dwidth; +#endif + } +#if MINIMIZE_HEIGHT + set_size(x(), y(), dwidth-1, + min(h(),min(350,label_w+3*BUTTON_H+BUTTON_TOP+BUTTON_BOTTOM)), + 1); +#else + set_size(x(), y(), dwidth-1, h(), 1); +#endif + } else { +#if MINIMIZE_HEIGHT + set_size(x(), y(), restore_w+dwidth, restore_h+dwidth, 1); +#else + set_size(x(), y(), restore_w+dwidth, h(), 1); +#endif + } + show_hide_buttons(); + break; + case 'w': // max-width button + if (b->value()) { + if (!min_w_button.value()) {restore_x=x()+left; restore_w=w()-dwidth;} + int W = maximize_width()+dwidth; + int X = force_x_onscreen(x() + (w()-W)/2, W); + set_size(X, y(), W, h(), 3); + } else { + set_size(restore_x-left, y(), restore_w+dwidth, h(), 1); + } + show_hide_buttons(); + break; + case 'h': // max-height button + if (b->value()) { + restore_y = y()+top; + restore_h = h()-dwidth; + int H = maximize_height()+dheight; + int Y = force_y_onscreen(y() + (h()-H)/2, H); + set_size(x(), Y, w(), H, 3); + } else { + set_size(x(), restore_y-top, w(), restore_h+dwidth, 1); + } + break; + case 'X': + close(); + break; + default: // iconize button + iconize(); + break; + } +} + +// static callback for fltk: +void Frame::button_cb_static(Fl_Widget* w, void*) { + ((Frame*)(w->parent()))->button_cb((Fl_Button*)w); +} + +// This method figures out what way the mouse will resize the window. +// It is used to set the cursor and to actually control what you grab. +// If the window cannot be resized in some direction this should not +// return that direction. +int Frame::mouse_location() { + int x = Fl::event_x(); + int y = Fl::event_y(); + int r = 0; + if (flag(NO_RESIZE)) return 0; + if (min_h != max_h) { + if (y < RESIZE_EDGE) r |= FL_ALIGN_TOP; + else if (y >= h()-RESIZE_EDGE) r |= FL_ALIGN_BOTTOM; + } + if (min_w != max_w) { +#if RESIZE_LEFT + if (x < RESIZE_EDGE) r |= FL_ALIGN_LEFT; +#else + if (x < RESIZE_EDGE && r) r |= FL_ALIGN_LEFT; +#endif + else if (x >= w()-RESIZE_EDGE) r |= FL_ALIGN_RIGHT; + } + return r; +} + +// set the cursor correctly for a return value from mouse_location(): +void Frame::set_cursor(int r) { + Fl_Cursor c = r ? FL_CURSOR_ARROW : FL_CURSOR_MOVE; + switch (r) { + case FL_ALIGN_TOP: + case FL_ALIGN_BOTTOM: + c = FL_CURSOR_NS; + break; + case FL_ALIGN_LEFT: + case FL_ALIGN_RIGHT: + c = FL_CURSOR_WE; + break; + case FL_ALIGN_LEFT|FL_ALIGN_TOP: + case FL_ALIGN_RIGHT|FL_ALIGN_BOTTOM: + c = FL_CURSOR_NWSE; + break; + case FL_ALIGN_LEFT|FL_ALIGN_BOTTOM: + case FL_ALIGN_RIGHT|FL_ALIGN_TOP: + c = FL_CURSOR_NESW; + break; + } + static Frame* previous_frame; + static Fl_Cursor previous_cursor; + if (this != previous_frame || c != previous_cursor) { + previous_frame = this; + previous_cursor = c; + cursor(c, CURSOR_FG_SLOT, CURSOR_BG_SLOT); + } +} + +#ifdef AUTO_RAISE +// timeout callback to cause autoraise: +void auto_raise(void*) { + if (Frame::activeFrame() && !Fl::grab() && !Fl::pushed()) + Frame::activeFrame()->raise(); +} +#endif + +extern void ShowMenu(); + +// If cursor is in the contents of a window this is set to that window. +// This is only used to force the cursor to an arrow even though X keeps +// sending mysterious erroneous move events: +static Frame* cursor_inside = 0; + +// Handle an fltk event. +int Frame::handle(int e) { + static int what, dx, dy, ix, iy, iw, ih; + // see if child widget handles event: + if (Fl_Window::handle(e) && e != FL_ENTER && e != FL_MOVE) { + if (e == FL_PUSH) set_cursor(-1); + return 1; + } + switch (e) { + + case FL_SHOW: + case FL_HIDE: + return 0; // prevent fltk from messing things up + + case FL_ENTER: +#if !CLICK_TO_TYPE + if (Fl::pushed() || Fl::grab()) return 1; + if (activate()) { +#ifdef AUTO_RAISE + Fl::remove_timeout(auto_raise); + Fl::add_timeout(AUTO_RAISE, auto_raise); +#endif + } +#endif + goto GET_CROSSINGS; + + case FL_LEAVE: +#if !CLICK_TO_TYPE && !STICKY_FOCUS + if (active()) { + deactivate(); + XSetInputFocus(fl_display, PointerRoot, RevertToPointerRoot, + fl_event_time); + active_ = 0; + } +#endif + goto GET_CROSSINGS; + + case 0: + GET_CROSSINGS: + // set cursor_inside to true when the mouse is inside a window + // set it false when mouse is on a frame or outside a window. + // fltk mangles the X enter/leave events, we need the original ones: + + switch (fl_xevent->type) { + case EnterNotify: + + // see if cursor skipped over frame and directly to interior: + if (fl_xevent->xcrossing.detail == NotifyVirtual || + fl_xevent->xcrossing.detail == NotifyNonlinearVirtual) + cursor_inside = this; + + else { + // cursor is now pointing at frame: + cursor_inside = 0; + } + + // fall through to FL_MOVE: + break; + + case LeaveNotify: + if (fl_xevent->xcrossing.detail == NotifyInferior) { + // cursor moved from frame to interior + cursor_inside = this; + set_cursor(-1); + return 1; + } + return 1; + + default: + return 0; // other X event we don't understand + } + + case FL_MOVE: + if (Fl::belowmouse() != this || cursor_inside == this) + set_cursor(-1); + else + set_cursor(mouse_location()); + return 1; + + case FL_PUSH: + if (Fl::event_button() > 2) { + set_cursor(-1); + ShowMenu(); + return 1; + } + ix = x(); iy = y(); iw = w(); ih = h(); + if (!max_w_button.value() && !min_w_button.value()) { + restore_x = ix+left; restore_w = iw-dwidth; + } +#if MINIMIZE_HEIGHT + if (!min_w_button.value()) +#endif + if (!max_h_button.value()) { + restore_y = iy+top; restore_h = ih-dwidth; + } + what = mouse_location(); + if (Fl::event_button() > 1) what = 0; // middle button does drag + dx = Fl::event_x_root()-ix; + if (what & FL_ALIGN_RIGHT) dx -= iw; + dy = Fl::event_y_root()-iy; + if (what & FL_ALIGN_BOTTOM) dy -= ih; + set_cursor(what); + return 1; + case FL_DRAG: + if (Fl::event_is_click()) return 1; // don't drag yet + case FL_RELEASE: + if (Fl::event_is_click()) { + if (Fl::grab()) return 1; +#if CLICK_TO_TYPE + if (activate()) { + if (Fl::event_button() <= 1) raise(); + return 1; + } +#endif + if (Fl::event_button() > 1) lower(); else raise(); + } else if (!what) { + int nx = Fl::event_x_root()-dx; + int W = Root->x()+Root->w(); + if (nx+iw > W && nx+iw < W+SCREEN_SNAP) { + int t = W+1-iw; + if (iw >= Root->w() || x() > t || nx+iw >= W+EDGE_SNAP) + t = W+(dwidth-left)-iw; + if (t >= x() && t < nx) nx = t; + } + int X = Root->x(); + if (nx < X && nx > X-SCREEN_SNAP) { + int t = X-1; + if (iw >= Root->w() || x() < t || nx <= X-EDGE_SNAP) t = X-BUTTON_LEFT; + if (t <= x() && t > nx) nx = t; + } + int ny = Fl::event_y_root()-dy; + int H = Root->y()+Root->h(); + if (ny+ih > H && ny+ih < H+SCREEN_SNAP) { + int t = H+1-ih; + if (ih >= Root->h() || y() > t || ny+ih >= H+EDGE_SNAP) + t = H+(dheight-top)-ih; + if (t >= y() && t < ny) ny = t; + } + int Y = Root->y(); + if (ny < Y && ny > Y-SCREEN_SNAP) { + int t = Y-1; + if (ih >= H || y() < t || ny <= Y-EDGE_SNAP) t = Y-top; + if (t <= y() && t > ny) ny = t; + } + set_size(nx, ny, iw, ih); + } else { + int nx = ix; + int ny = iy; + int nw = iw; + int nh = ih; + if (what & FL_ALIGN_RIGHT) + nw = Fl::event_x_root()-dx-nx; + else if (what & FL_ALIGN_LEFT) + nw = ix+iw-(Fl::event_x_root()-dx); + else {nx = x(); nw = w();} + if (what & FL_ALIGN_BOTTOM) + nh = Fl::event_y_root()-dy-ny; + else if (what & FL_ALIGN_TOP) + nh = iy+ih-(Fl::event_y_root()-dy); + else {ny = y(); nh = h();} + if (flag(KEEP_ASPECT)) { + if (nw-dwidth > nh-dwidth + && (what&(FL_ALIGN_LEFT|FL_ALIGN_RIGHT)) + || !(what&(FL_ALIGN_TOP|FL_ALIGN_BOTTOM))) + nh = nw-dwidth+dheight; + else + nw = nh-dheight+dwidth; + } + int MINW = min_w+dwidth; + if (nw <= dwidth && dwidth > TITLE_WIDTH) { + nw = dwidth-1; +#if MINIMIZE_HEIGHT + restore_h = nh; +#endif + } else { + if (inc_w > 1) nw = ((nw-MINW+inc_w/2)/inc_w)*inc_w+MINW; + if (nw < MINW) nw = MINW; + else if (max_w && nw > max_w+dwidth) nw = max_w+dwidth; + } + int MINH = min_h+dheight; + const int MINH_B = BUTTON_H+BUTTON_TOP+BUTTON_BOTTOM; + if (MINH_B > MINH) MINH = MINH_B; + if (inc_h > 1) nh = ((nh-MINH+inc_h/2)/inc_h)*inc_h+MINH; + if (nh < MINH) nh = MINH; + else if (max_h && nh > max_h+dheight) nh = max_h+dheight; + if (what & FL_ALIGN_LEFT) nx = ix+iw-nw; + if (what & FL_ALIGN_TOP) ny = iy+ih-nh; + set_size(nx,ny,nw,nh); + } + return 1; + } + return 0; +} + +// Handle events that fltk did not recognize (mostly ones directed +// at the desktop): + +int Frame::handle(const XEvent* ei) { + + switch (ei->type) { + + case ConfigureRequest: { + const XConfigureRequestEvent* e = &(ei->xconfigurerequest); + unsigned long mask = e->value_mask; + if (mask & CWBorderWidth) app_border_width = e->border_width; + // Try to detect if the application is really trying to move the + // window, or is simply echoing it's postion, possibly with some + // variation (such as echoing the parent window position), and + // dont' move it in that case: + int X = (mask & CWX && e->x != x()) ? e->x+app_border_width-left : x(); + int Y = (mask & CWY && e->y != y()) ? e->y+app_border_width-top : y(); + int W = (mask & CWWidth) ? e->width+dwidth : w(); + int H = (mask & CWHeight) ? e->height+dheight : h(); + // Generally we want to obey any application positioning of the + // window, except when it appears the app is trying to position + // the window "at the edge". + if (!(mask & CWX) || (X >= -2*left && X < 0)) X = force_x_onscreen(X,W); + if (!(mask & CWY) || (Y >= -2*top && Y < 0)) Y = force_y_onscreen(Y,H); + // Fix Rick Sayre's program that resizes it's windows bigger than the + // maximum size: + if (W > max_w+dwidth) max_w = 0; + if (H > max_h+dheight) max_h = 0; + set_size(X, Y, W, H, 2); + if (e->value_mask & CWStackMode && e->detail == Above) raise(); + return 1;} + + case MapRequest: { + //const XMapRequestEvent* e = &(ei->xmaprequest); + raise(); + return 1;} + + case UnmapNotify: { + const XUnmapEvent* e = &(ei->xunmap); + if (e->from_configure); + else if (state_flags_&IGNORE_UNMAP) clear_state_flag(IGNORE_UNMAP); + else state(UNMAPPED); + return 1;} + + case DestroyNotify: { + //const XDestroyWindowEvent* e = &(ei->xdestroywindow); + delete this; + return 1;} + + case ReparentNotify: { + const XReparentEvent* e = &(ei->xreparent); + if (e->parent==fl_xid(this)) return 1; // echo + if (e->parent==fl_xid(Root)) return 1; // app is trying to tear-off again? + delete this; // guess they are trying to paste tear-off thing back? + return 1;} + + case ClientMessage: { + const XClientMessageEvent* e = &(ei->xclient); + if (e->message_type == wm_change_state && e->format == 32) { + if (e->data.l[0] == NormalState) raise(); + else if (e->data.l[0] == IconicState) iconize(); + } else + // we may want to ignore _WIN_LAYER from xmms? + Fl::warning("flwm: unexpected XClientMessageEvent, type 0x%lx, " + "window 0x%lx\n", e->message_type, e->window); + return 1;} + + case ColormapNotify: { + const XColormapEvent* e = &(ei->xcolormap); + if (e->c_new) { // this field is called "new" in the old C++-unaware Xlib + colormap = e->colormap; + if (active()) installColormap(); + } + return 1;} + + case PropertyNotify: { + const XPropertyEvent* e = &(ei->xproperty); + Atom a = e->atom; + + // case XA_WM_ICON_NAME: (do something similar to name) + if (a == XA_WM_NAME) { + getLabel(e->state == PropertyDelete); + + } else if (a == wm_state) { + // it's not clear if I really need to look at this. Need to make + // sure it is not seeing the state echoed by the application by + // checking for it being different... + switch (getIntProperty(wm_state, wm_state, state())) { + case IconicState: + if (state() == NORMAL || state() == OTHER_DESKTOP) iconize(); break; + case NormalState: + if (state() != NORMAL && state() != OTHER_DESKTOP) raise(); break; + } + + } else if (a == wm_colormap_windows) { + getColormaps(); + if (active()) installColormap(); + + } else if (a == _motif_wm_hints) { + // some #%&%$# SGI Motif programs change this after mapping the window! + // :-( :=( :-( :=( :-( :=( :-( :=( :-( :=( :-( :=( + if (getMotifHints()) { // returns true if any flags changed + fix_transient_for(); + updateBorder(); + show_hide_buttons(); + } + + } else if (a == wm_protocols) { + getProtocols(); + // get Motif hints since they may do something with QUIT: + getMotifHints(); + + } else if (a == XA_WM_NORMAL_HINTS || a == XA_WM_SIZE_HINTS) { + getSizes(); + show_hide_buttons(); + + } else if (a == XA_WM_TRANSIENT_FOR) { + XGetTransientForHint(fl_display, window_, &transient_for_xid); + fix_transient_for(); + show_hide_buttons(); + + } else if (a == XA_WM_COMMAND) { + clear_state_flag(SAVE_PROTOCOL_WAIT); + + } + return 1;} + + } + return 0; +} + +//////////////////////////////////////////////////////////////// +// X utility routines: + +void* Frame::getProperty(Atom a, Atom type, int* np) const { + return ::getProperty(window_, a, type, np); +} + +void* getProperty(Window w, Atom a, Atom type, int* np) { + Atom realType; + int format; + unsigned long n, extra; + int status; + void* prop; + status = XGetWindowProperty(fl_display, w, + a, 0L, 256L, False, type, &realType, + &format, &n, &extra, (uchar**)&prop); + if (status != Success) return 0; + if (!prop) return 0; + if (!n) {XFree(prop); return 0;} + if (np) *np = (int)n; + return prop; +} + +int Frame::getIntProperty(Atom a, Atom type, int deflt) const { + return ::getIntProperty(window_, a, type, deflt); +} + +int getIntProperty(Window w, Atom a, Atom type, int deflt) { + void* prop = getProperty(w, a, type); + if (!prop) return deflt; + int r = int(*(long*)prop); + XFree(prop); + return r; +} + +void setProperty(Window w, Atom a, Atom type, int v) { + long prop = v; + XChangeProperty(fl_display, w, a, type, 32, PropModeReplace, (uchar*)&prop,1); +} + +void Frame::setProperty(Atom a, Atom type, int v) const { + ::setProperty(window_, a, type, v); +} + +void Frame::sendMessage(Atom a, Atom l) const { + XEvent ev; + long mask; + memset(&ev, 0, sizeof(ev)); + ev.xclient.type = ClientMessage; + ev.xclient.window = window_; + ev.xclient.message_type = a; + ev.xclient.format = 32; + ev.xclient.data.l[0] = long(l); + ev.xclient.data.l[1] = long(fl_event_time); + mask = 0L; + XSendEvent(fl_display, window_, False, mask, &ev); +} diff --git a/Frame.H b/Frame.H new file mode 100644 index 0000000..6c680ab --- /dev/null +++ b/Frame.H @@ -0,0 +1,192 @@ +// Frame.H + +// Each X window being managed by fltk has one of these + +#ifndef Frame_H +#define Frame_H + +#include +#include +#include +#include + +// The state is an enumeration of reasons why the window may be invisible. +// Only if it is NORMAL is the window visible. +enum { + UNMAPPED = 0, // unmap command from app (X calls this WithdrawnState) + NORMAL = 1, // window is visible +//SHADED = 2, // acts like NORMAL + ICONIC = 3, // hidden/iconized + OTHER_DESKTOP = 4 // normal but on another desktop +}; + +// values for flags: +// The flags are constant and are turned on by information learned +// from the Gnome, KDE, and/or Motif window manager hints. Flwm will +// ignore attempts to change these hints after the window is mapped. +enum { + NO_FOCUS = 0x0001, // does not take focus + CLICK_TO_FOCUS = 0x0002, // must click on window to give it focus + NO_BORDER = 0x0004, // raw window with no border + THIN_BORDER = 0x0008, // just resize border + NO_RESIZE = 0x0010, // don't resize even if sizehints say its ok + NO_CLOSE = 0x0040, // don't put a close box on it + TAKE_FOCUS_PROTOCOL = 0x0080, // send junk when giving window focus + DELETE_WINDOW_PROTOCOL= 0x0100, // close box sends a message + KEEP_ASPECT = 0x0200, // aspect ratio from sizeHints + MODAL = 0x0400, // grabs focus from transient_for window + ICONIZE = 0x0800, // transient_for_ actually means group :-( + QUIT_PROTOCOL = 0x1000, // Irix 4DWM "quit" menu item + SAVE_PROTOCOL = 0x2000 // "WM_SAVE_YOURSELF" stuff +}; + +// values for state_flags: +// These change over time +enum { + IGNORE_UNMAP = 0x01, // we did something that echos an UnmapNotify + SAVE_PROTOCOL_WAIT = 0x02 +}; + +class FrameButton : public Fl_Button { + void draw(); +public: + FrameButton(int X, int Y, int W, int H, const char* L=0) + : Fl_Button(X,Y,W,H,L) {} +}; + +class Desktop; + +class Frame : public Fl_Window { + + Window window_; + + short state_; // X server state: iconic, withdrawn, normal + short state_flags_; // above state flags + void set_state_flag(short i) {state_flags_ |= i;} + void clear_state_flag(short i) {state_flags_&=~i;} + + int flags_; // above constant flags + void set_flag(int i) {flags_ |= i;} + void clear_flag(int i) {flags_&=~i;} + + int restore_x, restore_w; // saved size when min/max width is set + int restore_y, restore_h; // saved size when max height is set + int min_w, max_w, inc_w; // size range and increment + int min_h, max_h, inc_h; // size range and increment + int app_border_width; // value of border_width application tried to set + + int left, top, dwidth, dheight; // current thickness of border + int label_y, label_h; // location of label + int label_w; // measured width of printed label + + Window transient_for_xid; // value from X + Frame* transient_for_; // the frame for that xid, if found + + Frame* revert_to; // probably the xterm this was run from + + Colormap colormap; // this window's colormap + int colormapWinCount; // list of other windows to install colormaps for + Window *colormapWindows; + Colormap *window_Colormaps; // their colormaps + + Desktop* desktop_; + + FrameButton close_button; + FrameButton iconize_button; + FrameButton max_h_button; + FrameButton max_w_button; + FrameButton min_w_button; + + int maximize_width(); + int maximize_height(); + int force_x_onscreen(int X, int W); + int force_y_onscreen(int Y, int H); + + void sendMessage(Atom, Atom) const; + void sendConfigureNotify() const; + void setStateProperty() const; + + void* getProperty(Atom, Atom = AnyPropertyType, int* length = 0) const; + int getIntProperty(Atom, Atom = AnyPropertyType, int deflt = 0) const; + void setProperty(Atom, Atom, int) const; + void getLabel(int del = 0); + void getColormaps(); + int getSizes(); + int getGnomeState(int&); + void getProtocols(); + int getMotifHints(); + void updateBorder(); + void fix_transient_for(); // called when transient_for_xid changes + + void installColormap() const; + + void set_size(int,int,int,int, int warp=0); + void resize(int,int,int,int); + void show_hide_buttons(); + + int handle(int); // handle fltk events + void set_cursor(int); + int mouse_location(); + + void draw(); + + static Frame* active_; + static void button_cb_static(Fl_Widget*, void*); + void button_cb(Fl_Button*); + + void deactivate(); + int activate_if_transient(); + void _desktop(Desktop*); + + int border() const {return !(flags_&NO_BORDER);} + int flags() const {return flags_;} + int flag(int i) const {return flags_&i;} + void throw_focus(int destructor = 0); + void warp_pointer(); + +public: + + int handle(const XEvent*); + + static Frame* first; + Frame* next; // stacking order, top to bottom + + Frame(Window, XWindowAttributes* = 0); + ~Frame(); + + Window window() const {return window_;} + Frame* transient_for() const {return transient_for_;} + int is_transient_for(const Frame*) const; + + Desktop* desktop() const {return desktop_;} + void desktop(Desktop*); + + void raise(); // also does map + void lower(); + void iconize(); + void close(); + void kill(); + int activate(int warp = 0); // returns true if it actually sets active state + + short state() const {return state_;} + void state(short); // don't call this unless you know what you are doing! + + int active() const {return active_==this;} + static Frame* activeFrame() {return active_;} + + static void save_protocol(); // called when window manager exits + + // The following should be conditionally defined based on the + // SHOW_CLOCK definition in config.h but that definition is not + // available at the time we are evaluating this; it does no harm + // to be present even if not SHOW_CLOCK. + void redraw_clock(); + +}; + +// handy wrappers for those ugly X routines: +void* getProperty(Window, Atom, Atom = AnyPropertyType, int* length = 0); +int getIntProperty(Window, Atom, Atom = AnyPropertyType, int deflt = 0); +void setProperty(Window, Atom, Atom, int); + +#endif diff --git a/FrameWindow.C b/FrameWindow.C new file mode 100644 index 0000000..6ea8fd3 --- /dev/null +++ b/FrameWindow.C @@ -0,0 +1,42 @@ +// FrameWindow.C + +// X does not echo back the window-map events (it probably should when +// override_redirect is off). Unfortunately this means you have to use +// this subclass if you want a "normal" fltk window, it will force a +// Frame to be created and destroy it upon hide. + +// Warning: modal() does not work! Don't turn it on as it screws up the +// interface with the window borders. You can use set_non_modal() to +// disable the iconize box but the window manager must be written to +// not be modal. + +#include +#include "FrameWindow.H" +#include "Frame.H" + +extern int dont_set_event_mask; + +void FrameWindow::show() { + if (shown()) {Fl_Window::show(); return;} + Fl_Window::show(); + dont_set_event_mask = 1; + frame = new Frame(fl_xid(this)); + dont_set_event_mask = 0; +} + +void FrameWindow::hide() { + if (shown()) { + Fl_Window::hide(); + delete frame; + } +} + +int FrameWindow::handle(int e) { + if (Fl_Window::handle(e)) return 1; + // make Esc close the window: + if (e == FL_SHORTCUT && Fl::event_key()==FL_Escape) { + do_callback(); + return 1; + } + return 0; +} diff --git a/FrameWindow.H b/FrameWindow.H new file mode 100644 index 0000000..3b9e0ad --- /dev/null +++ b/FrameWindow.H @@ -0,0 +1,31 @@ +// FrameWindow.H + +// X does not echo back the window-map events (it probably should when +// override_redirect is off). Unfortunately this means you have to use +// this subclass if you want a "normal" fltk window, it will force a +// Frame to be created and destroy it upon hide. + +// Warning: modal() does not work! Don't turn it on as it screws up the +// interface with the window borders. You can use set_non_modal() to +// disable the iconize box but the window manager must be written to +// not be modal. + +#ifndef FrameWindow_H +#define FrameWindow_H + +#include +class Frame; + +class FrameWindow : public Fl_Window { + Frame* frame; +public: + void hide(); + void show(); + int handle(int); + FrameWindow(int X, int Y, int W, int H, const char* L = 0) : + Fl_Window(X,Y,W,H,L) {} + FrameWindow(int W, int H, const char* L = 0) : + Fl_Window(W,H,L) {} +}; + +#endif diff --git a/Hotkeys.C b/Hotkeys.C new file mode 100644 index 0000000..8b4197e --- /dev/null +++ b/Hotkeys.C @@ -0,0 +1,181 @@ +// Hotkeys.C + +// If you want to change what the hotkeys are, see the table at the bottom! + +#include "config.h" +#include "Frame.H" +#include "Desktop.H" +#include + +extern void ShowMenu(); +extern void ShowTabMenu(int tab); + +#if STANDARD_HOTKEYS + +static void NextWindow() { // Alt+Tab + ShowTabMenu(1); +} + +static void PreviousWindow() { // Alt+Shift+Tab + ShowTabMenu(-1); +} + +#endif + +#if DESKTOPS + +static void NextDesk() { + if (Desktop::current()) { + Desktop::current(Desktop::current()->next? + Desktop::current()->next:Desktop::first); + } else { + Desktop::current(Desktop::first); + } +} + +static void PreviousDesk() { + Desktop* search=Desktop::first; + while (search->next && search->next!=Desktop::current()){ + search=search->next; + } + Desktop::current(search); +} + +// warning: this assummes it is bound to Fn key: +static void DeskNumber() { + Desktop::current(Desktop::number(Fl::event_key()-FL_F, 1)); +} +#endif + +#if WMX_HOTKEYS || CDE_HOTKEYS + +static void Raise() { // Alt+Up + Frame* f = Frame::activeFrame(); + if (f) f->raise(); +} + +static void Lower() { // Alt+Down + Frame* f = Frame::activeFrame(); + if (f) f->lower(); +} + +static void Iconize() { // Alt+Enter + Frame* f = Frame::activeFrame(); + if (f) f->iconize(); + else ShowMenu(); // so they can deiconize stuff +} + +static void Close() { // Alt+Delete + Frame* f = Frame::activeFrame(); + if (f) f->close(); +} + +#endif + +//////////////////////////////////////////////////////////////// + +static struct {int key; void (*func)();} keybindings[] = { +#if STANDARD_HOTKEYS || MINIMUM_HOTKEYS + // these are very common and tend not to conflict, due to Windoze: + {FL_ALT+FL_Escape, ShowMenu}, + {FL_ALT+FL_Menu, ShowMenu}, +#endif +#if STANDARD_HOTKEYS + {FL_ALT+FL_Tab, NextWindow}, + {FL_ALT+FL_SHIFT+FL_Tab,PreviousWindow}, + {FL_ALT+FL_SHIFT+0xfe20,PreviousWindow}, // XK_ISO_Left_Tab +#endif +#if KWM_HOTKEYS && DESKTOPS // KWM uses these to switch desktops +// {FL_CTRL+FL_Tab, NextDesk}, +// {FL_CTRL+FL_SHIFT+FL_Tab,PreviousDesk}, +// {FL_CTRL+FL_SHIFT+0xfe20,PreviousDesk}, // XK_ISO_Left_Tab + {FL_CTRL+FL_F+1, DeskNumber}, + {FL_CTRL+FL_F+2, DeskNumber}, + {FL_CTRL+FL_F+3, DeskNumber}, + {FL_CTRL+FL_F+4, DeskNumber}, + {FL_CTRL+FL_F+5, DeskNumber}, + {FL_CTRL+FL_F+6, DeskNumber}, + {FL_CTRL+FL_F+7, DeskNumber}, + {FL_CTRL+FL_F+8, DeskNumber}, + {FL_CTRL+FL_F+9, DeskNumber}, + {FL_CTRL+FL_F+10, DeskNumber}, + {FL_CTRL+FL_F+11, DeskNumber}, + {FL_CTRL+FL_F+12, DeskNumber}, +#endif +#if WMX_HOTKEYS + // wmx also sets all these, they seem pretty useful: + {FL_ALT+FL_Up, Raise}, + {FL_ALT+FL_Down, Lower}, + {FL_ALT+FL_Enter, Iconize}, + {FL_ALT+FL_Delete, Close}, + //{FL_ALT+FL_Page_Up, ToggleMaxH}, + //{FL_ALT+FL_Page_Down,ToggleMaxW}, +#endif +#if WMX_DESK_HOTKEYS && DESKTOPS + // these wmx keys are not set by default as they break NetScape: + {FL_ALT+FL_Left, PreviousDesk}, + {FL_ALT+FL_Right, NextDesk}, +#endif +#if CDE_HOTKEYS + // CDE hotkeys (or at least what SGI's 4DWM uses): + {FL_ALT+FL_F+1, Raise}, +//{FL_ALT+FL_F+2, unknown}, // KWM uses this to run a typed-in command + {FL_ALT+FL_F+3, Lower}, + {FL_ALT+FL_F+4, Close}, // this matches KWM +//{FL_ALT+FL_F+5, Restore}, // useless because no icons visible +//{FL_ALT+FL_F+6, unknown}, // ? +//{FL_ALT+FL_F+7, Move}, // grabs the window for movement +//{FL_ALT+FL_F+8, Resize}, // grabs the window for resizing + {FL_ALT+FL_F+9, Iconize}, +//{FL_ALT+FL_F+10, Maximize}, +//{FL_ALT+FL_F+11, unknown}, // ? + {FL_ALT+FL_F+12, Close}, // actually does "quit" +#else +#if DESKTOPS && DESKTOP_HOTKEYS + // seem to be common to Linux window managers + {FL_ALT+FL_F+1, DeskNumber}, + {FL_ALT+FL_F+2, DeskNumber}, + {FL_ALT+FL_F+3, DeskNumber}, + {FL_ALT+FL_F+4, DeskNumber}, + {FL_ALT+FL_F+5, DeskNumber}, + {FL_ALT+FL_F+6, DeskNumber}, + {FL_ALT+FL_F+7, DeskNumber}, + {FL_ALT+FL_F+8, DeskNumber}, + {FL_ALT+FL_F+9, DeskNumber}, + {FL_ALT+FL_F+10, DeskNumber}, + {FL_ALT+FL_F+11, DeskNumber}, + {FL_ALT+FL_F+12, DeskNumber}, +#endif +#endif + {0}}; + +int Handle_Hotkey() { + for (int i = 0; keybindings[i].key; i++) { + if (Fl::test_shortcut(keybindings[i].key) || + (keybindings[i].key & 0xFFFF) == FL_Delete + && Fl::event_key() == FL_BackSpace// fltk bug? + ) { + keybindings[i].func(); + return 1; + } + } + return 0; +} + +extern Fl_Window* Root; + +void Grab_Hotkeys() { + Window root = fl_xid(Root); + for (int i = 0; keybindings[i].key; i++) { + int k = keybindings[i].key; + int keycode = XKeysymToKeycode(fl_display, k & 0xFFFF); + if (!keycode) continue; + // Silly X! we need to ignore caps lock & numlock keys by grabbing + // all the combinations: + XGrabKey(fl_display, keycode, k>>16, root, 0, 1, 1); + XGrabKey(fl_display, keycode, (k>>16)|2, root, 0, 1, 1); // CapsLock + XGrabKey(fl_display, keycode, (k>>16)|16, root, 0, 1, 1); // NumLock + XGrabKey(fl_display, keycode, (k>>16)|18, root, 0, 1, 1); // both + } +} + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..486c524 --- /dev/null +++ b/Makefile @@ -0,0 +1,84 @@ +SHELL=/bin/sh + +PROGRAM = flwm +VERSION = 0.25 + +CXXFILES = main.C Frame.C Rotated.C Menu.C FrameWindow.C Desktop.C Hotkeys.C + +LIBS = -lfltk + +MANPAGE = 1 + +################################################################ + +OBJECTS = $(CXXFILES:.C=.o) + +all: $(PROGRAM) + +$(PROGRAM) : $(OBJECTS) + $(CXX) $(LDFLAGS) -o $(PROGRAM) $(OBJECTS) $(LIBS) $(LDLIBS) + +makeinclude: configure + ./configure +include makeinclude + +.SUFFIXES : .fl .do .C .c .H + +.C.o : + $(CXX) $(CXXFLAGS) -c $< +.C : + $(CXX) $(CXXFLAGS) -c $< +.fl.C : + -fluid -c $< +.fl.H : + -fluid -c $< + +clean : + -@ rm -f *.o $(PROGRAM) $(CLEAN) core *~ makedepend + @touch makedepend + +depend: + $(MAKEDEPEND) -I.. $(CXXFLAGS) $(CXXFILES) $(CFILES) > makedepend +makedepend: + touch makedepend +include makedepend + +install: $(PROGRAM) + $(INSTALL) -s $(PROGRAM) $(bindir)/$(PROGRAM) + $(INSTALL) $(PROGRAM).$(MANPAGE) $(mandir)/man$(MANPAGE)/$(PROGRAM).$(MANPAGE) + +uninstall: + -@ rm -f $(bindir)/$(PROGRAM) + -@ rm -f $(mandir)/man$(MANPAGE)/$(PROGRAM).$(MANPAGE) + +dist: + cat /dev/null > makedepend + -@mkdir $(PROGRAM)-$(VERSION) + -@ln README Makefile configure install-sh makedepend *.C *.H *.h *.in *.fl $(PROGRAM).$(MANPAGE) flwm_wmconfig $(PROGRAM)-$(VERSION) + tar -cvzf $(PROGRAM)-$(VERSION).tgz $(PROGRAM)-$(VERSION) + -@rm -r $(PROGRAM)-$(VERSION) + +exedist: + -@mkdir $(PROGRAM)-$(VERSION)-x86 + -@ln README $(PROGRAM) $(PROGRAM).$(MANPAGE) flwm_wmconfig $(PROGRAM)-$(VERSION)-x86 + tar -cvzf $(PROGRAM)-$(VERSION)-x86.tgz $(PROGRAM)-$(VERSION)-x86 + -@rm -r $(PROGRAM)-$(VERSION)-x86 + +################################################################ + +PROGRAM_D = $(PROGRAM)_d + +debug: $(PROGRAM_D) + +OBJECTS_D = $(CXXFILES:.C=.do) $(CFILES:.c=.do) + +.C.do : + $(CXX) -I.. $(CXXFLAGS_D) -c -o $@ $< +.c.do : + $(CC) -I.. $(CFLAGS_D) -c -o $@ $< + +$(PROGRAM_D) : $(OBJECTS_D) + $(CXX) $(LDFLAGS) -o $(PROGRAM_D) $(OBJECTS_D) $(LIBS) $(LDLIBS) + +rotated_test: Rotated.o rotated_test.C + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o rotated_test rotated_test.C Rotated.o $(LIBS) $(LDLIBS) diff --git a/Menu.C b/Menu.C new file mode 100644 index 0000000..5e1883e --- /dev/null +++ b/Menu.C @@ -0,0 +1,627 @@ +// Menu.cxx + +#include "config.h" +#include "Frame.H" +#if DESKTOPS +#include "Desktop.H" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "FrameWindow.H" + +#include +#include +#include + +// it is possible for the window to be deleted or withdrawn while +// the menu is up. This will detect that case (with reasonable +// reliability): +static int +window_deleted(Frame* c) +{ + return c->state() != NORMAL + && c->state() != ICONIC + && c->state() != OTHER_DESKTOP; +} + +static void +frame_callback(Fl_Widget*, void*d) +{ + Frame* c = (Frame*)d; + if (window_deleted(c)) return; + c->raise(); + c->activate(2); +} + +#if DESKTOPS +// raise it but also put it on the current desktop: +static void +move_frame_callback(Fl_Widget*, void*d) +{ + Frame* c = (Frame*)d; + if (window_deleted(c)) return; + c->desktop(Desktop::current()); + c->raise(); + c->activate(2); +} +#endif + +#define SCREEN_DX 1 // offset to corner of contents area +#define SCREEN_W (MENU_ICON_W-2) // size of area to draw contents in +#define SCREEN_H (MENU_ICON_H-2) // size of area to draw contents in + +#define MAX_NESTING_DEPTH 32 + +extern Fl_Window* Root; + +static void +frame_label_draw(const Fl_Label* o, int X, int Y, int W, int H, Fl_Align align) +{ + Frame* f = (Frame*)(o->value); + if (window_deleted(f)) return; + fl_draw_box(FL_THIN_DOWN_BOX, X, Y, MENU_ICON_W, MENU_ICON_H, FL_GRAY); + for (Frame* c = Frame::first; c; c = c->next) { + if (c->state() != UNMAPPED && (c==f || c->is_transient_for(f))) { + int x = ((c->x()-Root->x())*SCREEN_W+Root->w()/2)/Root->w(); + int w = (c->w()*SCREEN_W+Root->w()-1)/Root->w(); + if (w > SCREEN_W) w = SCREEN_W; + if (w < 3) w = 3; + if (x+w > SCREEN_W) x = SCREEN_W-w; + if (x < 0) x = 0; + int y = ((c->y()-Root->y())*SCREEN_H+Root->h()/2)/Root->h(); + int h = (c->h()*SCREEN_H+Root->h()-1)/Root->h(); + if (h > SCREEN_H) h = SCREEN_H; + if (h < 3) h = 3; + if (y+h > SCREEN_H) y = SCREEN_H-h; + if (y < 0) y = 0; + fl_color(FL_BLACK); + if (c->state() == ICONIC) + fl_rect(X+x+SCREEN_DX, Y+y+SCREEN_DX, w, h); + else + fl_rectf(X+x+SCREEN_DX, Y+y+SCREEN_DX, w, h); + } + } + fl_font(o->font, o->size); + fl_color((Fl_Color)o->color); + const char* l = f->label(); if (!l) l = "unnamed"; + fl_draw(l, X+MENU_ICON_W+3, Y, W-MENU_ICON_W-3, H, align); +} + +static void +frame_label_measure(const Fl_Label* o, int& W, int& H) +{ + Frame* f = (Frame*)(o->value); + if (window_deleted(f)) {W = MENU_ICON_W+3; H = MENU_ICON_H; return;} + const char* l = f->label(); if (!l) l = "unnamed"; + fl_font(o->font, o->size); + fl_measure(l, W, H); + W += MENU_ICON_W+3; + if (W > MAX_MENU_WIDTH) W = MAX_MENU_WIDTH; + if (H < MENU_ICON_H) H = MENU_ICON_H; +} + +// This labeltype is used for non-frame items so the text can line +// up with the icons: + +static void +label_draw(const Fl_Label* o, int X, int Y, int W, int H, Fl_Align align) +{ + fl_font(o->font, o->size); + fl_color((Fl_Color)o->color); + fl_draw(o->value, X+MENU_ICON_W+3, Y, W-MENU_ICON_W-3, H, align); +} + +static void +label_measure(const Fl_Label* o, int& W, int& H) +{ + fl_font(o->font, o->size); + fl_measure(o->value, W, H); + W += MENU_ICON_W+3; + if (W > MAX_MENU_WIDTH) W = MAX_MENU_WIDTH; + if (H < MENU_ICON_H) H = MENU_ICON_H; +} + +#define FRAME_LABEL FL_FREE_LABELTYPE +#define TEXT_LABEL Fl_Labeltype(FL_FREE_LABELTYPE+1) + +//////////////////////////////////////////////////////////////// + +static void +cancel_cb(Fl_Widget* w, void*) +{ + w->window()->hide(); +} + +#if DESKTOPS + +static void +desktop_cb(Fl_Widget*, void* v) +{ + Desktop::current((Desktop*)v); +} + +static void +delete_desktop_cb(Fl_Widget*, void* v) +{ + delete (Desktop*)v; +} + +#if ASK_FOR_NEW_DESKTOP_NAME + +static Fl_Input* new_desktop_input; + +static void +new_desktop_ok_cb(Fl_Widget* w, void*) +{ + w->window()->hide(); + Desktop::current(new Desktop(new_desktop_input->value(), Desktop::available_number())); +} + +static void +new_desktop_cb(Fl_Widget*, void*) +{ + if (!new_desktop_input) { + FrameWindow* w = new FrameWindow(190,90); + new_desktop_input = new Fl_Input(10,30,170,25,"New desktop name:"); + new_desktop_input->align(FL_ALIGN_TOP_LEFT); + new_desktop_input->labelfont(FL_BOLD); + Fl_Return_Button* b = new Fl_Return_Button(100,60,80,20,"OK"); + b->callback(new_desktop_ok_cb); + Fl_Button* b2 = new Fl_Button(10,60,80,20,"Cancel"); + b2->callback(cancel_cb); + w->set_non_modal(); + w->end(); + } + char buf[120]; + sprintf(buf, "Desktop %d", Desktop::available_number()); + new_desktop_input->value(buf); + new_desktop_input->window()->hotspot(new_desktop_input); + new_desktop_input->window()->show(); +} + +#else // !ASK_FOR_NEW_DESKTOP_NAME + +static void +new_desktop_cb(Fl_Widget*, void*) +{ + char buf[120]; + int i = Desktop::available_number(); + sprintf(buf, "Desktop %d", i); + Desktop::current(new Desktop(buf, i)); +} + +#endif + +#endif +//////////////////////////////////////////////////////////////// + +static void +exit_cb(Fl_Widget*, void*) +{ + Frame::save_protocol(); + exit(0); +} + +static void +logout_cb(Fl_Widget*, void*) +{ + static FrameWindow* w; + if (!w) { + w = new FrameWindow(190,90); + Fl_Box* l = new Fl_Box(0, 0, 190, 60, "Really log out?"); + l->labelfont(FL_BOLD); + Fl_Return_Button* b = new Fl_Return_Button(100,60,80,20,"OK"); + b->callback(exit_cb); + Fl_Button* b2 = new Fl_Button(10,60,80,20,"Cancel"); + b2->callback(cancel_cb); + w->set_non_modal(); + w->end(); + } + w->hotspot(w); + w->show(); +} + +//////////////////////////////////////////////////////////////// + +#include +#include +#include + +#if XTERM_MENU_ITEM || WMX_MENU_ITEMS + +static const char* xtermname = "xterm"; + +static void +spawn_cb(Fl_Widget*, void*n) +{ + char* name = (char*)n; + // strange code thieved from 9wm to avoid leaving zombies + if (fork() == 0) { + if (fork() == 0) { + close(ConnectionNumber(fl_display)); + if (name == xtermname) execlp(name, name, "-ut", 0); + else execl(name, name, 0); + fprintf(stderr, "flwm: can't run %s, %s\n", name, strerror(errno)); + XBell(fl_display, 70); + exit(1); + } + exit(0); + } + wait((int *) 0); +} + +#endif + +static Fl_Menu_Item other_menu_items[] = { +#if XTERM_MENU_ITEM + {"New xterm", 0, spawn_cb, (void*)xtermname, 0, 0, 0, 12}, +#endif +#if DESKTOPS + {"New desktop", 0, new_desktop_cb, 0, 0, 0, 0, 12}, +#endif + {"Logout", 0, logout_cb, 0, 0, 0, 0, 12}, + {0}}; +#define num_other_items (sizeof(other_menu_items)/sizeof(Fl_Menu_Item)) + +// use this to fill in a menu location: +static void +init(Fl_Menu_Item& m, const char* data) +{ +#ifdef HAVE_STYLES + m.style = 0; +#endif + m.label(data); + m.flags = 0; + m.labeltype(FL_NORMAL_LABEL); + m.shortcut(0); + m.labelfont(MENU_FONT_SLOT); + m.labelsize(MENU_FONT_SIZE); + m.labelcolor(FL_BLACK); +} + +#if WMX_MENU_ITEMS + +// wmxlist is an array of char* pointers (for efficient sorting purposes), +// which are stored in wmxbuffer (for memory efficiency and to avoid +// freeing and fragmentation) +static char** wmxlist = NULL; +static int wmxlistsize = 0; +// wmx commands are read from ~/.wmx, +// they are stored null-separated here: +static char* wmxbuffer = NULL; +static int wmxbufsize = 0; +static int num_wmx = 0; +static time_t wmx_time = 0; +static int wmx_pathlen = 0; + +static int +scan_wmx_dir (char *path, int bufindex, int nest) +{ + DIR* dir = opendir(path); + struct stat st; + int pathlen = strlen (path); + if (dir) { + struct dirent* ent; + while ((ent=readdir(dir))) { + if (ent->d_name[0] == '.') + continue; + strcpy(path+pathlen, ent->d_name); + if (stat(path, &st) < 0) continue; + int len = pathlen+strlen(ent->d_name); + // worst-case alloc needs + if (bufindex+len+nest+1 > wmxbufsize) + wmxbuffer = (char*)realloc(wmxbuffer, (wmxbufsize+=1024)); + for (int i=0; i toupper(*pB)) + return(1); + if (toupper(*pA) < toupper(*pB)) + return(-1); + pA++; + pB++; + } + if (*pA) + return(1); + if (*pB) + return(-1); + return(0); +} + +static void +load_wmx() +{ + const char* home=getenv("HOME"); if (!home) home = "."; + char path[1024]; + strcpy(path, home); + if (path[strlen(path)-1] != '/') strcat(path, "/"); + strcat(path, ".wmx/"); + struct stat st; if (stat(path, &st) < 0) return; + if (st.st_mtime == wmx_time) return; + wmx_time = st.st_mtime; + num_wmx = 0; + wmx_pathlen = strlen(path); + scan_wmx_dir(path, 0, 0); + + // Build wmxlist + if (num_wmx > wmxlistsize) { + if (wmxlist) + delete [] wmxlist; + wmxlist = new char *[num_wmx]; + wmxlistsize = num_wmx; + } + for (int i=0; itransient_for()) + if (a == c) return 1; + return 0; +} + +void +ShowTabMenu(int tab) +{ + + static char beenhere; + if (!beenhere) { + beenhere = 1; + Fl::set_labeltype(FRAME_LABEL, frame_label_draw, frame_label_measure); + Fl::set_labeltype(TEXT_LABEL, label_draw, label_measure); + if (exit_flag) { + Fl_Menu_Item* m = other_menu_items+num_other_items-2; + m->label("Exit"); + m->callback(exit_cb); + } + } + + static Fl_Menu_Item* menu = 0; + static int arraysize = 0; + +#if DESKTOPS + int one_desktop = !Desktop::first->next; +#endif + + // count up how many items are on the menu: + + int n = num_other_items; +#if WMX_MENU_ITEMS + load_wmx(); + if (num_wmx) { + n -= 1; // delete "new xterm" + // add wmx items + int level = 0; + for (int i=0; i level) + level++; + n++; + } + } +#endif + +#if DESKTOPS + // count number of items per desktop in these variables: + int numsticky = 0; + Desktop* d; + for (d = Desktop::first; d; d = d->next) d->junk = 0; +#endif + + // every frame contributes 1 item: + Frame* c; + for (c = Frame::first; c; c = c->next) { + if (c->state() == UNMAPPED || c->transient_for()) continue; +#if DESKTOPS + if (!c->desktop()) { + numsticky++; + } else { + c->desktop()->junk++; + } +#endif + n++; + } + +#if DESKTOPS + if (!one_desktop) { + // add the sticky "desktop": + n += 2; if (!numsticky) n++; + if (Desktop::current()) { + n += numsticky; + Desktop::current()->junk += numsticky; + } + // every desktop contributes menu title, null terminator, + // and possible delete: + for (d = Desktop::first; d; d = d->next) { + n += 2; if (!d->junk) n++; + } + } +#endif + + if (n > arraysize) { + delete[] menu; + menu = new Fl_Menu_Item[arraysize = n]; + } + + // build the menu: + n = 0; + const Fl_Menu_Item* preset = 0; + const Fl_Menu_Item* first_on_desk = 0; +#if DESKTOPS + if (one_desktop) { +#endif + for (c = Frame::first; c; c = c->next) { + if (c->state() == UNMAPPED || c->transient_for()) continue; + init(menu[n],(char*)c); + menu[n].labeltype(FRAME_LABEL); + menu[n].callback(frame_callback, c); + if (is_active_frame(c)) preset = menu+n; + n++; + } + if (n > 0) first_on_desk = menu; +#if DESKTOPS + } else for (d = Desktop::first; ; d = d->next) { + // this loop adds the "sticky" desktop last, when d==0 + if (d == Desktop::current()) preset = menu+n; + init(menu[n], d ? d->name() : "Sticky"); + menu[n].callback(desktop_cb, d); + menu[n].flags = FL_SUBMENU; + n++; + if (d && !d->junk) { + init(menu[n],"delete this desktop"); + menu[n].callback(delete_desktop_cb, d); + n++; + } else if (!d && !numsticky) { + init(menu[n],"(empty)"); + menu[n].callback_ = 0; + menu[n].deactivate(); + n++; + } else { + if (d == Desktop::current()) first_on_desk = menu+n; + for (c = Frame::first; c; c = c->next) { + if (c->state() == UNMAPPED || c->transient_for()) continue; + if (c->desktop() == d || !c->desktop() && d == Desktop::current()) { + init(menu[n],(char*)c); + menu[n].labeltype(FRAME_LABEL); + menu[n].callback(d == Desktop::current() ? + frame_callback : move_frame_callback, c); + if (d == Desktop::current() && is_active_frame(c)) preset = menu+n; + n++; + } + } + } + menu[n].label(0); n++; // terminator for submenu + if (!d) break; + } +#endif + + // For ALT+Tab, move the selection forward or backward: + if (tab > 0 && first_on_desk) { + if (!preset) + preset = first_on_desk; + else { + preset++; + if (!preset->label() || preset->callback_ != frame_callback) + preset = first_on_desk; + } + } else if (tab < 0 && first_on_desk) { + if (preset && preset != first_on_desk) + preset--; + else { + // go to end of menu + preset = first_on_desk; + while (preset[1].label() && preset[1].callback_ == frame_callback) + preset++; + } + } + +#if WMX_MENU_ITEMS + // put wmx-style commands above that: + if (num_wmx > 0) { + char* cmd; + int pathlen[MAX_NESTING_DEPTH]; + int level = 0; + pathlen[0] = wmx_pathlen; + for (int i = 0; i < num_wmx; i++) { + cmd = wmxlist[i]; + cmd += strspn(cmd, "/")-1; + init(menu[n], cmd+pathlen[level]); +#if DESKTOPS + if (one_desktop) +#endif + if (!level) + menu[n].labeltype(TEXT_LABEL); + + int nextlev = (i==num_wmx-1)?0:strspn(wmxlist[i+1], "/")-1; + if (nextlev < level) { + menu[n].callback(spawn_cb, cmd); + // Close 'em off + for (; level>nextlev; level--) + init(menu[++n], 0); + } else if (nextlev > level) { + // This should be made a submenu + pathlen[++level] = strlen(cmd)+1; // extra for next trailing / + menu[n].flags = FL_SUBMENU; + menu[n].callback((Fl_Callback*)0); + } else { + menu[n].callback(spawn_cb, cmd); + } + n++; + } + } + + // put the fixed menu items at the bottom: +#if XTERM_MENU_ITEM + if (num_wmx) // if wmx commands, delete the built-in xterm item: + memcpy(menu+n, other_menu_items+1, sizeof(other_menu_items)-sizeof(Fl_Menu_Item)); + else +#endif +#endif + memcpy(menu+n, other_menu_items, sizeof(other_menu_items)); +#if DESKTOPS + if (one_desktop) +#endif + // fix the menus items so they are indented to align with window names: + while (menu[n].label()) menu[n++].labeltype(TEXT_LABEL); + + const Fl_Menu_Item* picked = + menu->popup(Fl::event_x(), Fl::event_y(), 0, preset); + if (picked && picked->callback()) picked->do_callback(0); +} + +void ShowMenu() {ShowTabMenu(0);} diff --git a/README b/README new file mode 100644 index 0000000..5214641 --- /dev/null +++ b/README @@ -0,0 +1,121 @@ +flwm Version 0.25 + +---------------------------------------------------------------- +How to compile flwm: +---------------------------------------------------------------- + +You need fltk. If you do not have it yet, download it from +http://fltk.easysw.com, and compile and install it. + +To customize flwm (for instance to turn on click-to-type), edit the +config.h file. + +Type "./configure" (not necessary if you have gmake) + +Type "make" + +Become superuser and type "make install" + +If you wish to edit the code, type "make depend" + +---------------------------------------------------------------- +How to run flwm: +---------------------------------------------------------------- + +Flwm should be run by X when it logs you in. This is done by putting +a call to flwm into the file ~/.xinitrc. With any luck you already +have this file. If not try copying /usr/X11/lib/X11/xinit/xinitrc. +Edit the file and try to remove any call to another window manager +(these are usually near the end). + +Recommended contents of ~/.xinitrc: + +#!/bin/sh +xsetroot -solid \#006060 +xrdb .Xresources +# +flwm & +WindowManager=$! +# +wait $WindowManager + +ALLOWING THE WINDOW MANAGER TO EXIT W/O LOGOUT: + +That is the most user-friendly but it logs you out when flwm exits, +which means it logs out if flwm crashes (:-)) and you cannot switch +window managers. Another possibility is to run another program last +so flwm can exit, by putting lines like this at the end: + +/usr/local/bin/flwm -x & +exec rxvt -geometry 80x11+8-8 -C -T "Ctrl-D_to_logout" + +The -x tells flwm to put "exit" on the menu rather than "logout". + +REDHAT USERS: + +You may want to run the program "./flwm_wmconfig". This will read +RedHat's window manager menu configuration files and build an initial +.wmx directory so you have a large set of menu items that run +programs. + +SGI IRIX: + +You need to edit the file ~/.xsession instead of ~/.xinitrc. + +SGI's version of XDM has a nice feature so that the window manager can +still have a logout command, but you are not logged out if it +crashes. This is done by running the programs "reaper" and +"endsession", as in this sample .xsession file: + +#! /bin/sh +xsetroot -solid \#004040 +xrdb .Xresources +reaper +flwm -x & +xwsh -console -t console -iconic & + +Also create the file "~/.wmx/Logout" with these contents: + +#! /bin/sh +endsession + +The result will be that flwm has a menu itme "Logout" that logs you +out. + +---------------------------------------------------------------- +Usage: +---------------------------------------------------------------- + +Type "man flwm" for the manual page. + +---------------------------------------------------------------- +Acknoledgements +---------------------------------------------------------------- + +This program was inspired by and much code copied from the "wm2" +window manager by Chris Cannam + +Code contributions by Steve );Hara-Smith + +---------------------------------------------------------------- +Copyright (C) 1998-1999 Bill Spitzak +---------------------------------------------------------------- +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or (at +your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA. + +Written by Bill Spitzak spitzak@d2.com +---------------------------------------------------------------- +END +---------------------------------------------------------------- diff --git a/Rotated.C b/Rotated.C new file mode 100644 index 0000000..daa4d43 --- /dev/null +++ b/Rotated.C @@ -0,0 +1,427 @@ +// Rotated text drawing with X. + +// Original code: +// Copyright (c) 1992 Alan Richardson (mppa3@uk.ac.sussex.syma) */ +// +// Modifications for fltk: +// Copyright (c) 1997 Bill Spitzak (spitzak@d2.com) +// Modifications are to draw using the current fl_font. All fonts +// used are cached in local structures. This can get real expensive, +// use " + +/* xvertext, Copyright (c) 1992 Alan Richardson (mppa3@uk.ac.sussex.syma) + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and without fee is hereby granted, provided + * that the above copyright notice appear in all copies and that both the + * copyright notice and this permission notice appear in supporting + * documentation. All work developed as a consequence of the use of + * this program should duly acknowledge such use. No representations are + * made about the suitability of this software for any purpose. It is + * provided "as is" without express or implied warranty. + */ + +// if not defined then portions not used by flwm are included: +#define FLWM 1 + +/* ********************************************************************** */ + +#include +#include +#include "Rotated.H" +#include +#include +#include + +struct BitmapStruct { + int bit_w; + int bit_h; + Pixmap bm; +}; + +struct XRotCharStruct { + int ascent; + int descent; + int lbearing; + int rbearing; + int width; + BitmapStruct glyph; +}; + +struct XRotFontStruct { + int dir; + int height; + int max_ascent; + int max_descent; + int max_char; + int min_char; + XFontStruct* xfontstruct; + XRotCharStruct per_char[256]; +}; + +/* *** Load the rotated version of a given font *** */ + +static XRotFontStruct* +XRotLoadFont(Display *dpy, XFontStruct* fontstruct, int dir) +{ + char val; + XImage *I1, *I2; + Pixmap canvas; + Window root; + int screen; + GC font_gc; + char text[3];/*, errstr[300];*/ + + XRotFontStruct *rotfont; + int ichar, i, j, index, boxlen = 60; + int vert_w, vert_h, vert_len, bit_w, bit_h, bit_len; + int min_char, max_char; + unsigned char *vertdata, *bitdata; + int ascent, descent, lbearing, rbearing; + int on = 1, off = 0; + + /* useful macros ... */ + screen = DefaultScreen(dpy); + root = DefaultRootWindow(dpy); + + /* create the depth 1 canvas bitmap ... */ + canvas = XCreatePixmap(dpy, root, boxlen, boxlen, 1); + + /* create a GC ... */ + font_gc = XCreateGC(dpy, canvas, 0, 0); + XSetBackground(dpy, font_gc, off); + + XSetFont(dpy, font_gc, fontstruct->fid); + + /* allocate space for rotated font ... */ + rotfont = (XRotFontStruct *)malloc((unsigned)sizeof(XRotFontStruct)); + + /* determine which characters are defined in font ... */ + min_char = fontstruct->min_char_or_byte2; + if (min_char<0) min_char = 0; + rotfont->min_char = min_char; + max_char = fontstruct->max_char_or_byte2; + if (max_char>255) max_char = 255; + rotfont->max_char = max_char; + + /* some overall font data ... */ + rotfont->dir = dir; + rotfont->max_ascent = fontstruct->max_bounds.ascent; + rotfont->max_descent = fontstruct->max_bounds.descent; + rotfont->height = rotfont->max_ascent+rotfont->max_descent; + + rotfont->xfontstruct = fontstruct; + /* remember xfontstruct for `normal' text ... */ + if (dir != 0) { + /* font needs rotation ... */ + /* loop through each character ... */ + for (ichar = min_char; ichar <= max_char; ichar++) { + + index = ichar-fontstruct->min_char_or_byte2; + + /* per char dimensions ... */ + ascent = rotfont->per_char[ichar].ascent = + fontstruct->per_char[index].ascent; + descent = rotfont->per_char[ichar].descent = + fontstruct->per_char[index].descent; + lbearing = rotfont->per_char[ichar].lbearing = + fontstruct->per_char[index].lbearing; + rbearing = rotfont->per_char[ichar].rbearing = + fontstruct->per_char[index].rbearing; + rotfont->per_char[ichar].width = + fontstruct->per_char[index].width; + + /* some space chars have zero body, but a bitmap can't have ... */ + if (!ascent && !descent) + ascent = rotfont->per_char[ichar].ascent = 1; + if (!lbearing && !rbearing) + rbearing = rotfont->per_char[ichar].rbearing = 1; + + /* glyph width and height when vertical ... */ + vert_w = rbearing-lbearing; + vert_h = ascent+descent; + + /* width in bytes ... */ + vert_len = (vert_w-1)/8+1; + + XSetForeground(dpy, font_gc, off); + XFillRectangle(dpy, canvas, font_gc, 0, 0, boxlen, boxlen); + + /* draw the character centre top right on canvas ... */ + sprintf(text, "%c", ichar); + XSetForeground(dpy, font_gc, on); + XDrawImageString(dpy, canvas, font_gc, boxlen/2 - lbearing, + boxlen/2 - descent, text, 1); + + /* reserve memory for first XImage ... */ + vertdata = (unsigned char *) malloc((unsigned)(vert_len*vert_h)); + + /* create the XImage ... */ + I1 = XCreateImage(dpy, DefaultVisual(dpy, screen), 1, XYBitmap, + 0, (char *)vertdata, vert_w, vert_h, 8, 0); + +// if (I1 == NULL) ... do something here + + I1->byte_order = I1->bitmap_bit_order = MSBFirst; + + /* extract character from canvas ... */ + XGetSubImage(dpy, canvas, boxlen/2, boxlen/2-vert_h, + vert_w, vert_h, 1, XYPixmap, I1, 0, 0); + I1->format = XYBitmap; + + /* width, height of rotated character ... */ + if (dir == 2) { + bit_w = vert_w; + bit_h = vert_h; + } else { + bit_w = vert_h; + bit_h = vert_w; + } + + /* width in bytes ... */ + bit_len = (bit_w-1)/8 + 1; + + rotfont->per_char[ichar].glyph.bit_w = bit_w; + rotfont->per_char[ichar].glyph.bit_h = bit_h; + + /* reserve memory for the rotated image ... */ + bitdata = (unsigned char *)calloc((unsigned)(bit_h*bit_len), 1); + + /* create the image ... */ + I2 = XCreateImage(dpy, DefaultVisual(dpy, screen), 1, XYBitmap, 0, + (char *)bitdata, bit_w, bit_h, 8, 0); + +// if (I2 == NULL) ... error + + I2->byte_order = I2->bitmap_bit_order = MSBFirst; + + /* map vertical data to rotated character ... */ + for (j = 0; j < bit_h; j++) { + for (i = 0; i < bit_w; i++) { + /* map bits ... */ + if (dir == 1) + val = vertdata[i*vert_len + (vert_w-j-1)/8] & + (128>>((vert_w-j-1)%8)); + + else if (dir == 2) + val = vertdata[(vert_h-j-1)*vert_len + (vert_w-i-1)/8] & + (128>>((vert_w-i-1)%8)); + + else + val = vertdata[(vert_h-i-1)*vert_len + j/8] & + (128>>(j%8)); + + if (val) + bitdata[j*bit_len + i/8] = bitdata[j*bit_len + i/8] | + (128>>(i%8)); + } + } + + /* create this character's bitmap ... */ + rotfont->per_char[ichar].glyph.bm = + XCreatePixmap(dpy, root, bit_w, bit_h, 1); + + /* put the image into the bitmap ... */ + XPutImage(dpy, rotfont->per_char[ichar].glyph.bm, + font_gc, I2, 0, 0, 0, 0, bit_w, bit_h); + + /* free the image and data ... */ + XDestroyImage(I1); + XDestroyImage(I2); + /* free((char *)bitdata); -- XDestroyImage does this + free((char *)vertdata);*/ + } + + } + + for (ichar = 0; ichar < min_char; ichar++) + rotfont->per_char[ichar] = rotfont->per_char['?']; + for (ichar = max_char+1; ichar < 256; ichar++) + rotfont->per_char[ichar] = rotfont->per_char['?']; + + /* free pixmap and GC ... */ + XFreePixmap(dpy, canvas); + XFreeGC(dpy, font_gc); + + return rotfont; +} + +/* *** Free the resources associated with a rotated font *** */ + +static void XRotUnloadFont(Display *dpy, XRotFontStruct *rotfont) +{ + int ichar; + + if (rotfont->dir != 0) { + /* loop through each character, freeing its pixmap ... */ + for (ichar = rotfont->min_char; ichar <= rotfont->max_char; ichar++) + XFreePixmap(dpy, rotfont->per_char[ichar].glyph.bm); + } + /* rotfont should never be referenced again ... */ + free((char *)rotfont); +} + +/* ---------------------------------------------------------------------- */ + +/* *** A front end to XRotPaintString : mimics XDrawString *** */ + +static void +XRotDrawString(Display *dpy, XRotFontStruct *rotfont, Drawable drawable, + GC gc, int x, int y, const char *str, int len) +{ + int i, xp, yp, dir, ichar; + + if (str == NULL || len<1) return; + + dir = rotfont->dir; + + /* a horizontal string is easy ... */ + if (dir == 0) { + XSetFont(dpy, gc, rotfont->xfontstruct->fid); + XDrawString(dpy, drawable, gc, x, y, str, len); + return; + } + + /* vertical or upside down ... */ + + XSetFillStyle(dpy, gc, FillStippled); + + /* loop through each character in string ... */ + for (i = 0; iper_char[ichar].ascent; + yp = y-rotfont->per_char[ichar].rbearing; + } + else if (dir == 2) { + xp = x-rotfont->per_char[ichar].rbearing; + yp = y-rotfont->per_char[ichar].descent+1; + } + else { + xp = x-rotfont->per_char[ichar].descent+1; + yp = y+rotfont->per_char[ichar].lbearing; + } + + /* draw the glyph ... */ + XSetStipple(dpy, gc, rotfont->per_char[ichar].glyph.bm); + + XSetTSOrigin(dpy, gc, xp, yp); + + XFillRectangle(dpy, drawable, gc, xp, yp, + rotfont->per_char[ichar].glyph.bit_w, + rotfont->per_char[ichar].glyph.bit_h); + + /* advance position ... */ + if (dir == 1) + y -= rotfont->per_char[ichar].width; + else if (dir == 2) + x -= rotfont->per_char[ichar].width; + else + y += rotfont->per_char[ichar].width; + } + XSetFillStyle(dpy, gc, FillSolid); +} + +#ifndef FLWM +/* *** Return the width of a string *** */ + +static int XRotTextWidth(XRotFontStruct *rotfont, const char *str, int len) +{ + int i, width = 0, ichar; + + if (str == NULL) return 0; + + if (rotfont->dir == 0) + width = XTextWidth(rotfont->xfontstruct, str, strlen(str)); + + else + for (i = 0; iper_char[((unsigned char*)str)[i]].width; + } + + return width; +} +#endif + +/* ---------------------------------------------------------------------- */ + +// the public functions use the fltk global variables for font & gc: + +static XRotFontStruct* font; + +void draw_rotated(const char* text, int n, int x, int y, int angle) { + if (!text || !*text) return; + /* make angle positive ... */ + if (angle < 0) do angle += 360; while (angle < 0); + /* get nearest vertical or horizontal direction ... */ + int dir = ((angle+45)/90)%4; + + if (font && font->xfontstruct == fl_xfont && font->dir == dir) { + ; + } else { + if (font) XRotUnloadFont(fl_display, font); + font = XRotLoadFont(fl_display, fl_xfont, dir); + } + XRotDrawString(fl_display, font, fl_window, fl_gc, x, y, text, n); +} + +#ifndef FLWM +void draw_rotated(const char* text, int x, int y, int angle) { + if (!text || !*text) return; + draw_rotated(text, strlen(text), x, y, angle); +} +#endif + +static void draw_rot90(const char* str, int n, int x, int y) { + draw_rotated(str, n, y, -x, 90); +} +void draw_rotated90( + const char* str, // the (multi-line) string + int x, int y, int w, int h, // bounding box + Fl_Align align) { + if (!str || !*str) return; + if (w && h && !fl_not_clipped(x, y, w, h)) return; + if (align & FL_ALIGN_CLIP) fl_clip(x, y, w, h); + int a1 = align&(-16); + if (align & FL_ALIGN_LEFT) a1 |= FL_ALIGN_TOP; + if (align & FL_ALIGN_RIGHT) a1 |= FL_ALIGN_BOTTOM; + if (align & FL_ALIGN_TOP) a1 |= FL_ALIGN_RIGHT; + if (align & FL_ALIGN_BOTTOM) a1 |= FL_ALIGN_LEFT; + fl_draw(str, -(y+h), x, h, w, (Fl_Align)a1, draw_rot90); + if (align & FL_ALIGN_CLIP) fl_pop_clip(); +} + +#ifndef FLWM +static void draw_rot180(const char* str, int n, int x, int y) { + draw_rotated(str, n, -x, -y, 180); +} +void draw_rotated180( + const char* str, // the (multi-line) string + int x, int y, int w, int h, // bounding box + Fl_Align align) { + int a1 = align&(-16); + if (align & FL_ALIGN_LEFT) a1 |= FL_ALIGN_RIGHT; + if (align & FL_ALIGN_RIGHT) a1 |= FL_ALIGN_LEFT; + if (align & FL_ALIGN_TOP) a1 |= FL_ALIGN_BOTTOM; + if (align & FL_ALIGN_BOTTOM) a1 |= FL_ALIGN_TOP; + fl_draw(str, -(x+w), -(y+h), w, h, (Fl_Align)a1, draw_rot180); +} + +static void draw_rot270(const char* str, int n, int x, int y) { + draw_rotated(str, n, -y, x, 270); +} +void draw_rotated270( + const char* str, // the (multi-line) string + int x, int y, int w, int h, // bounding box + Fl_Align align) { + int a1 = align&(-16); + if (align & FL_ALIGN_LEFT) a1 |= FL_ALIGN_BOTTOM; + if (align & FL_ALIGN_RIGHT) a1 |= FL_ALIGN_TOP; + if (align & FL_ALIGN_TOP) a1 |= FL_ALIGN_LEFT; + if (align & FL_ALIGN_BOTTOM) a1 |= FL_ALIGN_RIGHT; + fl_draw(str, y, -(x+w), h, w, (Fl_Align)a1, draw_rot270); +} +#endif + diff --git a/Rotated.H b/Rotated.H new file mode 100644 index 0000000..025333e --- /dev/null +++ b/Rotated.H @@ -0,0 +1,18 @@ +// Rotated text drawing with X. + +// Original code: +// Copyright (c) 1992 Alan Richardson (mppa3@uk.ac.sussex.syma) */ +// +// Modifications for fltk: +// Copyright (c) 1997 Bill Spitzak (spitzak@d2.com) + +#ifndef Rotated_H +#define Rotated_H + +void draw_rotated(const char* text, int n, int x, int y, int angle); +void draw_rotated(const char* text, int x, int y, int angle); +void draw_rotated90(const char*, int x, int y, int w, int h, Fl_Align); +void draw_rotated270(const char*, int x, int y, int w, int h, Fl_Align); +void draw_rotated180(const char*, int x, int y, int w, int h, Fl_Align); + +#endif diff --git a/configure.in b/configure.in new file mode 100644 index 0000000..90c5298 --- /dev/null +++ b/configure.in @@ -0,0 +1,60 @@ +dnl# -*- sh -*- +dnl# the "configure" script is made from this by running GNU "autoconf" + +AC_INIT(Frame.C) +AC_PROG_CC +AC_PROG_CXX +AC_PROG_INSTALL + +AC_PATH_XTRA +echo "Ignoring libraries \"$X_PRE_LIBS\" requested by configure." +dnl# LIBS="$LIBS$X_LIBS$X_PRE_LIBS" +LIBS="$LIBS$X_LIBS" + +MAKEDEPEND="\$(CXX) -M" + +dnl# add warnings and optimization to compiler switches: +dnl# do this last so messing with switches does not break tests +if test -n "$GXX"; then + # GNU C compiler + # -Wno-return-type is necessary for Xlib header files on many systems: + CFLAGS="$CFLAGS -Wall -Wno-return-type -O2 $X_CFLAGS" + CFLAGS_D="$CFLAGS -Wall -Wno-return-type -g -DDEBUG $X_CFLAGS" + CXXFLAGS="$CXXFLAGS -Wall -Wno-return-type -O2 $X_CFLAGS" + CXXFLAGS_D="$CXXFLAGS -Wall -Wno-return-type -g -DDEBUG $X_CFLAGS" +else +if test "`(uname) 2>/dev/null`" = IRIX; then + if expr "`(uname -r)`" \>= 6.2; then + # turn on new "n32" Irix compiler: + CXX="CC -n32" + CC="cc -n32" + LD="ld -n32" + # but -M is broken so use old compiler: + MAKEDEPEND="CC -M" + # -woff 3322 is necessary due to errors in Xlib headers on IRIX + CFLAGS="$CFLAGS -fullwarn -O2 $X_CFLAGS" + CFLAGS_D="$CFLAGS -fullwarn -gslim -DDEBUG $X_CFLAGS" + CXXFLAGS="$CXXFLAGS -fullwarn -woff 3322 -O2 $X_CFLAGS" + CXXFLAGS_D="$CXXFLAGS -fullwarn -woff 3322 -gslim -DDEBUG $X_CFLAGS" + else + # old Irix compiler: + CFLAGS="$CFLAGS -O2 $X_CFLAGS" + CFLAGS_D="$CFLAGS -g -DDEBUG $X_CFLAGS" + CXXFLAGS="$CXXFLAGS +w +pp -O2 $X_CFLAGS" + CXXFLAGS_D="$CXXFLAGS +w +pp -g -DDEBUG $X_CFLAGS" + fi +else + # generic C compiler: + CFLAGS="$CFLAGS -O $X_CFLAGS" + CFLAGS_D="$CFLAGS -g -DDEBUG $X_CFLAGS" + CXXFLAGS="$CXXFLAGS -O $X_CFLAGS" + CXXFLAGS_D="$CXXFLAGS -g -DDEBUG $X_CFLAGS" +fi +fi +AC_SUBST(MAKEDEPEND) +AC_SUBST(CFLAGS_D) +AC_SUBST(CXXFLAGS_D) +dnl# AC_CONFIG_HEADER(config.h:configh.in) +AC_OUTPUT(makeinclude) + +dnl# end of configure.in diff --git a/flwm.1 b/flwm.1 new file mode 100644 index 0000000..f41cdf8 --- /dev/null +++ b/flwm.1 @@ -0,0 +1,274 @@ +.\"Man page for flwm, by Bill Spitzak. +.TH flwm 1 "15 May 1999" +.SH NAME +\fIflwm\fR - The Fast Light Window Manager +.SH SYNOPSIS +.B flwm +[-d[isplay] host:n.n] [-g[eometry] WxH+X+Y] +[-fg color] [-bg color] [-bg2 color] +.SH DESCRIPTION +.I flwm +is a very small and fast X window manager, featuring +.I no +icons and "sideways" title bars. + +.SH .xinitrc + +Recommened contents of your ~/.xinitrc file: + +.nf +#!/bin/sh +xsetroot -solid \#006060 +xrdb .Xresources +# +flwm & +WindowManager=$! +# +wait $WindowManager +.fi + +You may instead use the standard of running the window manager last with +an "exec flwm" statement at the end of .xinitrc. + +.SH SWITCHES + +.B -d[isplay] host:#.# +Sets the display and screen for flwm to manage + +.B -v[isual] # +Visual number to use (probably only works for non-color-mapped ones) + +.B -g[eometry] WxH+X+Y +Flwm will act as though the screen is only the specified area. It +will constrain initial window positions to this area and stop them at +the edges when dragging them around. This can be used to surround the +screen with fixed "toolbars" that are never covered by windows. These +toolbars must be created by a program using override-redirect so that +flwm does not try to move them. + +.B -m[aximum] WxH +Set the size of windows when the maximize buttons are pushed. +Normally this is the size of the screen. This is useful for +XFree86 servers that are run with a smaller screen than display +memory. + +.B -x +The menu will say "Exit" instead of "Logout" and will not ask for +confirmation. This is a good idea if you are running flwm in some +other way than with exec at the end of .xinitrc, since it won't log +you out then. + +.B -fg color, -bg color +Set the label color and the color of the window frames and the +menu. + +.B -c[ursor] # +What cursor to use on the desktop (you will have to experiment to find +out what each number means) + +.B -cfg color, -cbg color +Colors for the desktop and window resizing cursors + +In addition to these switches there is much customization that can be +done by editing the config.h file in the source code and recompiling. +GCC is your friend. + +.SH MENU ITEMS + +Flwm can launch programs from it's menu. This is controlled by files +in the directory +.B ~/.wmx +(this was chosen to be compatible with wmx and wm2). + +Each executable file in ~/.wmx is a program to run. Usually these are +symbolic links to the real program or very short shell scripts. + +Each subdirectory creates a child menu so you can build a hierarchy +(up to 10 deep). + +Cut and paste the following lines you your shell to create some +example files: + +.nf +mkdir ~/.wmx +ln -s /usr/bin/gimp ~/.wmx/"The Gimp" +cat << EOF > ~/.wmx/"Terminal" +#! /bin/sh +/usr/local/bin/rxvt -ut +EOF +chmod +x !* +.fi + +RedHat users can run the program +.B flwm_wmconfig +to read the /etc/X11/wmconfig directory and produce an initial set of +menu items. + +.SH MOUSE USAGE + +.B Left-click +on a window border raises window. + +.B Left-drag +will move the window when in the title bar, and will resize it in the +edges. If the window cannot be resized then it will always move the +window. What it will do is indicated by the cursor shape. + +.B Middle-click +on a window border lowers it to bottom. + +.B Middle-drag +anywhere on window border will move the window. + +When you move a window it will stop at the edges of the screen. +Dragging about 150 pixels further will unstick it and let you drag it +off the screen. + +.B Right-click +on a window border pops up the menu. + +.B Any button +on the desktop will pop up the menu. + +.SH BUTTONS + +The empty button "iconizes" the window: it will completely vanish. To +get it back use the menu. + +The vertical-bar button "shades" (or "Venetian blinds"?) the window. +Click it again to restore the window. You can also resize the shaded +window to a new height or "open" it by resizing horizontally. + +The two buttons below it toggle maximum height and/or maximum width. + +The X button at the bottom closes the window. + +.SH MENU + +.B Right-click +on window border, or +.B any-click +on the desktop, or typing +.B Alt+Esc +or +.B Alt+Tab +or +.B Alt+Shift+Tab +will pop up the menu. + +Releasing Alt will pick the current menu item. This makes flwm work +very much (exactly?) like the Windows 95 shortcuts. + +Each main window is a menu item. If the window is "iconized" the +little picture shows an open rectangle, otherwise it shows a filled +rectangle. Picking a menu item deiconizes and raises that window and +warps the pointer so it is current. + +.B New desktop +asks for a name of a new desktop and makes it current. The desktop +will initially be empty (except for sticky items). + +To move windows to the current desktop, pop up the menu and pick +windows off of other desktops (if using the keyboard, use left +arrow to go to the desktop names, move up and down to the other +desktop, and use right arrow to enter that desktop). The window will +be moved from the other desktop to the current one. + +To switch to another desktop, pick the title of the desktop (if using +the keyboard, use left arrow to go to the desktop names, move up and +down to the other desktop). + +If a desktop is empty you can delete it. It's sub menu will show +.B delete this desktop. +Pick that and the desktop is gone. + +.B Sticky +is a special "desktop": windows on it appear on all desktops. To make +a window "sticky" switch to the Sticky desktop and pick the window off +it's current desktop (thus "moving" it to the Sticky desktop). To +"unstick" a window go to another desktop and pick the window off the +sticky desktop menu. + +.B New xterm +will run a new xterm on the current desktop. Useful if +you accidentally close everything. This item does not appear if a +~/.wmx directory exists. + +.B Logout +will ask for confirmation and if so flwm will exit. + +.B Exit +will exit flwm without confirmation. This item will appear if flwm +was run with the -x switch. + +.SH HOT KEYS + +These are the defaults, the hot keys may be different depending on how +flwm was compiled: + +.B Alt+Escape +Pops up the menu with the current window preselected + +.B Alt+Tab +Pops up the menu with the next window preselected + +.B Alt+Shift+Tab +Pops up the menu with the previous window preselected + +.B Ctrl+Tab +Switch to the next desktop. + +.B Ctrl+Shift+Tab +Switch to the previous desktop. + +.B Ctrl+Function key +Switch to desktop N. + +.B Alt+Up +Raise the current window. + +.B Alt+Down +Lower the current window. + +.B Alt+Delete +Close the current window (same as clicking close box). + +.B Alt+Enter +"Iconizes" (hides) the current window. + +.SH BUGS + +It is impossible to move windows smaller than 100 pixels off +the screen. + +Only obeys "keep aspect" if the aspect ratio is 1x1. + +.SH ACKNOWLEDGEMENTS + +This program was inspired by and much code copied from the "wm2" +window manager by Chris Cannam + +Thanks to Ron Koerner for the recursive .wmx directory reading code. + +.SH COPYRIGHT + +Copyright (C) 1999 Bill Spitzak + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or (at +your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA. + +.SH AUTHORS + +Written by Bill Spitzak spitzak@d2.com diff --git a/flwm_wmconfig b/flwm_wmconfig new file mode 100755 index 0000000..e8af6c6 --- /dev/null +++ b/flwm_wmconfig @@ -0,0 +1,65 @@ +#! /usr/bin/tcl + +# flwm_wmconfig reads the RedHat "/etc/X11/wmconfig" directory (and +# the ~/.wmconfig directory) and builds a ~/.wmx directory from it so +# that you have a big complex menu in flwm! + +set count 0 + +proc read_wmfile {fname} { + global count + global env + if [catch {set f [open $fname]} message] { + puts $message + } else { + set group "" + set name "" + set exec "" + while {[gets $f list]>=0} { + if [llength $list]<3 continue + set tag [lindex $list 1] + set value [lrange $list 2 1000] + if [llength $value]==1 {set value [lindex $value 0]} + if {$tag=="group"} {set group $value} + if {$tag=="name"} {set name $value} + if {$tag=="exec"} {set exec $value} + } + close $f + if {$group=="" || $name == "" || $exec == ""} { + puts "$fname is missing necessary data" + return + } + set dir $env(HOME)/.wmx/$group + set exec [string trimright $exec "& "] + catch {mkdir [list $dir]} + if [llength $exec]==1 { + if [catch {set command [exec which $exec]}] { + puts "$fname : can't find the program $exec" + return + } else { + catch {unlink [list $dir/$name]} + link -sym $command $dir/$name + } + } else { + set f [open $dir/$name "w"] + puts $f "#! /bin/sh" + puts $f "exec $exec" + close $f + chmod +x [list $dir/$name] + } + incr count + } +} + +if ![catch {set l [glob /etc/X11/wmconfig/*]}] { + foreach f $l {read_wmfile $f} +} + +if ![catch {set l [glob $env(HOME)/.wmconfig/*]}] { + foreach f $l {read_wmfile $f} +} + +if !$count { + error "No files found in /etc/X11/wmconfig or ~/.wmconfig" +} + diff --git a/main.C b/main.C new file mode 100644 index 0000000..91c7fd9 --- /dev/null +++ b/main.C @@ -0,0 +1,374 @@ +// Define "TEST" and it will compile to make a single fake window so +// you can test the window controls. +//#define TEST 1 + +#include "Frame.H" +#include +#include +#include +#include +#include +#include "config.h" +#ifdef SHOW_CLOCK +#include +#include +#endif + +//////////////////////////////////////////////////////////////// + +static const char* program_name; +static int initializing; + +static int xerror_handler(Display* d, XErrorEvent* e) { + if (initializing && (e->request_code == X_ChangeWindowAttributes) && + e->error_code == BadAccess) + Fl::fatal("Another window manager is running. You must exit it before running %s.", program_name); +#ifndef DEBUG + if (e->error_code == BadWindow) return 0; + if (e->error_code == BadColor) return 0; +#endif + char buf1[128], buf2[128]; + sprintf(buf1, "XRequest.%d", e->request_code); + XGetErrorDatabaseText(d,"",buf1,buf1,buf2,128); + XGetErrorText(d, e->error_code, buf1, 128); + Fl::warning("%s: %s: %s 0x%lx", program_name, buf2, buf1, e->resourceid); + return 0; +} + +//////////////////////////////////////////////////////////////// +// The Fl_Root class looks like a window to fltk but is actually the +// screen's root window. This is done by using set_xid to "show" it +// rather than have fltk create the window. + +class Fl_Root : public Fl_Window { + int handle(int); +public: + Fl_Root() : Fl_Window(0,0,Fl::w(),Fl::h()) {} + void show() { + if (!shown()) Fl_X::set_xid(this, RootWindow(fl_display, fl_screen)); + } +}; +Fl_Window *Root; + +extern void ShowMenu(); +extern int Handle_Hotkey(); +extern void Grab_Hotkeys(); + +int Fl_Root::handle(int e) { + if (e == FL_PUSH) { + ShowMenu(); + return 1; + } + return 0; +} + +#if CLICK_RAISES || CLICK_TO_TYPE +extern void click_raise(Frame*); +#endif + +// fltk calls this for any events it does not understand: +static int flwm_event_handler(int e) { + if (!e) { // XEvent that fltk did not understand. + Window window = fl_xevent->xany.window; + // unfortunately most of the redirect events put the interesting + // window id in a different place: + switch (fl_xevent->type) { + case CirculateNotify: + case CirculateRequest: + case ConfigureNotify: + case ConfigureRequest: + case CreateNotify: + case DestroyNotify: + case GravityNotify: + case MapNotify: + case MapRequest: + case ReparentNotify: + case UnmapNotify: + window = fl_xevent->xmaprequest.window; + } + for (Frame* c = Frame::first; c; c = c->next) + if (c->window() == window || fl_xid(c) == window) +#if CLICK_RAISES || CLICK_TO_TYPE + if (fl_xevent->type == ButtonPress) {click_raise(c); return 1;} + else +#endif + return c->handle(fl_xevent); + switch (fl_xevent->type) { + case ButtonPress: + printf("got a button press in main\n"); + return 0; + case ConfigureRequest: { + const XConfigureRequestEvent *e = &(fl_xevent->xconfigurerequest); + XConfigureWindow(fl_display, e->window, + e->value_mask&~(CWSibling|CWStackMode), + (XWindowChanges*)&(e->x)); + return 1;} + case MapRequest: { + const XMapRequestEvent* e = &(fl_xevent->xmaprequest); + (void)new Frame(e->window); + return 1;} + case KeyRelease: { + if (!Fl::grab()) return 0; + // see if they released the alt key: + unsigned long keysym = + XKeycodeToKeysym(fl_display, fl_xevent->xkey.keycode, 0); + if (keysym == FL_Alt_L || keysym == FL_Alt_R) { + Fl::e_keysym = FL_Enter; + return Fl::grab()->handle(FL_KEYBOARD); + } + return 0;} + } + } else if (e == FL_SHORTCUT) { +#if FL_MAJOR_VERSION == 1 && FL_MINOR_VERSION == 0 && FL_PATCH_VERSION < 3 + // make the tab keys work in the menus in older fltk's: + // (they do not cycle around however, so a new fltk is a good idea) + if (Fl::grab()) { + // make fltk's menus resond to tab + shift+tab: + if (Fl::event_key() == FL_Tab) { + if (Fl::event_state() & FL_SHIFT) goto J1; + Fl::e_keysym = FL_Down; + } else if (Fl::event_key() == 0xFE20) { + J1: Fl::e_keysym = FL_Up; + } else return 0; + return Fl::grab()->handle(FL_KEYBOARD); + } +#endif + return Handle_Hotkey(); + } + return 0; +} + +#if DESKTOPS +extern void init_desktops(); +extern Atom _win_workspace; +extern Atom _win_workspace_count; +extern Atom _win_workspace_names; +#endif + +extern Atom _win_state; +extern Atom _win_hints; + +#ifdef SHOW_CLOCK +int clock_period = 1; +int clock_oldmin = 61; +int clock_alarm_on = 0; +char clock_buf[80]; + +struct sigaction flwm_clock_alarm_start = {0,}, flwm_clock_alarm_stop = {0,}; + +void flwm_update_clock(void*) { + time_t newtime; + struct tm *tm_p; + + // get current time + time(&newtime); + tm_p = localtime(&newtime); + + // Update a window frame if necessary + if (Frame::activeFrame() && tm_p->tm_min != clock_oldmin) { + if (clock_oldmin != 61) + clock_period = 60; // now that we're in sync, only update 1/minute + clock_oldmin = tm_p->tm_min; + strftime(clock_buf, 80, SHOW_CLOCK, tm_p); + Frame::activeFrame()->redraw_clock(); + } + // Now reschedule the timeout + Fl::remove_timeout(flwm_update_clock); + Fl::add_timeout(clock_period, flwm_update_clock); +} + +void flwm_clock_alarm_on(int) { + clock_alarm_on = 1; + Frame::activeFrame()->redraw_clock(); +} + +void flwm_clock_alarm_off(int) { + clock_alarm_on = 0; + Frame::activeFrame()->redraw_clock(); +} +#endif + +static const char* cfg, *cbg; +static int cursor = FL_CURSOR_ARROW; + +static void color_setup(Fl_Color slot, const char* arg, ulong value) { + if (arg) { + XColor x; + if (XParseColor(fl_display, fl_colormap, arg, &x)) + value = ((x.red>>8)<<24)|((x.green>>8)<<16)|((x.blue)); + } + Fl::set_color(slot, value); +} + +static void initialize() { + + Display* d = fl_display; + +#ifdef TEST + Window w = XCreateSimpleWindow(d, root, + 100, 100, 200, 300, 10, + BlackPixel(fl_display, 0), +// WhitePixel(fl_display, 0)); + 0x1234); + Frame* frame = new Frame(w); + XSelectInput(d, w, + ExposureMask | StructureNotifyMask | + KeyPressMask | KeyReleaseMask | FocusChangeMask | + KeymapStateMask | + ButtonPressMask | ButtonReleaseMask | + EnterWindowMask | LeaveWindowMask /*|PointerMotionMask*/ + ); +#else + + Fl::add_handler(flwm_event_handler); + + // setting attributes on root window makes me the window manager: + initializing = 1; + XSelectInput(d, fl_xid(Root), + SubstructureRedirectMask | SubstructureNotifyMask | + ColormapChangeMask | PropertyChangeMask | + ButtonPressMask | ButtonReleaseMask | + EnterWindowMask | LeaveWindowMask | + KeyPressMask | KeyReleaseMask | KeymapStateMask); + color_setup(CURSOR_FG_SLOT, cfg, CURSOR_FG_COLOR<<8); + color_setup(CURSOR_BG_SLOT, cbg, CURSOR_BG_COLOR<<8); + Root->cursor((Fl_Cursor)cursor, CURSOR_FG_SLOT, CURSOR_BG_SLOT); + +#ifdef TITLE_FONT + Fl::set_font(TITLE_FONT_SLOT, TITLE_FONT); +#endif +#ifdef MENU_FONT + Fl::set_font(MENU_FONT_SLOT, MENU_FONT); +#endif +#ifdef ACTIVE_COLOR + Fl::set_color(FL_SELECTION_COLOR, ACTIVE_COLOR<<8); +#endif + + // Gnome crap: + // First create a window that can be watched to see if wm dies: + Atom a = XInternAtom(d, "_WIN_SUPPORTING_WM_CHECK", False); + Window win = XCreateSimpleWindow(d, fl_xid(Root), -200, -200, 5, 5, 0, 0, 0); + CARD32 val = win; + XChangeProperty(d, fl_xid(Root), a, XA_CARDINAL, 32, PropModeReplace, (uchar*)&val, 1); + XChangeProperty(d, win, a, XA_CARDINAL, 32, PropModeReplace, (uchar*)&val, 1); + // Next send a list of Gnome stuff we understand: + a = XInternAtom(d, "_WIN_PROTOCOLS", 0); + Atom list[10]; unsigned int i = 0; +//list[i++] = XInternAtom(d, "_WIN_LAYER", 0); + list[i++] = _win_state = XInternAtom(d, "_WIN_STATE", 0); + list[i++] = _win_hints = XInternAtom(d, "_WIN_HINTS", 0); +//list[i++] = XInternAtom(d, "_WIN_APP_STATE", 0); +//list[i++] = XInternAtom(d, "_WIN_EXPANDED_SIZE", 0); +//list[i++] = XInternAtom(d, "_WIN_ICONS", 0); +#if DESKTOPS + list[i++] = _win_workspace = XInternAtom(d, "_WIN_WORKSPACE", 0); + list[i++] = _win_workspace_count = XInternAtom(d, "_WIN_WORKSPACE_COUNT", 0); + list[i++] = _win_workspace_names = XInternAtom(d, "_WIN_WORKSPACE_NAMES", 0); +#endif +//list[i++] = XInternAtom(d, "_WIN_FRAME_LIST", 0); + XChangeProperty(d, fl_xid(Root), a, XA_ATOM, 32, PropModeReplace, (uchar*)list, i); + + Grab_Hotkeys(); + +#ifdef SHOW_CLOCK + Fl::add_timeout(clock_period, flwm_update_clock); + flwm_clock_alarm_start.sa_handler = &flwm_clock_alarm_on; + flwm_clock_alarm_stop.sa_handler = &flwm_clock_alarm_off; + sigaction(SIGALRM, &flwm_clock_alarm_start, NULL); + sigaction(SIGCONT, &flwm_clock_alarm_stop, NULL); +#endif + + XSync(d, 0); + initializing = 0; + +#if DESKTOPS + init_desktops(); +#endif + + // find all the windows and create a Frame for each: + unsigned int n; + Window w1, w2, *wins; + XWindowAttributes attr; + XQueryTree(d, fl_xid(Root), &w1, &w2, &wins, &n); + for (i = 0; i < n; ++i) { + XGetWindowAttributes(d, wins[i], &attr); + if (attr.override_redirect || !attr.map_state) continue; + (void)new Frame(wins[i],&attr); + } + XFree((void *)wins); + +#endif +} + +//////////////////////////////////////////////////////////////// + +extern int exit_flag; +extern int max_w_switch; +extern int max_h_switch; + +// consume a switch from argv. Returns number of words eaten, 0 on error: +int arg(int argc, char **argv, int &i) { + const char *s = argv[i]; + if (s[0] != '-') return 0; + s++; + + // do single-word switches: + if (!strcmp(s,"x")) { + exit_flag = 1; + i++; + return 1; + } + + // do switches with a value: + const char *v = argv[i+1]; + if (i >= argc-1 || !v) + return 0; // all the rest need an argument, so if missing it is an error + + if (!strcmp(s, "cfg")) { + cfg = v; + } else if (!strcmp(s, "cbg")) { + cbg = v; + } else if (*s == 'c') { + cursor = atoi(v); + } else if (*s == 'v') { + int visid = atoi(v); + fl_open_display(); + XVisualInfo templt; int num; + templt.visualid = visid; + fl_visual = XGetVisualInfo(fl_display, VisualIDMask, &templt, &num); + if (!fl_visual) Fl::fatal("No visual with id %d",visid); + fl_colormap = XCreateColormap(fl_display, RootWindow(fl_display,fl_screen), + fl_visual->visual, AllocNone); + } else if (*s == 'm') { + max_w_switch = atoi(v); + while (*v && *v++ != 'x'); + max_h_switch = atoi(v); + } else + return 0; // unrecognized + // return the fact that we consumed 2 switches: + i += 2; + return 2; +} + +int main(int argc, char** argv) { + program_name = filename_name(argv[0]); + int i; if (Fl::args(argc, argv, i, arg) < argc) Fl::error( +"options are:\n" +" -d[isplay] host:#.#\tX display & screen to use\n" +" -v[isual] #\t\tvisual to use\n" +" -g[eometry] WxH+X+Y\tlimits windows to this area\n" +" -m[aximum] WxH\t\tsize of maximized windows\n" +" -x\t\t\tmenu says Exit instead of logout\n" +" -bg color\t\tFrame color\n" +" -fg color\t\tLabel color\n" +" -bg2 color\t\tText field color\n" +" -c[ursor] #\t\tCursor number for root\n" +" -cfg color\t\tCursor color\n" +" -cbg color\t\tCursor outline color" +); + Root = new Fl_Root(); + Root->show(argc,argv); // fools fltk into using -geometry to set the size + XSetErrorHandler(xerror_handler); + initialize(); + return Fl::run(); +} diff --git a/makeinclude.in b/makeinclude.in new file mode 100644 index 0000000..28ae844 --- /dev/null +++ b/makeinclude.in @@ -0,0 +1,26 @@ +# @configure_input@ + +prefix =@prefix@ +exec_prefix =@exec_prefix@ +bindir =@bindir@ +mandir =@mandir@ +includedir =@includedir@ +libdir =@libdir@ +srcdir =@srcdir@ +VPATH =@srcdir@ + +# compiler names: +CXX =@CXX@ +CC =@CC@ +MAKEDEPEND =@MAKEDEPEND@ + +# flags for C++ compiler: +CFLAGS =@CFLAGS@ +CFLAGS_D =@CFLAGS_D@ +CXXFLAGS =@CXXFLAGS@ +CXXFLAGS_D =@CXXFLAGS_D@ + +# libraries to link with: +LDLIBS =@LIBS@ -lX11 -lXext @X_EXTRA_LIBS@ -lm + +INSTALL =@INSTALL@ diff --git a/rotated_test.C b/rotated_test.C new file mode 100644 index 0000000..2714376 --- /dev/null +++ b/rotated_test.C @@ -0,0 +1,118 @@ +// Test the xvertext routines for rotated text + +#include +#include +#include +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////// + +#include "Rotated.H" + +class RotText : public Fl_Box { + void draw(); +public: + RotText(int X, int Y, int W, int H, const char* L = 0) : + Fl_Box(X,Y,W,H,L) {} +}; + +void RotText::draw() { + draw_box(); + fl_color(FL_BLACK); + fl_font(labelfont(), labelsize()); + draw_rotated90(label(), x(), y(), w(), h(), align()); +} + +//////////////////////////////////////////////////////////////// + +Fl_Toggle_Button *leftb,*rightb,*topb,*bottomb,*insideb,*clipb,*wrapb; +RotText *text; +Fl_Input *input; +Fl_Hor_Value_Slider *fonts; +Fl_Hor_Value_Slider *sizes; +Fl_Double_Window *window; + +void button_cb(Fl_Widget *,void *) { + int i = 0; + if (leftb->value()) i |= FL_ALIGN_LEFT; + if (rightb->value()) i |= FL_ALIGN_RIGHT; + if (topb->value()) i |= FL_ALIGN_TOP; + if (bottomb->value()) i |= FL_ALIGN_BOTTOM; + if (insideb->value()) i |= FL_ALIGN_INSIDE; + if (clipb->value()) i |= FL_ALIGN_CLIP; + if (wrapb->value()) i |= FL_ALIGN_WRAP; + text->align(i); + window->redraw(); +} + +void font_cb(Fl_Widget *,void *) { + text->labelfont(int(fonts->value())); + window->redraw(); +} + +void size_cb(Fl_Widget *,void *) { + text->labelsize(int(sizes->value())); + window->redraw(); +} + +void input_cb(Fl_Widget *,void *) { + text->label(input->value()); + window->redraw(); +} + +int main(int argc, char **argv) { + window = new Fl_Double_Window(400,400); + + input = new Fl_Input(50,0,350,25); + input->static_value("The quick brown fox jumped over the lazy dog."); + input->when(FL_WHEN_CHANGED); + input->callback(input_cb); + + sizes= new Fl_Hor_Value_Slider(50,25,350,25,"Size:"); + sizes->align(FL_ALIGN_LEFT); + sizes->bounds(1,64); + sizes->step(1); + sizes->value(14); + sizes->callback(size_cb); + + fonts=new Fl_Hor_Value_Slider(50,50,350,25,"Font:"); + fonts->align(FL_ALIGN_LEFT); + fonts->bounds(0,15); + fonts->step(1); + fonts->value(0); + fonts->callback(font_cb); + + Fl_Group *g = new Fl_Group(0,0,0,0); + leftb = new Fl_Toggle_Button(50,75,50,25,"left"); + leftb->callback(button_cb); + rightb = new Fl_Toggle_Button(100,75,50,25,"right"); + rightb->callback(button_cb); + topb = new Fl_Toggle_Button(150,75,50,25,"top"); + topb->callback(button_cb); + bottomb = new Fl_Toggle_Button(200,75,50,25,"bottom"); + bottomb->callback(button_cb); + insideb = new Fl_Toggle_Button(250,75,50,25,"inside"); + insideb->callback(button_cb); + wrapb = new Fl_Toggle_Button(300,75,50,25,"wrap"); + wrapb->callback(button_cb); + clipb = new Fl_Toggle_Button(350,75,50,25,"clip"); + clipb->callback(button_cb); + g->resizable(insideb); + g->forms_end(); + + text= new RotText(100,225,200,100,input->value()); + text->box(FL_FRAME_BOX); + text->align(FL_ALIGN_CENTER); + window->resizable(text); + window->forms_end(); + window->show(argc,argv); + return Fl::run(); +} + +// +// End of "$Id$". +//