(* Copyright (C) 1992, Digital Equipment Corporation *) (* All rights reserved. *) (* See the file COPYRIGHT for a full description. *) (* *) (* by Steve Glassman, Mark Manasse and Greg Nelson *) (* Last modified on Sat Dec 12 17:35:49 PST 1992 by msm *) (* modified on Mon Feb 24 13:59:46 PST 1992 by muller *) <*PRAGMA LL*> UNSAFE MODULE XMessenger; IMPORT XClient, XClientF, TrestleClass, VBT, VBTClass, XEventQueue, X, Thread, Split, TrestleComm, VBTRep, Ctypes, Word, IntegerToRefanyTable, Rect, XProperties, Region, DpyFilter, TrestleOnX, MiscDetail, Text, Point; TYPE Closure = Thread.SizedClosure OBJECT xcon: XClient.T OVERRIDES apply := Messenger END; PROCEDURE Start (trsl: XClient.T; stackSize := 20000) = BEGIN EVAL Thread.Fork(NEW(Closure, xcon := trsl, stackSize := stackSize)); END Start; TYPE Last = RECORD x, y : INTEGER := 0; root : X.Window := X.None; time : X.Time := 0; button := -1; clickCount : CARDINAL := 0; safetyRadius, doubleClickInterval: CARDINAL := 0; keysym : X.KeySym := X.None; xcompstatus := X.XComposeStatus{NIL, 0}; END; (* last{x,y} = position of last mouseclick; lastRoot = root window of last mouseclick; lastTime = time of last mouseClick; lastClickCount = clickcount of last mouseclick, as defined in the VBT interface; lastButton = button that last went up or down. *) PROCEDURE Owns (ur: XClientF.Child; s: VBT.Selection): BOOLEAN = BEGIN RETURN s.sel < NUMBER(ur.owns^) AND ur.owns[s.sel] END Owns; PROCEDURE Messenger (self: Closure): REFANY RAISES {} = VAR evRec : X.XEvent; ev : X.XAnyEventStar := ADR(evRec); ra : REFANY; v : VBT.T; ur : XClientF.Child; last := Last{}; lost, takeFocus: BOOLEAN; xcon := self.xcon; <*FATAL XEventQueue.Exhausted, Split.NotAChild*> BEGIN TRY LOCK xcon DO XClientF.AdjustCoverage(xcon, 1) END; LOOP LOCK xcon DO XClientF.AdjustCoverage(xcon, -1); WHILE XEventQueue.IsEmpty(xcon.evq) AND NOT xcon.dead DO Thread.Wait(xcon, xcon.evc) END; IF xcon.dead THEN EXIT END; evRec := XEventQueue.Remove(xcon.evq); IF ev.type = X.MappingNotify THEN v := NIL; ur := NIL; WITH e = LOOPHOLE(ev, X.XMappingEventStar) DO IF e.request # X.MappingPointer THEN X.XRefreshKeyboardMapping(ADR(evRec)) END END; XClientF.SetUngrabs(xcon) ELSIF xcon.vbts.in(ev.window, ra) THEN v := ra; ur := v.upRef; IF ur # NIL THEN VAR owns := Owns(ur, VBT.KBFocus); ownsX := ur.isXFocus OR ur.inside AND ur.underXFocus; BEGIN lost := owns AND NOT ownsX; takeFocus := NOT owns AND ownsX AND ur.recentlyOutside END ELSE lost := FALSE; takeFocus := FALSE END; last.safetyRadius := 3 (* xcon.params.safetyRadius*); last.doubleClickInterval := 500 (* xcon.params.doubleClickInterval*); ELSE v := NIL; ur := NIL; lost := FALSE; takeFocus := FALSE END END; XClientF.AdjustCoverage(xcon, 1); IF ur # NIL OR (ev.type = X.EnterNotify OR ev.type = X.MotionNotify) AND ev.window = LOOPHOLE(ev, X.XMotionEventStar).root THEN HandleEvent(v, xcon, ur, last, lost, takeFocus, ev) END END EXCEPT TrestleComm.Failure => (* skip *) END; LOCK VBT.mu DO VAR vbts : IntegerToRefanyTable.T; junkI: INTEGER; junkR: REFANY; BEGIN LOCK xcon DO vbts := xcon.vbts.copy() END; EVAL vbts.enumerate(Disconnect, xcon, junkI, junkR) END END; RETURN NIL END Messenger; PROCEDURE HandleEvent ( v : VBT.T; xcon : XClient.T; ur : XClientF.Child; VAR last : Last; lost, takeFocus: BOOLEAN; ev : X.XAnyEventStar ) RAISES {TrestleComm.Failure} = VAR width, height: CARDINAL; junk : ARRAY [0 .. 0] OF Ctypes.Char; junkRef : REFANY; BEGIN LOCK VBT.mu DO IF v = NIL OR v.upRef # NIL THEN VBTRep.CoverRedisplay(); TRY CASE ev.type OF X.KeyPress, X.KeyRelease => WITH e = LOOPHOLE(ev, X.XKeyEventStar) DO LOCK xcon DO EVAL X.XLookupString( e, ADR(junk[0]), NUMBER(junk), ADR(last.keysym), ADR(last.xcompstatus)) END; VBTClass.Key( v, VBT.KeyRec{last.keysym, e.time, e.type = X.KeyPress, LOOPHOLE(e.state, VBT.Modifiers)}) END | X.ButtonPress, X.ButtonRelease => WITH e = LOOPHOLE(ev, X.XButtonEventStar) DO ButtonEvent(v, xcon, ur, last, e) END | X.EnterNotify, X.LeaveNotify => WITH e = LOOPHOLE(ev, X.XCrossingEventStar) DO EnterLeave(v, xcon, ur, lost, takeFocus, e) END | X.MotionNotify => (* WITH e = LOOPHOLE(ev, X.MotionEventStar) DO VAR cd: VBT.PositionRec; BEGIN cd.time := e.time; cd.modifiers := LOOPHOLE(e.state, VBT.Modifiers); cd.cp.pt.h := ... *) | X.FocusIn, X.FocusOut => IF lost THEN LOCK xcon DO XProperties.ExtendOwns(ur.owns, VBT.KBFocus); XProperties.ExtendSel(xcon.sel, VBT.KBFocus); ur.owns[VBT.KBFocus.sel] := FALSE; IF xcon.sel[VBT.KBFocus.sel].v = v THEN xcon.sel[VBT.KBFocus.sel].v := NIL END END; VBTClass.Misc( v, VBT.MiscRec{VBT.Lost, VBT.NullDetail, 0, VBT.KBFocus}) END | X.Expose, X.GraphicsExpose => DeliverBadRegion(v, ur) | X.DestroyNotify => IF ev.window = ur.w THEN XClientF.Delete(xcon, v, ur) ELSE LOCK xcon DO EVAL xcon.vbts.delete(ev.window, junkRef) END END | X.ConfigureNotify => WITH e = LOOPHOLE(ev, X.XConfigureEventStar) DO LOCK xcon DO XClientF.GetDomain(ur, width, height) END; XClientF.Reshape(v, width, height, e.send_event # X.False) END | X.UnmapNotify, X.MapNotify => LOCK xcon DO XClientF.GetDomain(ur, width, height) END; XClientF.Reshape(v, width, height) | X.SelectionClear => WITH e = LOOPHOLE(ev, X.XSelectionClearEventStar) DO FOR s := FIRST(xcon.sel^) TO LAST(xcon.sel^) DO IF xcon.sel[s].name = e.selection THEN VAR mustDeliver: BOOLEAN; BEGIN LOCK xcon DO XProperties.ExtendOwns(ur.owns, VBT.Selection{s}); mustDeliver := ur.owns[s]; ur.owns[s] := FALSE; IF xcon.sel[s].v = v THEN xcon.sel[s].v := NIL END END; IF mustDeliver THEN VBTClass.Misc( v, VBT.MiscRec{VBT.Lost, VBT.NullDetail, e.time, VBT.Selection{s}}) END END END END END | X.ClientMessage => WITH e = LOOPHOLE(ev, X.XClientMessageEventStar) DO ClientMessage(v, xcon, ur, takeFocus, e) END ELSE (* skip *) END (* CASE *) FINALLY VBTRep.UncoverRedisplay() END END (* IF v.ur # NIL *) END (* LOCK VBT.mu *) END HandleEvent; PROCEDURE ButtonEvent ( v : VBT.T; xcon: XClient.T; ur : XClientF.Child; VAR last: Last; e : X.XButtonEventStar) RAISES {TrestleComm.Failure} = VAR cd: VBT.MouseRec; mf := xcon.mouseFocus; CONST NonButtons = VBT.Modifiers{FIRST(VBT.Modifier).. LAST(VBT.Modifier)} - VBT.Buttons; BEGIN IF e.root = last.root AND Word.Minus(e.time, last.time) <= last.doubleClickInterval AND ABS(last.x - e.x) <= last.safetyRadius AND ABS(last.y - e.y) <= last.safetyRadius AND last.button = e.button THEN INC(last.clickCount) ELSE last.clickCount := 0; last.root := e.root; last.x := e.x; last.y := e.y; last.button := e.button END; last.time := e.time; cd.modifiers := LOOPHOLE(e.state, VBT.Modifiers); cd.whatChanged := FIRST(VBT.Button); INC(cd.whatChanged, e.button - X.Button1); IF e.type = X.ButtonPress THEN IF cd.modifiers <= NonButtons THEN cd.clickType := VBT.ClickType.FirstDown; xcon.mouseFocus := v; xcon.mouseFocusRoot := ScreenNumber(xcon, e.root) ELSE cd.clickType := VBT.ClickType.OtherDown END ELSE IF cd.modifiers <= NonButtons + VBT.Modifiers{cd.whatChanged} THEN cd.clickType := VBT.ClickType.LastUp; xcon.mouseFocus := NIL ELSE cd.clickType := VBT.ClickType.OtherUp END END; cd.time := e.time; cd.cp.pt.h := e.x; cd.cp.pt.v := e.y; cd.cp.offScreen := e.same_screen = X.False; LOCK xcon DO cd.cp.gone := cd.cp.offScreen OR e.subwindow # ur.w END; cd.cp.screen := ScreenNumber(xcon, e.root); cd.clickCount := last.clickCount; DeliverPosition(xcon, VBT.PositionRec{cd.cp, cd.time, cd.modifiers}, e.x_root, e.y_root, v, xcon.current, mf); VBTClass.Mouse(v, cd); IF mf # NIL AND mf # v THEN cd.cp.offScreen := e.root # xcon.mouseFocusRoot; cd.cp.pt.h := e.x_root; cd.cp.pt.v := e.y_root; cd.cp.gone := TRUE; IF NOT cd.cp.offScreen THEN VAR mfur: XClientF.Child := mf.upRef; BEGIN TrestleOnX.Enter(xcon); TRY XClientF.ValidateNW(xcon, mfur, mf.st); DEC(cd.cp.pt.h, mfur.nw.h); DEC(cd.cp.pt.v, mfur.nw.v) FINALLY TrestleOnX.Exit(xcon) END END END; VBTClass.Mouse(mf, cd) END; TrestleOnX.Enter(xcon); TRY FOR s := FIRST(xcon.sel^) TO LAST(xcon.sel^) DO WITH sr = xcon.sel[s] DO IF s = VBT.KBFocus.sel THEN IF sr.v = v AND ur.isXFocus THEN X.XSetInputFocus(xcon.dpy, ur.w, X.RevertToParent, e.time); sr.ts := e.time END ELSIF sr.v = v THEN X.XSetSelectionOwner(xcon.dpy, sr.name, ur.w, e.time); sr.ts := e.time END END END FINALLY TrestleOnX.Exit(xcon) END END ButtonEvent; PROCEDURE EnterLeave (v : VBT.T; xcon : XClient.T; ur : XClientF.Child; lost, takeFocus: BOOLEAN; e : X.XCrossingEventStar) RAISES {TrestleComm.Failure} = VAR cd: VBT.PositionRec; BEGIN cd.time := e.time; cd.modifiers := LOOPHOLE(e.state, VBT.Modifiers); cd.cp.pt.h := e.x; cd.cp.pt.v := e.y; cd.cp.gone := e.type = X.LeaveNotify; cd.cp.offScreen := e.same_screen = X.False; cd.cp.screen := ScreenNumber(xcon, e.root); IF cd.cp.gone AND v = xcon.current THEN xcon.current := NIL; DeliverPosition(xcon, cd, e.x_root, e.y_root, v, xcon.mouseFocus) ELSE VAR oc := xcon.current; BEGIN IF NOT cd.cp.gone AND v # NIL THEN xcon.current := v ELSE oc := NIL END; DeliverPosition( xcon, cd, e.x_root, e.y_root, v, oc, xcon.mouseFocus) END END; IF ur # NIL AND lost THEN LOCK xcon DO XProperties.ExtendOwns(ur.owns, VBT.KBFocus); ur.owns[VBT.KBFocus.sel] := FALSE; IF xcon.sel[VBT.KBFocus.sel].v = v THEN xcon.sel[VBT.KBFocus.sel].v := NIL END END; VBTClass.Misc( v, VBT.MiscRec{VBT.Lost, VBT.NullDetail, 0, VBT.KBFocus}) ELSIF takeFocus THEN LOCK xcon DO ur.recentlyOutside := FALSE END; VBTClass.Misc(v, VBT.MiscRec{VBT.TakeSelection, VBT.NullDetail, e.time, VBT.KBFocus}) END END EnterLeave; PROCEDURE ClientMessage (v : VBT.T; xcon : XClient.T; ur : XClientF.Child; takeFocus: BOOLEAN; e : X.XClientMessageEventStar) RAISES {TrestleComm.Failure} = <* FATAL Split.NotAChild *> BEGIN WITH data = LOOPHOLE(e.data, ARRAY [0 .. 4] OF Ctypes.int) DO IF e.message_type = xcon.protocols THEN IF data[0] = xcon.deleteWindow THEN Split.Delete(xcon, v) ELSIF data[0] = xcon.takeFocus THEN LOCK xcon DO XProperties.ExtendOwns(ur.owns, VBT.KBFocus); takeFocus := NOT ur.owns[VBT.KBFocus.sel] END; IF takeFocus THEN VBTClass.Misc(v, VBT.MiscRec{VBT.TakeSelection, VBT.NullDetail, data[1], VBT.KBFocus}) END END ELSIF e.message_type = xcon.decTakeFocus THEN LOCK xcon DO XProperties.ExtendOwns(ur.owns, VBT.KBFocus); takeFocus := NOT ur.owns[VBT.KBFocus.sel] END; IF takeFocus THEN VBTClass.Misc(v, VBT.MiscRec{VBT.TakeSelection, VBT.NullDetail, data[0], VBT.KBFocus}) END ELSIF e.message_type = xcon.paNewScreen OR e.message_type = xcon.paNewDisplay OR e.message_type = xcon.paAddDisplay THEN NewScreen(v, xcon, ur, e) ELSIF e.message_type = xcon.miscAtom THEN (* data[0] is an externalized MiscCodeType, data[2] is an externalized Selection *) data[0] := VBT.GetMiscCodeType(XClient.ToName(xcon, data[0])).typ; data[2] := VBT.GetSelection(XClient.ToName(xcon, data[2])).sel; VBTClass.Misc( v, VBT.MiscRec{VBT.MiscCodeType{data[0]}, ARRAY [0 .. 1] OF INTEGER{data[3], data[4]}, data[1], VBT.Selection{data[2]}}) ELSIF e.message_type = xcon.wmMoved THEN LOCK xcon DO ur.nwValid := FALSE END END END END ClientMessage; PROCEDURE NewScreen (v : VBT.T; xcon: XClient.T; ur : XClientF.Child; e : X.XClientMessageEventStar) RAISES {TrestleComm.Failure} = VAR id := -1; prop, type, type2 := X.None; len, len2, format, format2, remaining := 0; addr, addr2 : Ctypes.char_star; hasprop2 := FALSE; BEGIN WITH shData = LOOPHOLE(e.data, ARRAY [0 .. 9] OF Ctypes.short) DO VAR screen := shData[0]; x := shData[1]; y := shData[2]; width := shData[3]; height := shData[4]; hasprop := shData[5] # 0; state := shData[6]; BEGIN TrestleOnX.Enter(xcon); TRY IF hasprop AND X.Success # X.XGetWindowProperty( xcon.dpy, ur.w, xcon.paNewScreen, 0, X.XMaxRequestSize(xcon.dpy) - 50, X.True, X.AnyPropertyType, ADR(type), ADR(format), ADR(len), ADR(remaining), ADR(addr)) THEN hasprop := FALSE END; IF (e.message_type = xcon.paNewDisplay OR e.message_type = xcon.paAddDisplay) AND X.Success = X.XGetWindowProperty( xcon.dpy, ur.w, e.message_type, 0, MIN(4096, X.XMaxRequestSize(xcon.dpy) - 50), X.True, X.AnyPropertyType, ADR(type2), ADR(format2), ADR(len2), ADR(remaining), ADR(addr2)) THEN hasprop2 := TRUE; IF format2 = 8 AND len2 > 0 THEN id := -2 END ELSE FOR i := FIRST(xcon.screens^) TO LAST(xcon.screens^) DO IF xcon.screens[i].screenID = screen THEN id := i END END END FINALLY TrestleOnX.Exit(xcon) END; IF id >= 0 THEN XClient.InnerOverlap( xcon, v, id, Point.T{x, y}, TRUE, iconic := state = X.IconicState, userPosition := TRUE, prop := prop, type := type, len := len, format := format, addr := addr) ELSIF id = -2 THEN VAR nsp := NEW(XClientF.NewScreenProp); m := NEW(DpyFilter.Message); xx := MiscDetail.FromRef(m); yy := MiscDetail.FromRef(NIL); a := LOOPHOLE(addr2, Ctypes.string_4096); BEGIN IF prop # X.None THEN nsp.prop := prop; nsp.type := type; nsp.len := len; nsp.format := format; nsp.data := NEW(REF ARRAY OF Ctypes.char, len * format DIV 8); nsp.data^ := SUBARRAY(LOOPHOLE(addr, UNTRACED REF ARRAY [0 .. 4095] OF Ctypes.char)^, 0, NUMBER(nsp.data^)); yy := MiscDetail.FromRef(nsp) END; m.x := x; m.y := y; m.width := width; m.height := height; m.screen := screen; m.iconic := state = X.IconicState; m.status := TRUE; ComputeHeads(a, m, len2); IF e.message_type = xcon.paNewDisplay THEN VBTClass.Misc( v, VBT.MiscRec{DpyFilter.ChangeDisplay, VBT.MiscCodeDetail{xx, yy}, 0, VBT.NilSel}) ELSE VBTClass.Misc( v, VBT.MiscRec{DpyFilter.AddDisplay, VBT.MiscCodeDetail{xx, yy}, 0, VBT.NilSel}) END; MiscDetail.Delete(xx); MiscDetail.Delete(yy); IF NOT m.status THEN TrestleOnX.Enter(xcon); TRY X.XDeleteProperty(xcon.dpy, ur.w, xcon.paNewDisplay) EXCEPT TrestleComm.Failure => END END END END; IF hasprop THEN X.XFree(addr) END; IF hasprop2 THEN X.XFree(addr2) END; END END END NewScreen; PROCEDURE ComputeHeads (READONLY a : Ctypes.string_4096; m : DpyFilter.Message; len2: INTEGER ) = CONST NumDisplays = 20; VAR heads: ARRAY [0 .. NumDisplays] OF INTEGER; cnt := 0; tail := a[len2 - 1] = '\000'; BEGIN heads[0] := 0; FOR i := 0 TO len2 DO IF a[i] = '\000' AND cnt < NumDisplays THEN INC(cnt); heads[cnt] := i + 1 END END; IF NOT tail AND cnt < NumDisplays THEN INC(cnt); heads[cnt] := len2 + 2 END; IF cnt > 1 AND heads[2] > heads[1] + 1 THEN m.oldAuth := Text.FromChars(SUBARRAY(a^, heads[1], heads[2] - heads[1] - 1)) ELSE m.oldAuth := NIL END; IF cnt > 2 AND heads[3] > heads[2] + 1 THEN m.newAuth := Text.FromChars(SUBARRAY(a^, heads[2], heads[3] - heads[2] - 1)) ELSE m.newAuth := NIL END; m.newDisplay := NEW(REF ARRAY OF TEXT, MAX(1, cnt - 2)); m.newDisplay[0] := Text.FromChars(SUBARRAY(a^, heads[0], heads[1] - heads[0] - 1)); FOR i := 3 TO cnt - 1 DO m.newDisplay[i - 2] := Text.FromChars(SUBARRAY(a^, heads[i], heads[i + 1] - heads[i] - 1)) END END ComputeHeads; PROCEDURE DeliverPosition ( t : XClient.T; READONLY cd : VBT.PositionRec; h, v : INTEGER; w, s1, s2: VBT.T := NIL) = <*FATAL Split.NotAChild*> (* Deliver the position in cd to all the children of t, starting with s1, including s2, and ending with w. *) VAR goneCd := cd; others: BOOLEAN; ch : VBT.T; BEGIN goneCd.cp.gone := TRUE; LOCK t DO others := t.otherCages; t.otherCages := FALSE END; IF s1 # NIL AND s1 # w THEN DoPosition(t, s1, goneCd, h, v) END; IF others THEN ch := Split.Succ(t, NIL); WHILE ch # NIL DO IF ch # s1 AND ch # w THEN DoPosition(t, ch, goneCd, h, v) END; ch := Split.Succ(t, ch) END ELSIF s2 # NIL AND s2 # w AND s2 # s1 THEN DoPosition(t, s2, goneCd, h, v) END; IF w # NIL THEN VBTClass.Position(w, cd) END END DeliverPosition; PROCEDURE Disconnect ( trsl : REFANY; <*UNUSED*> key : INTEGER; VAR value: REFANY ): BOOLEAN = VAR vbt := NARROW(value, VBT.T); BEGIN XClientF.Delete(trsl, vbt, vbt.upRef); RETURN FALSE END Disconnect; PROCEDURE ScreenNumber (t: XClient.T; w: X.Window): INTEGER = BEGIN IF t.screens = NIL THEN RETURN -1 END; IF w = t.currentRootWindow THEN RETURN t.currentRoot END; LOCK t DO t.otherCages := TRUE; FOR i := FIRST(t.screens^) TO LAST(t.screens^) DO IF t.screens[i].root = w THEN t.currentRootWindow := w; t.currentRoot := t.screens[i].screenID; RETURN t.currentRoot END END; t.currentRootWindow := X.None; t.currentRoot := -1; RETURN -1 END END ScreenNumber; PROCEDURE DeliverBadRegion (v: VBT.T; ur: XClientF.Child) = (* Join v's x-bad-region into v's child's ordinary bad region, call its repaint method, and clear its x-bad-region. LL = VBT.mu. *) BEGIN LOCK v DO LOCK v.parent DO VBTClass.ForceRepaint(v, ur.badR, FALSE); ur.badR := Region.Empty END END; VBTClass.Repaint(v, Region.Empty) END DeliverBadRegion; PROCEDURE DoPosition (<*UNUSED*> t : XClient.T; w : VBT.T; VAR cd : VBT.PositionRec; <*UNUSED*> h, v: INTEGER ) = VAR cg := VBTClass.Cage(w); BEGIN IF (cg.screen = cd.cp.screen OR cg.screen = VBT.AllScreens) AND TRUE IN cg.inOut THEN IF Rect.Equal(cg.rect, Rect.Full) THEN RETURN END; END END DoPosition; BEGIN END XMessenger.