; AutoFocus module, written by Vincent Lefvre
; Freeware.

; $Id: autofocus 2.1 2002/12/15 17:16:54 vlefevre Exp $

; A group of windows consists of a window and its panes. The AutoFocus
; module only works with groups of windows to decide whether or not it
; needs to change the input focus. When it is changed, the input focus
; is given to the group of windows that is under the mouse pointer; the
; window in this group which had the input focus for the last time will
; get the focus (if there's no such window, a lose caret is performed).

; The input focus is changed when:
;   * the window having the focus is not a menu, and
;   * all the mouse buttons are released, and
;   * the group of windows that is under the mouse pointer has changed.

; The second condition allows to select a block by scrolling.

; Mode:
;   * bit 0: 0 = active, 1 = inactive.
;   * bit 1: never lose the caret if 1.
;   * bit 2: ignore menus if 1.
;   * bit 3: trap "self lose carets" and forget that the window had the
;            focus if 1 (useful if you use Alt-renaming under RO4, but
;            too strong if you iconize windows). Now that the set_caret
;            code has been improved, it shouldn't be useful any longer.


		GET	h:RegNames
		GET	h:SWINames
		GET	h:RCSsupport

; If you don't have the Tracker library, comment out the
; following GET line and uncomment the two macros.

		GET	h:DebugHdr

;		MACRO
;$label		DBSET	$flags, $cc
;$label
;		MEND
;
;		MACRO
;$label		DBF	$argstr, $cc
;$label
;		MEND

		GBLL	Debug
Debug		SETL	{FALSE}

		GETVERS	"$Revision: 2.1 $"
		GETDATE	"$Date: 2002/12/15 17:16:54 $"

		GBLS	NAME
NAME		SETS	"AutoFocus"
		GBLS	AUTHOR
AUTHOR		SETS	"Vincent Lefvre"
		GBLS	VERSION
VERSION		SETS	VERS:CC:" (":CC:DATE:CC:")"

INTERVAL	EQU	10		;Interval in cs for Wimp_PollIdle.
SIZE		EQU	4096		;Size of workspace: 4 KB.
		[	Debug
STKSIZE		EQU	4*90		;Stack size: 90 words (debug).
		|
STKSIZE		EQU	4*10		;Stack size: 10 words.
		]
QMAX		EQU	15		;Max size of the window queue.
PGRP		EQU	30
GRP		EQU	1<<PGRP
FOC		EQU	1<<29
AHF		EQU	1<<28
PICN		EQU	28
MASK		EQU	(-1)<<PICN
PIIS		EQU	16
IIS		EQU	1<<PIIS
PANE_FLAG	EQU	1<<5

MODE_INACT	EQU	1		;Mode 1: AutoFocus inactive.
MODE_NLC	EQU	2		;Mode 2: never lose the caret.
MODE_IGMEN	EQU	4		;Mode 4: ignore menus.
MODE_TSLC	EQU	8		;Mode 8: trap "self lose carets".

TaskCloseDown	EQU	&400C3		;Message: TaskCloseDown.

Service_FMI	EQU	&87		;Service: Filter Manager Installed.
Service_FMD	EQU	&88		;Service: Filter Manager Dying.

		^	0
		#	4		;Task handle.
		#	4*QMAX		;Window queue.
QWR		#	1		;Next entry of the queue for writing.
QRD		#	1		;Next entry of the queue for reading.
		#	2
FOCUS		#	4		;Window that had the focus or -1 or 0.
PTR_WIN		#	4		;Window under the mouse pointer or 0.
PTR_GRP		#	4		;Group under the mouse pointer or 0.
EOZ		#	0		;Window queue --> EOZ: initialised to 0.
MODE		#	1		;Mode.
		#	3
STACK		#	STKSIZE		;Stack.
BUFFER		#	256		;Buffer.
FRGC		#	4		;Filter routine GainCaret or 0.
FRWP		#	4		;Filter routine Wimp_Poll or 0.
CURR		#	4		;Handle of current task if FRWP <> 0.
USED		#	4		;Used memory.
DATA		#	0		;See structure below.

; Structure of DATA:
; * For each task having a window:
;     * Task handle (different from 0).
;     * Size of the list of the task windows (number of windows * 8).
;       The most significant 2 bits must be 0.
;     * For each window:
;         * Window handle.
;         * Flags:
;             * Bit 31: always 1 (flags identifier).
;             * Bit 30: 1 if new group of windows, i.e. the window is not
;                       a pane or it is a pane with no "parent" window.
;             * Bit 29: 1 if the window will get the input focus when the
;                       group gets it (focus flag).
;             * Bit 28: 1 if the window has already had the focus (AHF flag).
;             * Bits 27 to 16: icon handle (12-bit signed number).
;             * Bits 15 to 0: index into string (16-bit signed number).
; * 2 null words.

; Note 1: windows that are menus are not stored in the list.
; Note 2: when a task deletes a parent window, nothing is done to its children.
;   So, for consistency, the task should also delete the children.

; SWI Wimp_CloseDown is not trapped; Message_TaskCloseDown is used instead.
; This is better with tasks that do not call SWI Wimp_CloseDown or call it
; with a bad task handle.


		AREA	AutoFocus, CODE, PIC, READONLY
		ENTRY

BaseAddr	DCD	RM_Start  -BaseAddr
		DCD	RM_Init   -BaseAddr
		DCD	RM_Die    -BaseAddr
		DCD	RM_Service-BaseAddr
		DCD	RM_Title  -BaseAddr
		DCD	RM_Help   -BaseAddr
		DCD	RM_HCtable-BaseAddr
		DCD	0,0,0,0,0
		DCD	RM_Flags  -BaseAddr

RM_Flags	DCD	1		;32-bit compatible.

; *******************
; Initialisation code
; *******************

RM_Init		DBSET	DebugOn :OR: UseTracker
		DBF	"RM_Init\n"
		LDR	R2, [R12]	;R2: pointer to workspace or 0.
		ADDS	R6, LR, #0	;Make sure that V is cleared.
		TEQ	R2, #0
		BNE	init2		;Branch if re-init.
		MOV	R0, #6
		MOV	R3, #SIZE
		SWI	XOS_Module	;Claim.
		MOVVS	PC, R6		;Return with V=1 if not enough memory.
		STR	R2, [R12]	;Pointer to workspace.

init2		MOV	R3, R2
		MOV	R1, R10		;Pointer to environment string.
		MOV	R0, #10+(6<<29)
		SWI	XOS_ReadUnsigned ;Read the mode (if given).
		DBF	"Mode %2#w\n", VC
		MOVVS	R2, #0		;Default mode: 0.
		STRB	R2, [R3, #MODE]	;Set the mode.
		MOV	R2, R3
reset_taskh	SUBS	R0, R0, R0	;R0 = 0 and V is cleared.
		STR	R0, [R2]	;Reset task handle.
		STR	R0, [R2, #FRGC]	;Reset GC filter routine address.
		STR	R0, [R2, #FRWP]	;Reset WP filter routine address.
		MOV	PC, R6		;Return with V=0.

; *****************
; Finalisation code
; *****************

RM_Die		DBF	"RM_Die\n"
		LDR	R2, [R12]
		ADDS	R6, LR, #0	;Make sure that V is cleared.
		LDR	R0, [R2]	;Task handle or 0.
		MOVS	R5, R0
		MOVEQ	PC, R6		;Return with V=0 if 0.

		BL	unset_wp2
		LDR	R0, wswi
		MOV	R1, #1
		MOV	R3, #0
		ADR	R4, window_create
		SWI	XWimp_RegisterFilter ;Release Wimp_CreateWindow.
		MOV	R1, #3
		ADR	R4, window_delete
		SWI	XWimp_RegisterFilter ;Release Wimp_DeleteWindow.
		LDR	R1, [R2, #FRGC]	;Filter routine or 0.
		BL	params2
		TEQ	R1, #0		;Filter registered?
		SWINE	XFilter_DeRegisterPostFilter

		LDR	R1, task	;"TASK".
		MOV	R0, R5		;Task handle.
		SWI	XWimp_CloseDown
		B	reset_taskh	;Reset task handle and filter routine.

; ******************
; Routine add_window
; ******************

; add_window and add_window0 restore all the registers, including the PSR.
; At add_window2, R1 is [R12]+BUFFER+4, and R2 = [R1-4] is the window handle
; to add.

add_window	STMFD	SP!, {R0-R6,LR}
		LDRB	R0, [R12, #QRD]	;Next entry in the queue for reading.
		MOV	R3, #0
		ADD	R0, R0, #1
		LDR	R2, [R12, R0, LSL #2] ;Window handle.
		TEQ	R0, #QMAX
		STR	R3, [R12, R0, LSL #2] ;Store 0: free entry.
		STRNEB	R0, [R12, #QRD]	;Increment the index of the next entry.
		STREQB	R3, [R12, #QRD]	;Index of the next entry = 0.
		ADD	R1, R12, #BUFFER
		STR	R2, [R1], #4
		B	add_window2
add_window0	STMFD	SP!, {R0-R6,LR}
		LDR	R2, [R1], #4	;R2 = window handle. R1 += 4.
add_window2	DBF	"add_window   &%2w\n"
		MOV	R0, #24
		STR	R0, [R1]	;Length of block.
		MOV	R0, #0
		STR	R0, [R1, #12]	;Your ref = 0.
		MOV	R0, #19		;UserMessage_Acknowledge.
		SWI	XWimp_SendMessage ;R2 = owner of the window.
		TEQ	R2, #0		;Return if R2 = 0 (this sometimes
		BEQ	addwin_ret	;happens, I don't know why).
		SUBVC	R1, R1, #3
		SWIVC	XWimp_GetWindowInfo
		BVS	addwin_ret	;Return if error.
		LDR	R0, [R1, #51]
		CMP	R0, #1<<18	;Work area max x >= 1<<18 (i.e. menu)?
		BGE	addwin_ret	;Return if window is a menu.
		LDR	R0, [R1, #31]
		SUB	R1, R1, #1
		TST	R0, #1<<20
		MOV	R5, #(1<<31)+~MASK
		ORRNE	R5, R5, #FOC+AHF ;... if the window has the focus.
		ADD	R3, R1, #DATA-BUFFER
		B	addwin_ntask	;Search for task R2...

addwin_loop1	ADD	R3, R3, R4	;Skip the list of windows.
addwin_ntask	LDR	R4, [R3], #4	;Next task handle or 0.
		TEQ	R4, #0
		BEQ	new_task	;Branch if 0 (R2 is not in the list).
		TEQ	R4, R2
		LDR	R4, [R3], #4	;Size of the list of windows.
		BNE	addwin_loop1	;Loop while task R2 not found.

; R1: buffer.
; R3: pointer to the list of windows.
; R4: size of the list of windows.
; (R1): window info.

addwin_taskok	LDR	R0, [R1, #32]	;Window flags.
		TST	R0, #PANE_FLAG
		ADDEQ	R6, R3, R4	;Executed if window is not a pane.
		LDR	R4, [R1]	;R4: window handle.
		ORREQ	R5, R5, #GRP	;Add the new group flag.
		BEQ	addwin_end	;Branch if window is not a pane.
		MOV	R6, R3
		B	pane_open	;The window is a pane...

addwin_cancel	LDR	R0, [R3, #-4]
		TEQ	R0, #0
		STREQ	R0, [R3, #-8]	;If a task has been added, remove it...
		LDREQ	R0, [R1, #USED-BUFFER]
		SUBEQ	R0, R0, #8
		STREQ	R0, [R1, #USED-BUFFER]
		DBF	"Memory used: %0#w\n", EQ
		LDMFD	SP!, {R0-R6,PC}	;Return.

pane_loop	STR	R2, [R1]
		SWI	XWimp_GetWindowState
		BVS	pane_open	;Branch if error.
		LDR	R0, [R1, #32]
		TST	R0, #PANE_FLAG
		BNE	pane_compwin	;Branch if above window is a pane.
pane_open	LDR	R0, [R6, #4]	;Flags of current window in the list.
		TEQ	R0, #0
		BPL	addwin_cancel	;Branch if end of the list of windows.
		LDR	R2, [R6], #8	;Handle of current window in the list.
		STR	R2, [R1]
		SWI	XWimp_GetWindowState
		BVS	pane_open	;Branch if error (next window...).
pane_compwin	LDR	R2, [R1, #28]	;Above window.
		TEQ	R2, R4
		BNE	pane_loop	;Loop while window R4 not found.

; R1: buffer.
; R3: pointer to the list of windows.
; R4: window handle.
; R5: window flags.
; R6: address where the window handle and flags will be stored.

addwin_end	LDR	R0, [R1, #USED-BUFFER] ;Size of the data.
		ADD	R0, R0, #8
		CMP	R0, #SIZE
		BHI	addwin_cancel	;Branch if not enough memory.
		STR	R0, [R1, #USED-BUFFER] ;Update size of the data.
		DBF	"Memory used: %0#w\n"
		LDR	R2, [R3, #-4]	;Old size of the list of windows.
		SUB	R1, R1, #BUFFER
		ADD	R1, R1, R0	;R1: end of data.
		ADD	R2, R2, #8
		STR	R2, [R3, #-4]	;Update the size of the list of windows.
addwin_loop2	LDR	R0, [R1, #-4]!	;\
		STR	R0, [R1, #8]	;| Move the last part to insert the
		CMP	R1, R6		;| window handle and the flags.
		BNE	addwin_loop2	;/
		STMIA	R6, {R4,R5}	;Store the window handle and the flags.
		DBF	"Window added &%4w, flags: &%5w\n"
addwin_ret	LDMFD	SP!, {R0-R6,PC}	;Return.

new_task	LDR	R0, [R1, #USED-BUFFER]
		ADD	R0, R0, #8
		CMP	R0, #SIZE
		BCS	addwin_ret	;Return if not enough memory.
		STR	R0, [R1, #USED-BUFFER] ;Update size.
		STR	R2, [R3, #-4]	;Store task handle. [R3] = 0 already.
		STR	R4, [R3, #4]!	;Store 0: 1st null word (end of data).
		STR	R4, [R3, #4]	;Store 0: 2nd null word.
		B	addwin_taskok

; ******************
; Routine gain_caret
; ******************

; Called by the Filter Manager. According to Stewart Brodie, flags may
; be corrupted (<6a4ca5664a.sbrodie@usr-offsite-75.cam.pace.co.uk>).

gain_caret	TEQ	R0, #12		;Gain caret reason code?
		MOVNE	PC, LR		;No, return.
		STMFD	SP!, {R0-R2,R4-R6,R8-R9,LR}
		MOV	R6, R12
		LDR	R2, [R1]	;Window handle.
		DBF	"Gain caret   &%2w\n"
		BL	get_group2
		STR	R2, [R6, #FOCUS]
		BNE	gain_caret2	;Branch if the window is in the list.
		ADD	R1, R12, #BUFFER
		STR	R2, [R1]
		BL	add_window0
		BL	get_group2
gain_caret2	LDMFD	SP!, {R0-R1}
		DBF	"Window was not found\n", EQ
		LDMEQFD	SP!, {R2,R4-R6,R8-R9,PC}
		LDR	R2, [R5, #4]	;If window found, update the flags...
		BL	update_flags
		LDR	R8, [R1, #4]	;Icon handle, or -1.
		LDR	R9, [R1, #20]	;Index into string, or -1.
		MOV	R8, R8, LSL #32-PICN+PIIS
		MOV	R9, R9, LSL #32-PIIS
		AND	R2, R2, #MASK
		ORR	R2, R2, R8, LSR #32-PICN
		ORR	R2, R2, R9, LSR #32-PIIS
		STR	R2, [R5, #4]
		DBF	"New flags: &%2w\n"
		LDMFD	SP!, {R2,R4-R6,R8-R9,PC}

; **************
; Routine params
; **************

params1		ADR	R1, gain_caret	;Parameters for the Wimp filter...
params2		ADR	R0, RM_Title
		MVN	R4, #1<<12	;Mask all except gain caret.
		MOV	PC, LR

; *********************
; Routine window_create
; *********************

window_create	MOVVS	PC, LR		;Return if error.
		STMFD	SP!, {R0-R2,LR}
		DBF	"CreateWindow &%0w\n"
		LDRB	R1, [R12, #QWR]	;Next entry in the queue for writing.
		ADD	R1, R1, #1
		LDR	R2, [R12, R1, LSL #2] ;0 if free entry.
		TEQ	R2, #0
		BNE	wincr_ret	;Return if the window queue is full.
		TEQ	R1, #QMAX
		STRNEB	R1, [R12, #QWR]	;Increment the index of the next entry.
		STREQB	R2, [R12, #QWR]	;Index of the next entry = 0.
		STR	R0, [R12, R1, LSL #2] ;Store window handle.
		MOV	R1, R12
		ADR	R0, add_window
		SWI	XOS_AddCallBack
wincr_ret	LDMFD	SP!, {R0-R2,PC}

; *********************
; Routine window_delete
; *********************

window_delete	MOVVS	PC, LR		;Return if error.
		STMFD	SP!, {R0-R4,LR}
		LDR	R4, [R1]	;Handle of the deleted window.
		DBF	"DeleteWindow &%4w (the whole group will be removed)\n"
		MOV	R2, #0		;R2 = 0.
		LDR	R0, [R12, #FOCUS] ;\ If the deleted window is the
		TEQ	R0, R4		  ;| window that had the focus,
		STREQ	R2, [R12, #FOCUS] ;/ then FOCUS is set to 0.
		LDR	R0, [R12, #PTR_WIN] ;\
		TEQ	R0, R4		    ;| If the deleted window is either
		LDRNE	R1, [R12, #PTR_GRP] ;| the window under the pointer or
		TEQNE	R1, R4		    ;| its parent, then PTR_WIN and
		STREQ	R2, [R12, #PTR_WIN] ;| PTR_GRP are both set to 0.
		STREQ	R2, [R12, #PTR_GRP] ;/
		DBF	"New PTR_WIN  0\n", EQ
		DBF	"New PTR_GRP  0\n", EQ
		ADD	R3, R12, #DATA
delwin_outer	LDR	R1, [R3], #4	;Task handle, or 0 if no more task.
		TEQ	R1, #0
		LDMEQFD	SP!, {R0-R4,PC}	;if no more task [shouldn't happen].
		LDR	R1, [R3], #4	;Number of windows * 8.
		SUB	R2, R3, #4
delwin_inner	SUBS	R1, R1, #8
		BCC	delwin_outer	;Branch if task has no more window.
		LDR	R0, [R3], #8	;Handle of the next window.
		TEQ	R0, R4
		BNE	delwin_inner	;Loop if wrong window.

; The window to delete has been found.
; R1: number of remaining windows * 8 (for the current task).
; R2: pointer to the size of the list of windows (for the current task).
; R3: pointer to the data following the current window information.

		TEQ	R1, #0		;No more windows for the current task?
		LDRNE	R0, [R3, #-4]	;Flags of the window to be deleted.
		MOV	R4, #8
		TSTNE	R0, #GRP
		BEQ	delwin_remove	;Branch if new group flag isn't set.

; The current window (to be deleted) is the first one of a group. Then the
; next windows of the group (which exist, as R1 <> 0) will be deleted too.
; This is necessary, as some applications (like !LIRC) delete a window but
; not the panes.

delwin_group	LDR	R0, [R3, #4]	;Flags of the next window.
		TST	R0, #GRP	;New group flag?
		BNE	delwin_remove	;Branch if new group.
		SUBS	R1, R1, #8
		ADD	R3, R3, #8	;Next window.
		ADD	R4, R4, #8	;New length of the data to delete.
		BNE	delwin_group

; Remove the window data...

delwin_remove	LDR	R1, [R2]	;Old size of the list of windows.
		SUBS	R1, R1, R4	;New size.
		STRNE	R1, [R2]	;Update if non zero.
		ADDEQ	R4, R4, #8	;R4: size of data to delete.
		SUB	R3, R3, R4
		LDR	R1, [R12, #USED]
		SUB	R1, R1, R4
		STR	R1, [R12, #USED] ;Update memory used.
		DBF	"Memory used: %1#w\n"
		ADD	R2, R12, R1	;New end of data (after deletion).
delwin_move	LDR	R0, [R3, R4]	;Delete the window data, and the task
		STR	R0, [R3], #4	;data if it was its only window...
		CMP	R3, R2
		BCC	delwin_move	;Loop while R3 < R2.
		LDMFD	SP!, {R0-R4,PC}

; *********
; Some data
; *********

task		DCB	"TASK"
wswi		DCB	"WSWI"

RM_HCtable	DCB	"Desktop_"
RM_Title	DCB	NAME,0
		ALIGN
		DCD	Desktop-BaseAddr,0,0,Help_Desktop-BaseAddr
		DCB	"ToggleAutoFocus",0
		ALIGN
		DCD	Toggle-BaseAddr,0,0,Help_Toggle-BaseAddr
		DCB	NAME,"Mode",0
		ALIGN
		DCD	Mode-BaseAddr,&010100,Synt_Mode-BaseAddr,Help_Mode-BaseAddr
		DCB	0

RM_Help		DCB	NAME,9,VERSION," by ",AUTHOR,0
Help_Desktop	DCB	"Do not use *",27,0,", use *Desktop instead.",0
Help_Toggle	DCB	"*",27,0," toggles",27,2,"status of ",NAME,".",0
Help_Mode	DCB	"*",27,0," sets or",27,32,27,2,NAME," mode.",13
Synt_Mode	DCB	27,30,"<mode>]",0

		ALIGN
messages	DCD	TaskCloseDown

; Note: zero word follows.

ServiceTable	DCD	0, RM_FastServ-BaseAddr, Service_FMI, Service_FMD

; Note: zero word follows.

window_block	DCD	0,0,1,1,0,0,-2,&80000010
		DCB	&FF,0,0,&FF,0,0,0,0
		DCD	0,0,1,1,0,0,1,0,0,0,0,0

; **********************
; Desktop_AutoFocus code
; **********************

Desktop		MOV	R6, LR
		MOV	R2, R0
		ADR	R1, RM_Title
		MOV	R0, #2
		SWI	XOS_Module	;Enter AutoFocus.
		MOV	PC, R6

; **********
; Start code
; **********

RM_Start	LDR	R6, [R12]	;Pointer to workspace.
		ADD	SP, R6, #STACK+STKSIZE ;New stack pointer.

		LDR	R0, [R6]	;Task handle or 0.
		TEQ	R0, #0
		BEQ	start2
		LDR	R1, task	;"TASK".
		SWI	Wimp_CloseDown
		MOV	R0, #0
		STR	R0, [R6]	;Reset task handle.

start2		MOV	R0, #300	;Last Wimp version known: 3.00.
		LDR	R1, task	;"TASK".
		ADR	R2, RM_Title
		ADR	R3, messages	;List of the accepted user messages.
		SWI	XWimp_Initialise
		LDRVS	R1, task	;If init failed, try ROS 2.00...
		MOVVS	R0, #200
		SWIVS	XWimp_Initialise
		ADRVS	R3, RM_Title
		SWIVS	OS_ExitAndDie
		STR	R1, [R6]	;Store task handle.
		DBF	"Started (task handle: &%1h)\n"

		MOV	R0, #0		;Init window queue --> EOZ to 0.
		MOV	R1, #EOZ-4
init_loop	STR	R0, [R6, R1]
		SUBS	R1, R1, #4
		BNE	init_loop
		MOV	R1, #DATA+8
		STR	R1, [R6, #USED]	;Store the used memory.
		DBF	"Memory used: %1#w\n"
		STR	R0, [R6, #DATA]	;Null task handle --> empty window list.
		STR	R0, [R6, #DATA+4]

		MOV	R2, R6
		LDR	R1, [R2, #FRGC]
		MOV	R3, #0
		TEQ	R1, #0		;Filter already registered?
		BNE	start3		;Yes, branch.
		BL	params1
		SWI	XFilter_RegisterPostFilter
		MOVVS	R1, #0		;R1 = 0 if error.
		STR	R1, [R2, #FRGC]

start3		LDR	R0, wswi	;Use WimpSWIVe...
		MOV	R1, #&80000001
		ADR	R4, window_create
		ORR	R1, R1, #&F00	;NZCV won't be passed back to caller.
		SWI	XWimp_RegisterFilter ;Claim Wimp_CreateWindow.
		ADRVC	R4, window_delete
		ADDVC	R1, R1, #2
		SWIVC	XWimp_RegisterFilter ;Claim Wimp_DeleteWindow.

; Add the existing windows...

		ADRVC	R1, window_block
		SWIVC	XWimp_CreateWindow ;Create a window at bottom.
		LDMVCIA	R1, {R2-R5,R7-R9}
		ADDVC	R1, R6, #BUFFER
		STMVCIA	R1, {R0,R2-R5,R7-R9}
		MOVVC	R5, R0		;R5: window handle.
		SWIVC	XWimp_OpenWindow
		ADRVS	R3, RM_Title
		SWIVS	OS_ExitAndDie
		MOV	R0, R5		;First window handle.
		B	add_next
add_existing	CMP	R0, #-2		;If R0 <> -2 (icon bar), ...
		STRNE	R0, [R1]
		BLNE	add_window0	;... add the window to the list.
add_next	STR	R0, [R1]
		SWI	XWimp_GetWindowState
		LDRVC	R0, [R1, #28]	;Handle of next window or -1.
		BVS	add_end
		CMP	R0, #-1
		BNE	add_existing	;Loop until no more windows.
add_end		STR	R5, [R1]
		SWI	XWimp_DeleteWindow ;Delete the window created above.

		BL	set_wp6

; Main loop.

loop		SWI	XOS_ReadMonotonicTime
		ADDVC	R2, R0, #INTERVAL
		MOVVC	R0, #0		;Mask.
		ADDVC	R1, R6, #BUFFER	;256-byte block.
		SWIVC	XWimp_PollIdle	;(windows created and deleted *here*)
		ADR	R3, RM_Title
		SWIVS	OS_ExitAndDie
		TEQ	R0, #17		;UserMessage?
		TEQNE	R0, #18		;UserMessage_Recorded?
		BNE	caret_pos	;No, branch.
		LDR	R0, [R1, #16]	;Message code.
		TEQ	R0, #0		;Quit?
		SWIEQ	OS_ExitAndDie	;Die if quit.
		SUB	R0, R0, #&40000
		TEQ	R0, #&C3	;TaskCloseDown?
		BNE	caret_pos	;No, branch.

		LDR	R4, [R1, #4]	;Task that has closed down.
		ADD	R3, R6, #DATA
		B	close_nexttask	;Search for task R4...
close_loop	LDR	R0, [R3], #4	;Size of the current task data.
		ADD	R3, R3, R0
close_nexttask	LDR	R0, [R3], #4	;Task handle, or 0 if no more task.
		TEQ	R0, #0
		BEQ	caret_pos	;Branch if no more task.
		TEQ	R0, R4
		BNE	close_loop	;Loop while task R4 not found.
		DBF	"Task &%4h has closed down\n"
		LDR	R2, [R3], #-4
		ADD	R2, R2, #8	;Size of the block to delete.
		LDR	R0, [R6, #USED]
		SUB	R0, R0, R2
		STR	R0, [R6, #USED] ;Update memory used.
		DBF	"Memory used: %0#w\n"
		ADD	R4, R6, R0	;New end of data (after block deletion).
delblock_loop	LDR	R0, [R3, R2]	;Delete the task data...
		STR	R0, [R3], #4
		CMP	R3, R4
		BCC	delblock_loop	;Loop while R3 < R4.

; Now look at the caret position (-> input focus).

; R1 = pointer to the buffer.
; R6 = pointer to the workspace, as usual in the Start code.

caret_pos	SWI	XWimp_GetCaretPosition
		BVS	loop
		LDMIA	R1, {R7,R10}	;Window and icon having the focus or -1.
		LDR	R11, [R1, #20]	;Index into string or -1.
		LDR	R3, [R6, #FOCUS]
		CMP	R7, #-1
		STR	R7, [R6, #FOCUS] ;Update FOCUS.
		BEQ	chk_status	;Branch if no caret.
		ADD	R1, R1, #1	;Set bit 0 for XWimp_GetWindowInfo.
		SWI	XWimp_GetWindowInfo
		BVS	loop
		LDR	R0, [R1, #51]
		SUB	R1, R1, #1	;Restore R1.
		CMP	R0, #1<<18	;Work area max x >= 1<<18 (i.e. menu)?
		BGE	loop		;Branch if a menu has the input focus.

		TEQ	R3, R7		;Branch to chk_status if the window
		BLNE	get_group1	;that has the focus has not changed
		BEQ	chk_status	;or if it is not in the list.

; A possible new window has the focus; update the focus flag...

		LDR	R2, [R5, #4]
		TST	R2, #FOC
		BNE	chk_status
		BL	update_flags
		STR	R2, [R5, #4]
		DBF	"New flags: &%2w\n"

; Check if autofocus had to change the focus...

chk_status	LDRB	R0, [R6, #MODE]	;Mode.
		TST	R0, #MODE_INACT
		BNE	loop		;Branch if AutoFocus is inactive.

; AutoFocus is active.

; R1 = pointer to the buffer.
; R3 = handle of the window that previously had the focus, or -1.
; R7 = handle of the window that currently has the focus, or -1.
; R10 = icon that has the focus, or -1.
; R11 = index into string, or -1.

; If a mouse button is pressed, the focus mustn't be changed (in order
; to allow the pointer to leave the window during a block selection).

		SWI	XWimp_GetPointerInfo
		BVS	loop		;Branch if error (no change).
		LDR	R0, [R1, #8]
		TST	R0, #7
		BNE	loop		;Branch if a mouse button is pressed.

; No mouse button is pressed.

		CMN	R7, #1		;C = 1 iff R7 = -1.
		MOV	R0, #0
		ADCS	R0, R0, R0	;Z = 0 iff R7 = -1...
		TEQNE	R3, R7		;and R3 <> R7.

; Z = 0 if the focus has just been lost. AutoFocus will try to make
; the current window to regain the focus.

		LDR	R3, [R1, #12]	;Window that is under the mouse pointer.
		LDR	R0, [R6, #PTR_WIN]
		TEQEQ	R0, R3		;Branch if Z = 0 and the window under
		BEQ	loop		;the mouse pointer has not changed.

; The focus has just been lost or the window under the mouse pointer
; has changed.

; R1 = pointer to the buffer.
; R3 = handle of the window under the mouse pointer.
; R7 = handle of the window that currently has the focus, or -1.
; R10 = icon that has the focus, or -1.
; R11 = index into string, or -1.

		STR	R3, [R6, #PTR_WIN] ;Update PTR_WIN.
		DBF	"New PTR_WIN  &%3w\n"
		LDRB	R0, [R6, #MODE]	;Mode.
		TST	R0, #MODE_IGMEN	;Should menus be ignored?
		BEQ	set_caret0	;No, branch.
		STR	R3, [R1], #1	;Window under the mouse pointer.
		SWI	XWimp_GetWindowInfo
		LDRVC	R0, [R1, #51]
		BVS	loop
		CMP	R0, #1<<18	;Work area max x >= 1<<18 (i.e. menu)?
		BGE	loop		;Yes, branch.
		SUB	R1, R1, #1	;Restore R1.
set_caret0	CMP	R7, #-1
		BEQ	set_caret2
		CMP	R11, #IIS>>1	;If unsigned(R11) is too
		MOVCS	R11, #-1	;large, R11 = -1.
		BL	get_group1
		BNE	set_caret1	;Branch if old window is in the list.
		STR	R7, [R1]	;Handle of the window.
		BL	add_window0	;Add the window to the list.
		BL	get_group1
		BEQ	set_caret2	;Branch if failed.
set_caret1	LDR	R2, [R5, #4]	            ;\
		BL	update_flags	            ;|
		MOV	R10, R10, LSL #32-PICN+PIIS ;| Update the flags
		MOV	R11, R11, LSL #32-PIIS      ;| of the window that
		AND	R2, R2, #MASK	            ;| currently has the
		ORR	R2, R2, R10, LSR #32-PICN   ;| focus.
		ORR	R2, R2, R11, LSR #32-PIIS   ;|
		STR	R2, [R5, #4]	            ;/
		DBF	"New flags: &%2w\n"

; The data of the window that currently has the focus (if any) have been
; updated or added.

; R1 = pointer to the buffer.
; R3 = handle of the window under the mouse pointer.
; R7 = handle of the window that currently has the focus, or -1.

set_caret2	MOV	R2, R3
		BL	get_group2
		BNE	set_caret3	;Branch if the window is in the list.
		STR	R2, [R1]	;Handle of the window.
		BL	add_window0	;Add the window to the list.
		BL	get_group2	;... Z = 1 if failed.
set_caret3	LDRNE	R2, [R4]	;Group (PTR_WIN if window not found).
		STR	R2, [R6, #PTR_GRP] ;Update PTR_GRP.
		DBF	"New PTR_GRP  &%2w\n"
		BEQ	no_caret	;Branch if window not found.
		LDR	R2, [R4, #4]!	;Flags of the first window in the group.
		MOV	R8, #AHF
setcaret_loop	TST	R2, #FOC
		LDRNE	R0, [R4, #-4]	;R0 = window handle, and branch
		BNE	set_caret_pos1	;if FOC is set (R1: flags).
		TST	R2, R8		;For the 1st window having the AHF flag:
		MOVNE	R8, #0		;1) Clear R8 (then TST R2,R8 --> Z=1).
		SUBNE	R9, R4, #4	;2) R9 = pointer to the window handle.
		LDR	R2, [R4, #8]!	;Flags of the next window (if exists).
		TEQ	R2, R2, LSL #31-PGRP
		BMI	setcaret_loop	;Loop while window is in the same group.
		TEQ	R8, #AHF	;Branch if no windows had the AHF flag
		BEQ	no_caret	;(no caret to set).
		LDMIA	R9, {R0,R2}	;R0 = window handle, R2 = flags.

; R0 = handle of the window that will have the focus.
; R2 = flags.
; R7 = handle of the window that currently has the focus, or -1.

set_caret_pos1	DBF	"Set caret    &%0w  -  flags: &%2w\n"
		TEQ	R0, R7
		DBF	"--> window &%0w already has the focus.\n", EQ
		BEQ	loop		;Loop if the focus doesn't change.
		MOV	R5, R2, LSL #32-PIIS
		MOV	R5, R5, ASR #32-PIIS ;Index into string.
		MOV	R2, R2, LSL #32-PICN
		MOVS	R2, R2, ASR #32-PICN+PIIS ;Icon handle or -1.
		BMI	set_caret_pos2	;Branch if no icon.
		STMIA	R1, {R0,R2}	;Window handle and icon handle.
		SWI	XWimp_GetIconState
		LDR	R0, [R1]	;Window handle.
		LDRVC	R4, [R1, #24]	;Icon flags if no error.
		MOVS	R1, #-1		;Note: clear Z, V unchanged.
		TSTVC	R4, #3<<22	;Test "shaded" and "deleted" flags.
		ANDEQ	R3, R4, #&E000
		TEQEQ	R3, #&E000	;Test the button type.
		BEQ	set_caret_pos2	;Branch if selectable & writable.
no_caret	DBF	"No caret\n"
		LDRB	R1, [R6, #MODE]	;Mode.
		TST	R1, #MODE_NLC
		BNE	loop		;Loop if mode "never lose caret".
		MOV	R0, #-1		;Lose caret...
set_caret_pos2	MOV	R1, R2		;Icon handle or -1.
		MOV	R2, #-1
		MOV	R3, #-1
		MOV	R4, #-1
		SWI	XWimp_SetCaretPosition
		B	loop

; *****************
; Routine get_group
; *****************

; R7 (get_group1) or R2 (get_group2): window handle. R6: ptr to the workspace.
; --> Z = 0, R4: pointer to the group, R5: pointer to the window handle.
;  or Z = 1: window not found.
; R8-R9 modified. R2 modified if get_group1.

get_group1	MOV	R2, R7
get_group2	ADD	R5, R6, #DATA
gg_loop		LDMIA	R5!, {R8,R9}	;(R8,R9): (window,flags) or (task,size).
		TEQ	R9, #0
		MOVEQ	PC, LR		;Return with Z = 1 if window not found.
		BPL	gg_loop		;Branch if (task,size).
		TST	R9, #GRP
		SUBNE	R4, R5, #8	;Executed if new group.
		TEQ	R8, R2
		BNE	gg_loop		;Loop if not the right window.
		SUBS	R5, R5, #8
		MOV	PC, LR		;Return with Z = 0.

; ********************
; Routine update_flags
; ********************

update_flags	MOV	R9, R4
		LDR	R8, [R9, #4]!	;Flags of the first window in the group.
clr_focflag	BIC	R8, R8, #FOC	;Clear the focus flag.
		STR	R8, [R9]	;Store the flags.
		LDR	R8, [R9, #8]!	;Flags of the next window in the list.
		TEQ	R8, R8, LSL #31-PGRP
		BMI	clr_focflag	;Loop while window is in the same group.
		ORR	R2, R2, #FOC+AHF ;Set the focus flag and the AHF flag.
		DBF	"Update focus flags  -  "
		MOV	PC, LR

; ********************************
; Code for command ToggleAutoFocus
; ********************************

Toggle		LDR	R2, [R12]	;Toggle the status of AutoFocus...
		LDRB	R0, [R2, #MODE]	;Read the mode.
		EOR	R0, R0, #MODE_INACT ;Toggle the inactive mode.
		STRB	R0, [R2, #MODE]	;Update the mode.
		MOV	PC, LR

; ************
; Service code
; ************

		DCD	ServiceTable-BaseAddr
RM_Service	MOV	R0, R0		;Magic instruction for Ursula kernel.
		TEQ	R1, #Service_FMI
		TEQNE	R1, #Service_FMD
		MOVNE	PC, LR		;Return if neither FMI nor FMD.
RM_FastServ	STMFD	SP!, {R0-R4,LR}
		LDR	R2, [R12]	;Pointer to workspace.
		MOV	R3, #0
		TEQ	R1, #Service_FMI
		BEQ	FM_Installed
FM_Dying	LDR	R1, [R2, #FRWP]	;Filter Manager Dying.
		STR	R3, [R2, #FRGC]
		TEQ	R1, #0
		STRNE	R3, [R2, #FRWP]
		BLNE	params_scp	;Flags are not altered.
		SWINE	XWimp_RegisterFilter
		LDMFD	SP!, {R0-R4,PC}
FM_Installed	BL	params1		;Filter Manager Installed.
		SWI	XFilter_RegisterPostFilter
		MOVVS	R1, #0		;R1 = 0 if error.
		STR	R1, [R2, #FRGC]
		BL	set_wp2
		LDMFD	SP!, {R0-R4,PC}

; ******************************
; Code for command AutoFocusMode
; ******************************

Mode		LDR	R6, [R12]	;R6: pointer to workspace.
		MOV	R5, LR
		TEQ	R1, #0
		BNE	set_mode
		LDRB	R0, [R6, #MODE]	;Display the current mode...
		ADD	R1, R6, #BUFFER
		MOV	R2, #16
		SWI	XOS_ConvertCardinal1
		SWIVC	XOS_Write0
		SWIVC	XOS_NewLine
		MOV	PC, R5

set_mode	MOV	R1, R0		;Set the mode...
		MOV	R0, #10+(6<<29)
		SWI	XOS_ReadUnsigned
		MOVVS	PC, R5
		STRB	R2, [R6, #MODE]	;Update the mode.
		DBF	"Mode %2#w\n"
		TST	R2, #MODE_TSLC	;Trap "self lose carets"?
		MOV	LR, R5
		BEQ	unset_wp6	;No, remove the filters.

;Yes, install the filters...

; **************
; Routine set_wp
; **************

; May modify registers R0-R4. (Must follow set_mode.)

set_wp6		MOV	R2, R6
set_wp2		DBF	"*** set_wp called     \n"
		LDRB	R0, [R2, #MODE]
		TST	R0, #MODE_TSLC	;Trap "self lose carets"?
		MOVEQ	PC, LR		;No, return.

		LDR	R0, [R2, #FRWP]
		TEQ	R0, #0
		MOVNE	PC, LR		;Return if filter routines already set.

		STMFD	SP!, {LR}
		BL	params_scp
		ORR	R1, R1, #1<<31
		ORR	R1, R1, #&F00
		SWI	XWimp_RegisterFilter
		LDMVSFD	SP!, {PC}
		BL	params_wp
		SWI	XFilter_RegisterPostFilter
		STRVC	R1, [R2, #FRWP]
		BLVS	params_scp
		SWIVS	XWimp_RegisterFilter
		LDMFD	SP!, {PC}

params_wp	ADR	R0, RM_Title
		ADR	R1, get_taskh
		MOV	R3, #0
		MOV	R4, #0
		MOV	PC, LR		;Flags are not altered!

params_scp	LDR	R0, wswi
		MOV	R1, #&12	;Wimp_SetCaretPosition.
		ADR	R3, setcaretpos
		MOV	R4, #0
		MOV	PC, LR		;Flags are not altered!

; ****************
; Routine unset_wp
; ****************

; May modify registers R0-R4.

unset_wp6	MOV	R2, R6
unset_wp2	DBF	"*** unset_wp called\n"
		LDR	R0, [R2, #FRWP]
		TEQ	R0, #0
		MOVEQ	PC, LR		;Return if filter routines not set.
		STMFD	SP!, {LR}
		BL	params_wp
		SWI	XFilter_DeRegisterPostFilter
		BL	params_scp
		SWI	XWimp_RegisterFilter
		STR	R4, [R2, #FRWP]
		LDMFD	SP!, {PC}

; *****************
; Routine get_taskh
; *****************

get_taskh	STR	R2, [R12, #CURR] ;Handle of current task.
		MOV	PC, LR

; *******************
; Routine setcaretpos
; *******************

; Note: this routine is used by mode 8; this mode should be avoided as
; it makes iconized windows forget that they had the focus.

; If self lose caret, clear bits 28 and 29 of the window that had the
; focus. Note: this will not work correctly if there are 2 lose carets
; the one immediately after the other, but this is so rare that this
; case isn't taken into account.

setcaretpos	CMP	R0, #-1
		MOVNE	PC, LR		;Return unless lose caret.
		STMFD	SP!, {R0-R3,LR}
		LDR	R2, [R12, #CURR] ;Handle of the current task.
		ADD	R3, R12, #DATA
		MOV	R2, R2, LSL #16
scp_loop1	LDMIA	R3!, {R0,R1}	;(R0,R1): (task,size) or (0,0).
		TEQ	R0, #0
		BEQ	scp_ret		;Return if no more tasks.
		TEQ	R2, R0, LSL #16	;Same task as the current one?
		ADDNE	R3, R3, R1	;No, next
		BNE	scp_loop1	;task...
		LDR	R2, [R12, #FOCUS] ;Window that had the focus.
scp_loop2	LDMIA	R3!, {R0,R1}	;(R0,R1): (window, flags).
		TEQ	R1, #0
		BPL	scp_ret		;Return if no more windows.
		TEQ	R2, R0		;Same window?
		BNE	scp_loop2	;No, loop.
		BIC	R1, R1, #FOC+AHF
		STR	R1, [R3, #-4]	;Update the flags.
		DBF	"Self lose caret  -  New flags: &%1w\n"
		MOV	R0, #-1
		STR	R0, [R12, #FOCUS]
scp_ret		LDMFD	SP!, {R0-R3,PC}

		END
