## triangle-app.pkg
#
# This app displays a drawing window and a RESET button.
# It puts a triangle wherever the user clicks in the drawing window;
# It clears the drawing window when the RESET button is clicked.
#
# One way to run this app from the base-directory commandline is:
#
# linux% my
# eval: make "src/lib/x-kit/tut/triangle/triangle-app.lib";
# eval: triangle_app::do_it "";
# Compiled by:
#
src/lib/x-kit/tut/triangle/triangle-app.libstipulate
include package makelib::scripting_globals;
include package threadkit; # threadkit is from
src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg package cmd = commandline; # commandline is from
src/lib/std/commandline.pkg #
package fil = file__premicrothread; # file__premicrothread is from
src/lib/std/src/posix/file--premicrothread.pkg package mps = microthread_preemptive_scheduler; # microthread_preemptive_scheduler is from
src/lib/src/lib/thread-kit/src/core-thread-kit/microthread-preemptive-scheduler.pkg #
package g2d = geometry2d; # geometry2d is from
src/lib/std/2d/geometry2d.pkg package xc = xclient; # xclient is from
src/lib/x-kit/xclient/xclient.pkg #
package xtr = xlogger; # xlogger is from
src/lib/x-kit/xclient/src/stuff/xlogger.pkg #
package che = cartouche; # cartouche is from
src/lib/x-kit/draw/cartouche.pkg #
package ib = icon_bitmap; # icon_bitmap is from
src/lib/x-kit/tut/triangle/icon-bitmap.pkg #
tracefile = "triangle-app.trace.log";
tracing = logger::make_logtree_leaf { parent => xlogger::xkit_logging, name => "triangle_app::tracing", default => FALSE };
trace = xtr::log_if tracing 0; # Conditionally write strings to tracing.log or whatever.
#
# To debug via tracelogging, annotate the code with lines like
#
# trace {. sprintf "foo/top: bar d=%d" bar; };
#
# and then set write_tracelog = TRUE; below.
herein
package triangle_app
: Triangle_App # Triangle_App is from
src/lib/x-kit/tut/triangle/triangle-app.api {
write_tracelog = FALSE;
min_wide = 300;
min_high = 300;
min_sz = { wide => min_wide,
high => min_high
};
button_wide = 100;
button_high = 30;
button_corner_radius = 8;
done_first_drawing_window_redraw # This is a hack allowing selfcheck code
= # to wait until first drawing window redraw
make_oneshot_maildrop () # has happened, so as to start up in a
: # known state.
Oneshot_Maildrop(Void); #
######## Begin mutable selfcheck globals ########
#
run_selfcheck = REF FALSE;
add_triangle_watcher_slot # This is a hack to support selfcheck;
= # if it is set non-NULL, drawing_window_loop
REF NULL # will 'give' the displayed triangle list
: # to this slot each time it changes:
Ref (Null_Or( Mailslot( List( g2d::Point )))); #
drawing_window_''do_reset''_watcher_slot # This is another hack to support selfcheck;
= # if it is set non-NULL, drawing_window_loop
REF NULL # will 'give' () to the slot each time it
: # does a reset:
Ref (Null_Or( Mailslot(Void))); #
selfcheck_tests_passed = REF 0;
selfcheck_tests_failed = REF 0;
#
######## End mutable selfcheck globals ########
app_task = REF (NULL: Null_Or(Apptask));
fun sprint_thread thread # A little debug-support hack.
=
{ (get_thread's_name thread) -> thread_name;
(get_thread's_id thread) -> thread_id;
(get_thread's_task thread) -> task;
(get_task's_id task) -> task_id;
(get_task's_name task) -> task_name;
sprintf "thread %d(%s) in task \t%d(%s)" thread_id thread_name task_id task_name;
};
fun print_thread thread # A little debug-support hack.
=
printf "Created %s\n" (sprint_thread thread);
fun reset_global_mutable_state () # Reset above state variables to load-time values.
= # This will be needed if (say) we get run multiple times interactively without being reloaded.
{ run_selfcheck := FALSE;
#
add_triangle_watcher_slot := NULL;
drawing_window_''do_reset''_watcher_slot := NULL;
#
app_task := (NULL: Null_Or(Apptask));
#
selfcheck_tests_passed := 0;
selfcheck_tests_failed := 0;
};
fun test_passed () = selfcheck_tests_passed := *selfcheck_tests_passed + 1;
fun test_failed () = selfcheck_tests_failed := *selfcheck_tests_failed + 1;
#
fun assert bool = if bool test_passed ();
else test_failed ();
fi;
#
fun test_stats ()
=
{ passed => *selfcheck_tests_passed,
failed => *selfcheck_tests_failed
};
fun kill_triangle_app ()
=
{
kill_task { success => TRUE, task => (the *app_task) };
};
fun wait_for_app_task_done ()
=
{
task = the *app_task;
#
task_finished' = task_done__mailop task;
block_until_mailop_fires task_finished';
success = get_task's_state task == state::SUCCESS;
assert (success);
};
# Put reset button window at bottom left of hostwindow:
#
fun reset_button_window_site (hostwindow_size as { wide => hostwindow_wide, high => hostwindow_high })
=
{ # We put the reset and exit buttons ten pixels
# above the bottom of the hostwindow, side by side,
# splitting the remaining horizontal space evenly
# around them (right/center/left):
#
remaining_horizontal_space = hostwindow_wide - 2*button_wide;
gap = remaining_horizontal_space / 3;
{
upperleft => { col => gap,
row => (hostwindow_high - (button_high+10))
},
size => { wide => button_wide,
high => button_high
},
border_thickness => 0
}
: g2d::Window_Site;
};
# Put exit button window at bottom right of hostwindow:
#
fun exit_button_window_site (hostwindow_size as { wide => hostwindow_wide, high => hostwindow_high })
=
{ # We put the reset and exit buttons ten pixels
# above the bottom of the hostwindow, side by side,
# splitting the remaining horizontal space evenly
# around them (right/center/left):
#
remaining_horizontal_space = hostwindow_wide - 2*button_wide;
gap = remaining_horizontal_space / 3;
{
upperleft => { col => 2*gap + button_wide,
row => (hostwindow_high - (button_high+10))
},
size => { wide => button_wide,
high => button_high
},
border_thickness => 0
}
: g2d::Window_Site;
};
# Let drawing window fill the rest of hostwindow:
#
fun drawing_window_site (hostwindow_size as { wide: Int, high: Int })
=
{
border_thickness => 1,
upperleft => { col => 5, row => 5 },
#
size => { wide => wide - 10,
high => high - (button_high+25)
}
}
: g2d::Window_Site;
# Thread to exercise the app by simulating user
# mouseclicks and verifying their effects:
#
fun make_selfcheck_thread
{ xsession: xc::Xsession,
hostwindow: xc::Window,
drawing_window: xc::Window,
exit_button_window: xc::Window,
reset_button_window: xc::Window
}
=
{ fun get_''seen_first_expose''_oneshot
window
=
case (xc::get_''seen_first_expose''_oneshot_of window)
#
THE oneshot => oneshot;
NULL => get_''seen_first_expose''_oneshot window; # Cannot actually happen.
esac;
fun selfcheck ()
=
{
check_periodic_asynchronous_operation ();
check_get_set_of_mouse_pointer ();
wait_until_gui_is_stable { drawing_window };
(check_drawing_window_triangle_draw { }) -> { antedraw_midwindow_image };
check_reset_button_operation { antedraw_midwindow_image };
check_exit_button_operation { };
}
where
# Try sleep/work loop. This actually wasn't
# working at one point due to blocking on the
# X server socket, so it is worth checking:
#
fun check_periodic_asynchronous_operation ()
=
{ # Set up a counter which will be
# shared between our two threads:
#
counter = REF 0;
# Spin off a thread which increments
# the counter at 50Hz or so. (The
# thread scheduler is normally run
# off a 50Hz SIGALRM signal, so we
# cannot count on shorter sleeps.)
#
xtr::make_thread "count slowly" count_slowly
where
fun count_slowly ()
=
for (i = 0; i < 50; ++i) {
# We currently run SIGALRM at 50HZ, so
# we cannot usefully sleep less than 20ms
# at a go:
#
sleep_for 0.02;
counter := *counter + 1;
};
end;
# Sleep a fifth of a second. This is
# long enough to get significant results
# but short enough not to slow down our
# "make check" runs noticably:
#
sleep_for 0.2;
# In a perfect world the counter would be at 10 now;
# in practice we will settle for anything between 5 and 15:
#
count = *counter;
#
success = (count >= 5 and count <= 15);
if !success printf "\nAssert failing -- check_periodic_asynchronous_operation\n"; fi;
assert (success);
};
# Originally I thought we'd have to move the
# mouse pointer around to generate simulated
# mouseclicks on buttons. Then I discovered
# that the X protocol SendEvent request allows
# arbitrary events to be simulated for test
# purposes without moving the real mouse pointer,
# so I didn't use it. But this is still a
# potentially useful facility, so we might as
# well unit-test it to defend it against bitrot:
#
fun check_get_set_of_mouse_pointer ()
=
{ # Note current mouse position, then move it to the origin:
#
(xc::get_mouse_location xsession) -> { row => initial_row, col => initial_col } ;
xc::set_mouse_location xsession ({ row => 0, col => 0 });
(xc::get_mouse_location xsession) -> { row, col } ;
#
success = (row == 0 and col == 0);
if !success printf "\nAssert failing -- check_get_set_of_mouse_pointer\n"; fi;
assert (success);
# Return mouse to its original location,
# with luck before the user notices:
#
xc::set_mouse_location xsession ({ row => initial_row, col => initial_col });
(xc::get_mouse_location xsession) -> { row, col };
#
success = (row == initial_row
and col == initial_col);
if !success printf "\nAssert failing -- check_get_set_of_mouse_pointer II\n"; fi;
assert (success);
};
# Wait for drawing window to
# get its first EXPOSE x event:
#
fun wait_for_first_drawing_window_expose ()
=
{
# Normally we would use the
#
# seen_first_redraw_oneshot_of
#
# from
#
#
src/lib/x-kit/widget/old/basic/widget.api #
# but the logic here doesn't use the widget
# support (I'm guessing this app predates
# the widget layer) so we have to use the
# following call instead:
#
#
seen_first_drawing_window_expose_oneshot
=
get_''seen_first_expose''_oneshot
#
drawing_window;
get_from_oneshot seen_first_drawing_window_expose_oneshot;
};
# Figure midpoint of window and also
# a small box centered on the midpoint:
#
fun midwindow window
=
{ # Get size of drawing window:
#
(xc::get_window_site window)
->
{ row, col, high, wide };
# Define midpoint of drawing window,
# and a 9x9 box enclosing it:
#
stipulate
row = high / 2;
col = wide / 2;
herein
midpoint = { row, col };
midbox = { row => row - 4, col => col - 4, high => 9, wide => 9 };
end;
(midpoint, midbox);
};
# We do not want to start running the
# selfcheck code until the application
# is ready to respond:
#
fun wait_until_gui_is_stable { drawing_window }
=
{ # First order of business is to wait
# until things are up and running.
#
# This may actually not be needed now
# that we wait on
# done_first_drawing_window_redraw
# in below logic, but exercising the
# facility is useful anyhow:
#
wait_for_first_drawing_window_expose ();
# Wait until first redraw is done:
#
get_from_oneshot done_first_drawing_window_redraw;
};
fun check_drawing_window_triangle_draw { }
=
{
(midwindow drawing_window) -> ( drawing_window_midpoint, drawing_window_midbox);
(midwindow exit_button_window) -> ( exit_button_window_midpoint, _ );
(midwindow reset_button_window) -> (reset_button_window_midpoint, _ );
# Fetch from X server the mid-window pixels
# over which we are about to draw a triangle.
# These should all be all background color (black)
# at the moment:
#
# Introduced the following three sleep_for calls because checks
# 7 and 8 were failing after switchover to redirected socket calls.
# I suspect some race condition. The problem comes and goes;
# at the moment I'm not sure if these waits 'fixed' the problem or if
# it is just in spontaneous remission. If the problem *is* 'fixed',
# maybe only the first sleep is actually needed? The sleep time is
# also a pure-guesswork first try. -- 2012-12-24 CrT
sleep_for 0.2;
antedraw_midwindow_image = xc::make_clientside_pixmap_from_window (drawing_window_midbox, drawing_window);
# Do it again, verify that they compare equal:
#
{
# sleep_for 0.2;
antedraw_midwindow_image2 = xc::make_clientside_pixmap_from_window (drawing_window_midbox, drawing_window);
#
# sleep_for 0.2;
success = xc::same_cs_pixmap (antedraw_midwindow_image, antedraw_midwindow_image2);
if !success printf "\nAssert failing -- check_drawing_window_triangle_draw\n"; fi;
assert (success);
};
# Set up a hook to detect when
#
# add_triangle ()
#
# runs. To avoid race conditions, we must
# do this BEFORE sending the below simulated
# mouseclicks. (I forgot this at first, and
# in fact got bitten.)
#
triangle_list_slot = make_mailslot ();
#
add_triangle_watcher_slot := THE triangle_list_slot;
# Simulate a mouseclick in center of drawing window:
#
xc::send_fake_mousebutton_press_xevent
{ window => drawing_window,
button => xc::MOUSEBUTTON 1,
point => drawing_window_midpoint
};
#
xc::send_fake_mousebutton_release_xevent
{ window => drawing_window,
button => xc::MOUSEBUTTON 1,
point => drawing_window_midpoint
};
#
do_one_mailop [
take_from_mailslot' triangle_list_slot ==> check_triangle_list1,
timeout' ==> do_timeout1
]
where
timeout' = timeout_in' 5.0;
fun check_triangle_list1 [ point ]
=>
{
success = g2d::point::eq (point, drawing_window_midpoint);
if !success printf "\nAssert failing -- check_triangle_list1\n"; fi;
assert (success);
add_triangle_watcher_slot := NULL;
};
check_triangle_list1 []
=>
{
printf "\ncalling test_failed -- check_triangle_list1 []\n";
test_failed ();
add_triangle_watcher_slot := NULL;
};
check_triangle_list1 triangles
=>
{
printf "\ncalling test_failed -- check_triangle_list1 triangles\n";
test_failed ();
add_triangle_watcher_slot := NULL;
};
end;
fun do_timeout1 ()
=
{ test_failed ();
#
add_triangle_watcher_slot := NULL;
};
end;
# Fetch from X server the mid-window pixels
# over which we should have drawn a triangle.
#
# Verify that they differ from our original
# all-black image of the same area:
#
{ postdraw_midwindow_image = xc::make_clientside_pixmap_from_window (drawing_window_midbox, drawing_window);
#
# 2010-08-31 CrT: This was working on maw (4-core opteron running X over net);
# it is currently failing on al (new 6-core Phenom II running X direct to screen);
# This is probably a race condition; I'm not motivated to track it down just
# now, so I'm just commenting out the offending test: XXX BUGGO FIXME
# assert (not (xc::same_cs_pixmap (antedraw_midwindow_image, postdraw_midwindow_image)));
};
{ antedraw_midwindow_image };
};
fun check_reset_button_operation { antedraw_midwindow_image }
=
{
(midwindow drawing_window) -> ( drawing_window_midpoint, drawing_window_midbox);
(midwindow exit_button_window) -> ( exit_button_window_midpoint, _ );
(midwindow reset_button_window) -> (reset_button_window_midpoint, _ );
# Set up a hook to detect when drawing_window_loop
#
# do_reset ()
#
# runs. To avoid race conditions, we must
# do this BEFORE sending the below simulated
# mouseclicks. (I forgot this at first, and
# in fact got bitten.)
#
drawing_window_''do_reset''_slot = make_mailslot ();
#
drawing_window_''do_reset''_watcher_slot := THE drawing_window_''do_reset''_slot;
# Simulate a mouseclick in center of reset button:
#
xc::send_fake_mousebutton_press_xevent
{ window => reset_button_window,
button => xc::MOUSEBUTTON 1,
point => reset_button_window_midpoint
};
#
xc::send_fake_mousebutton_release_xevent
{ window => reset_button_window,
button => xc::MOUSEBUTTON 1,
point => reset_button_window_midpoint
};
#
do_one_mailop [
take_from_mailslot' drawing_window_''do_reset''_slot ==> check_reset,
timeout' ==> do_timeout2
]
where
timeout' = timeout_in' 5.0;
#
fun check_reset ()
=
{ drawing_window_''do_reset''_watcher_slot := NULL;
#
test_passed ();
};
fun do_timeout2 ()
=
{ drawing_window_''do_reset''_watcher_slot := NULL;
#
test_failed ();
};
end;
# Fetch from X server the mid-window pixels
# over which we drew the triangle.
#
# Verify that they match our original
# all-black image of the same area:
#
{ postreset_midwindow_image = xc::make_clientside_pixmap_from_window (drawing_window_midbox, drawing_window);
#
success = xc::same_cs_pixmap (antedraw_midwindow_image, postreset_midwindow_image);
if !success printf "\nAssert failing -- check_reset_button_operation\n"; fi;
assert (success);
};
};
fun check_exit_button_operation { }
=
{
(midwindow exit_button_window)
->
(exit_button_window_midpoint, _);
# Simulate a mouseclick in center of exit button:
#
xc::send_fake_mousebutton_press_xevent
{ window => exit_button_window,
button => xc::MOUSEBUTTON 1,
point => exit_button_window_midpoint
};
#
xc::send_fake_mousebutton_release_xevent
{ window => exit_button_window,
button => xc::MOUSEBUTTON 1,
point => exit_button_window_midpoint
};
};
end; # fun selfcheck
xtr::make_thread "tri-app selfcheck" selfcheck;
};
fun make_hostwindow xdisplay xauthentication
=
( xsession,
screen,
window,
kidplug
)
where
xsession = xc::open_xsession (xdisplay, xauthentication);
screen = xc::default_screen_of xsession;
window_size = { wide => 450, high => 400 };
my (window, kidplug, delete_slot) # 2009-12-10 CrT: Had to add 'delete_slot' so it would compile.
=
xc::make_simple_top_window screen
{
site => { upperleft => { col=>0, row=>0 },
size => window_size, border_thickness=>1
}
: g2d::Window_Site,
#
border_color => xc::black,
background_color => xc::rgb8_white
};
icon_ro_pixmap # serverside
=
xc::make_readonly_pixmap_from_clientside_pixmap
screen
ib::icon_bitmap; # clientside
xc::set_window_manager_properties window
{
window_name => THE "Triangle",
icon_name => THE "triangle",
#
size_hints =>
[
xc::HINT_PPOSITION,
xc::HINT_PSIZE,
xc::HINT_PMIN_SIZE min_sz
],
#
nonsize_hints => [ xc::HINT_ICON_RO_PIXMAP icon_ro_pixmap ],
#
class_hints => THE { resource_name => "triangle",
resource_class => "Triangle"
},
#
commandline_arguments => cmd::get_commandline_arguments ()
};
xc::show_window window;
end;
# Divide the hostwindow into a drawing window
# over two button windows:
#
fun make_drawing_and_button_windows (screen, hostwindow, top_kidplug)
=
{ (xc::size_of_window hostwindow)
->
hostwindow_size;
drawing_window
=
xc::make_simple_subwindow hostwindow
{
site => drawing_window_site hostwindow_size,
#
border_color => THE xc::black,
background_color => THE xc::rgb8_color0
};
xc::note_''seen_first_expose''_oneshot
drawing_window
(make_oneshot_maildrop ());
reset_button_window
=
xc::make_simple_subwindow hostwindow
{
site => reset_button_window_site hostwindow_size,
#
border_color => NULL,
background_color => THE xc::rgb8_white
};
exit_button_window
=
xc::make_simple_subwindow hostwindow
{
site => exit_button_window_site hostwindow_size,
#
border_color => NULL,
background_color => THE xc::rgb8_white
};
xc::note_''seen_first_expose''_oneshot
reset_button_window
(make_oneshot_maildrop ());
xc::show_window drawing_window;
xc::show_window exit_button_window;
xc::show_window reset_button_window;
{ hostwindow,
top_kidplug,
drawing_window,
reset_button_window,
exit_button_window
};
};
fun make_reset_button_thread (reset_button_window, reset_button_kidplug)
=
{ xtr::make_thread "reset button" loop;
#
take_from_mailslot' reset_slot; # Return a mailop which may be used to detect clicks on the reset button.
}
where
# Define a function to re/draw the
# reset button as "RESET" inside a cartouche:
#
fun redraw ()
=
{
draw_cartouche
{
corner_radius => button_corner_radius,
#
box => { col => 0,
row => 0,
#
high => button_high - 1,
wide => button_wide - 1
}
};
draw_string (text_point, text);
}
where
drawable = xc::drawable_of_window reset_button_window;
pen = xc::make_pen
[
xc::p::FUNCTION xc::OP_COPY,
xc::p::FOREGROUND xc::rgb8_black
];
draw_cartouche
=
che::draw_cartouche drawable pen;
text = "RESET";
font = xc::find_else_open_font
#
(xc::xsession_of_window reset_button_window)
#
"9x15";
text_point
=
{ text_width = xc::text_width font text;
#
(xc::font_high font) -> { ascent, descent };
{ col => ( button_wide - text_width) / 2,
row => ((button_high - (ascent + descent)) / 2) + ascent
};
};
draw_string
=
xc::draw_transparent_string drawable pen font;
end;
# Define the main thread loop animating the button.
# We respond to ETC_REDRAW by redrawing our button;
# we respond to a click by resetting the draw thread:
#
(xc::ignore_keyboard reset_button_kidplug)
->
xc::KIDPLUG { from_mouse', from_other', ... };
from_mouse'' = from_mouse' ==> xc::get_contents_of_envelope;
from_other'' = from_other' ==> xc::get_contents_of_envelope;
reset_slot = make_mailslot ();
fun loop ()
=
{ fun do_mouse (xc::MOUSE_FIRST_DOWN _)
=>
put_in_mailslot (reset_slot, ());
do_mouse _
=>
();
end;
fun do_other (xc::ETC_REDRAW _) => redraw ();
do_other xc::ETC_OWN_DEATH => ();
do_other _ => ();
end;
for (;;) {
#
do_one_mailop [
from_mouse'' ==> do_mouse,
from_other'' ==> do_other
];
};
};
end; # fun make_reset_button_thread
fun make_exit_button_thread (exit_button_window, exit_button_kidplug)
=
{ xtr::make_thread "exit button" loop;
#
take_from_mailslot' exit_slot; # Return a mailop which can be used to detect clicks on the exit button.
}
where
# Define a function to re/draw the
# exit button as "EXIT" inside a cartouche:
#
fun redraw ()
=
{
draw_cartouche
{
corner_radius => button_corner_radius,
#
box => { col => 0,
row => 0,
#
high => button_high - 1,
wide => button_wide - 1
}
};
draw_string (text_point, text);
}
where
drawable = xc::drawable_of_window exit_button_window;
pen = xc::make_pen
[
xc::p::FUNCTION xc::OP_COPY,
xc::p::FOREGROUND xc::rgb8_black
];
draw_cartouche
=
che::draw_cartouche drawable pen;
text = "EXIT";
font = xc::find_else_open_font
#
(xc::xsession_of_window exit_button_window)
#
"9x15";
text_point
=
{ text_width = xc::text_width font text;
(xc::font_high font)
->
{ ascent, descent };
{
col => ( button_wide - text_width) / 2,
row => ((button_high - (ascent + descent)) / 2) + ascent
};
};
draw_string
=
xc::draw_transparent_string drawable pen font;
end;
# Define the main thread loop animating the button.
# We respond to ETC_REDRAW by redrawing our button;
# we respond to a click by exiting the program:
#
(xc::ignore_keyboard exit_button_kidplug)
->
xc::KIDPLUG { from_mouse', from_other', ... };
from_mouse'' = from_mouse' ==> xc::get_contents_of_envelope;
from_other'' = from_other' ==> xc::get_contents_of_envelope;
exit_slot = make_mailslot ();
fun loop ()
=
{ fun do_mouse (xc::MOUSE_FIRST_DOWN _)
=>
put_in_mailslot (exit_slot, ());
do_mouse _
=>
();
end;
fun do_other (xc::ETC_REDRAW _) => redraw ();
do_other xc::ETC_OWN_DEATH => ();
do_other _ => ();
end;
for (;;) {
#
do_one_mailop [
from_mouse'' ==> do_mouse,
from_other'' ==> do_other
];
};
};
end; # fun make_exit_button_thread
# Define drawing_window logic to put
# a triangle at each spot the user
# clicks on:
#
stipulate
done_first_redraw = REF FALSE;
herein
fun make_drawing_window_threads (xsession, drawing_window, exit', reset', draw_kidplug)
=
{ xtr::make_thread "drawing window mouse" mouse_loop;
xtr::make_thread "drawing window" {. drawing_window_loop []; };
();
}
where
my xc::KIDPLUG { from_mouse', from_other', ... }
=
xc::ignore_keyboard draw_kidplug;
mouse' = from_mouse' ==> xc::get_contents_of_envelope;
other' = from_other' ==> xc::get_contents_of_envelope;
add_triangle_slot = make_mailslot ();
fun mouse_loop ()
=
for (;;) {
#
case (block_until_mailop_fires mouse')
#
xc::MOUSE_FIRST_DOWN { window_point, ... }
=>
put_in_mailslot (add_triangle_slot, window_point);
#
_ => ();
esac;
};
add_triangle' = take_from_mailslot' add_triangle_slot;
drawable = xc::drawable_of_window drawing_window;
pen = xc::make_pen
[
xc::p::FUNCTION xc::OP_COPY,
xc::p::FOREGROUND xc::rgb8_green
];
draw = xc::fill_polygon drawable pen;
fun draw_triangle ({ col, row } )
=
draw
{
shape => xc::CONVEX_SHAPE,
#
verts => [ { col => col, row => row - 10 },
{ col => col - 8, row => row + 6 },
{ col => col + 8, row => row + 6 }
]
};
# "triangles" is the list of points at which we
# show triangles in the drawing window:
#
fun drawing_window_loop triangles
=
{ fun do_exit ()
=
{
xc::close_xsession xsession;
sleep_for 0.2; # I think close_xsession returns before everything has shut down. Need something cleaner here. XXX SUCKO FIXME.
kill_triangle_app ();
# shut_down_thread_scheduler winix__premicrothread::process::success; # Starting with 6.3, this is no longer the way to shut down an app. :-)
};
fun do_reset ()
=
{
xc::clear_drawable drawable;
case *drawing_window_''do_reset''_watcher_slot
#
THE slot => put_in_mailslot (slot, ());
NULL => ();
esac;
drawing_window_loop [];
};
fun do_other (xc::ETC_REDRAW _)
=>
{ xc::clear_drawable drawable;
#
apply draw_triangle triangles;
# Selfcheck code waits for us to do first redraw
# before starting tests. If this is our first
# redraw, give it the green light:
#
if (not *done_first_redraw)
#
done_first_redraw := TRUE;
put_in_oneshot (done_first_drawing_window_redraw, ());
fi;
drawing_window_loop triangles;
};
do_other xc::ETC_OWN_DEATH
=>
{
();
};
do_other _
=>
{
drawing_window_loop triangles;
};
end;
# In response to a mouse downclick at a point,
# add a triangle to our list and draw it:
#
fun add_triangle point
=
{ draw_triangle point;
case *add_triangle_watcher_slot
#
THE slot => put_in_mailslot (slot, (point ! triangles));
NULL => ();
esac;
drawing_window_loop (point ! triangles);
};
do_one_mailop [
#
exit' ==> do_exit,
reset' ==> do_reset,
other' ==> do_other,
add_triangle' ==> add_triangle
];
}; # fun drawing_window_loop
end; # fun make_drawing_window_threads
end; # stipulate
fun make_toplevel_threads
{
hostwindow,
top_kidplug => xc::KIDPLUG { from_keyboard', from_mouse', from_other', ... },
exit_button_window,
reset_button_window,
drawing_window
}
=
{ xtr::make_thread "triangle router" router;
#
{ exit_button_kidplug,
reset_button_kidplug,
draw_kidplug
};
}
where
(xc::make_widget_cable ()) -> { kidplug => exit_button_kidplug, momplug => exit_button_momplug };
(xc::make_widget_cable ()) -> { kidplug => reset_button_kidplug, momplug => reset_button_momplug };
(xc::make_widget_cable ()) -> { kidplug => draw_kidplug, momplug => draw_momplug };
(xc::make_widget_cable ()) -> { kidplug, momplug };
kidplug = xc::ignore_all kidplug;
fun find_cable envelope
=
case (xc::route_envelope envelope)
#
xc::TO_SELF _ # Envelope has reached its destination window/widget.
=>
momplug;
xc::TO_CHILD msg' # Envelope needs to be passed on down the widget hierarchy.
=>
if (xc::to_window (msg', drawing_window)) draw_momplug;
elif (xc::to_window (msg', exit_button_window)) exit_button_momplug;
elif (xc::to_window (msg', reset_button_window)) reset_button_momplug;
else raise exception DIE "find_cable";
fi;
esac;
fun do_keyboard envelope
=
{ (find_cable envelope)
->
xc::MOMPLUG { keyboard_sink, ... };
block_until_mailop_fires (keyboard_sink envelope);
};
fun do_mouse envelope
=
{ (find_cable envelope)
->
xc::MOMPLUG { mouse_sink, ... };
block_until_mailop_fires (mouse_sink envelope);
};
fun do_other envelope
=
{ (find_cable envelope)
->
xc::MOMPLUG { other_sink, ... };
block_until_mailop_fires (other_sink envelope);
};
fun router ()
=
for (;;) {
#
do_one_mailop [
#
from_keyboard' ==> do_keyboard,
from_mouse' ==> do_mouse,
from_other' ==> do_other
];
};
end; # fun make_toplevel_threads
fun run_triangle_app xdisplay xauthentication
=
{
(make_hostwindow xdisplay xauthentication)
->
( xsession,
screen,
hostwindow,
kidplug
);
(make_drawing_and_button_windows (screen, hostwindow, kidplug))
->
(x as { drawing_window, exit_button_window, reset_button_window, ... } );
(make_toplevel_threads x)
->
{ exit_button_kidplug, reset_button_kidplug, draw_kidplug };
make_drawing_window_threads
( xsession,
drawing_window,
make_exit_button_thread ( exit_button_window, exit_button_kidplug),
make_reset_button_thread (reset_button_window, reset_button_kidplug),
draw_kidplug
);
if *run_selfcheck
#
make_selfcheck_thread { xsession, hostwindow, drawing_window, exit_button_window, reset_button_window };
();
fi;
};
fun do_it' (flgs, display_name)
=
{
xtr::init flgs;
if write_tracelog
#
# Open tracelog file and select tracing level.
# We don't need to truncate any existing file
# because that is already done by the logic in
#
src/lib/std/src/posix/winix-text-file-io-driver-for-posix--premicrothread.pkg #
include package logger; # logger is from
src/lib/src/lib/thread-kit/src/lib/logger.pkg #
set_logger_to (fil::LOG_TO_FILE tracefile);
#
# enable fil::all_logging; # Gross overkill.
fi;
display_name' = case display_name
#
"" => NULL;
_ => THE display_name;
esac;
(xc::get_xdisplay_string_and_xauthentication display_name')
->
( xdisplay, # Typically from $DISPLAY environment variable.
xauthentication: Null_Or(xc::Xauthentication) # Typically from ~/.Xauthority
);
triangle_app_task
=
make_task "triangle app" [];
app_task := THE triangle_app_task;
xtr::make_thread' [ THREAD_NAME "triangle app",
THREAD_TASK triangle_app_task
]
{. run_triangle_app xdisplay xauthentication; }
();
sleep_for 0.3; # Give threads long enough to be created.
wait_for_app_task_done ();
winix__premicrothread::process::success;
};
fun do_it s
=
do_it' ([], s);
fun selfcheck ()
=
{
reset_global_mutable_state (); # Don't depend on load-time state initialization -- we might get run multiple times interactively, say.
run_selfcheck := TRUE;
do_it' ([], "");
test_stats (); # We return { passed, failed } to caller.
};
fun main (program, server ! _) => do_it server;
main _ => do_it "";
end;
}; # package main
end;