Finally got around to fixing the "logcommands" command, which has

been broken ever since moving to the Tcl/Tk wrapped version.  Added
some new features that allow background commands from the window
handling (like pointer tracking) to be omitted from the log file
via a suspend/resume function.  Added a header file and a few
commands at the top of the log file that align the log file contents
with the screen and box state at the start of logging.  This makes
a log file which can be "played back" by sourcing it from the magic
console prompt.  Per request from Harald Pretl.
This commit is contained in:
Tim Edwards 2023-06-28 21:31:24 -04:00
parent ca469510d5
commit c8a2d06e08
8 changed files with 389 additions and 59 deletions

View File

@ -1 +1 @@
8.3.408
8.3.409

View File

@ -20,10 +20,20 @@
<H2>logcommands</H2>
<HR>
Log all commands into a file
Log all commands and mouse button actions into a file
<HR>
<H3>Usage:</H3>
<BLOCKQUOTE>
<B>logcommands</B> <I>option</I> [<I>file</I>] <BR><BR>
<BLOCKQUOTE>
where <I>option</I> is one of: <B>start</B>, <B>stop</B>,
<B>update</B>, <B>suspend</B>, or <B>resume</B>, and
<I>file</I> is the name of the log file to write to.
</BLOCKQUOTE>
or (legacy usage):
<BLOCKQUOTE>
<B>logcommands</B> [<I>file</I> [<B>update</B>]] <BR><BR>
<BLOCKQUOTE>
@ -33,10 +43,53 @@ Log all commands into a file
<H3>Summary:</H3>
<BLOCKQUOTE>
The <B>logcommands</B> command tells magic to write all
command-line commands and button pushes to the log file
named <I>file</I>. If <B>update</B> is specified, a
screen update is generated after each command executes.
The <B>logcommands</B> command creates and manages a file
containing all of the command-line commands and button actions
as they occur while file writing is active. This creates a
recording of the layout session which can be "played back" by
sourcing the log file. Behavior is controlled by one of the
command options, as follows:
<DL>
<DT><B>start</B> <I>filename</I>
<DD> This opens the log file named <I>filename</I>, writes
a header and issues a few rudimentary commands to
align the file with the current window size and zoom
factor, and the cursor box position. Then it starts
recording commands, not including the <B>logcommands</B>
command itself.
<DT><B>stop</B>
<DD> This command closes any open log file and stops recording
commands.
<DT><B>suspend</B>
<DD> This command stops recording commands until the
<B>logcommands resume</B> command is issued, without
ending or closing the file. This is used by the wrapper
script code to prevent some commands such as those that
track pointer movement from being added to the log file.
<DT><B>resume</B>
<DD> This command resumes recording after it has been suspended.
<DT><B>update</B>
<DD> This command sets a flag that issues a display update
command after every command recorded in the log file.
This usage is largely deprecated, since sourcing the
log file for playback will refresh the display as usual.
</DL>
The legacy syntax "<B>logcommands</B> [<I>file</I> [<B>update</B>]]"
will open a new command log file if <I>file</I> is given, optionally
with the "update" feature (see above) if the <B>update</B> keyword
is added. With no arguments, the log file is ended and closed.
</BLOCKQUOTE>
<H3>Caveats:</H3>
<BLOCKQUOTE>
The initial header and setup in the log file is rudimentary. It
will not attempt to load a technology file, load a layout, or
edit some part of a layout to match what is in the layout window
at the time that the log file was created. The best usage is to
start the log file immediately after starting magic, and only
play back the log file in the tech file environment in which it
was created.
</BLOCKQUOTE>
<H3>Implementation Notes:</H3>

View File

@ -243,10 +243,14 @@ proc magic::drcupdate { option } {
}
proc magic::drcstate { status } {
logcommands suspend
set winlist [*bypass windownames layout]
foreach lwin $winlist {
set framename [winfo parent $lwin]
if {$framename == "."} {return}
if {$framename == "."} {
logcommands resume
return
}
switch $status {
idle {
set dct [*bypass drc list count total]
@ -260,6 +264,7 @@ proc magic::drcstate { status } {
busy { ${framename}.titlebar.drcbutton configure -selectcolor yellow }
}
}
logcommands resume
}
# Create the menu of windows. This is kept separate from the cell manager,
@ -502,6 +507,7 @@ proc magic::captions {{subcommand {}}} {
if {$subcommand != {} && $subcommand != "writeable" && $subcommand != "load"} {
return
}
logcommands suspend
set winlist [magic::windownames layout]
foreach winpath $winlist {
set framename [winfo parent $winpath]
@ -521,6 +527,7 @@ proc magic::captions {{subcommand {}}} {
"Loaded: ${subcaption1} Editing: ${subcaption2} Tool: $Opts(tool) \
Technology: ${techname}"
}
logcommands resume
}
# Allow captioning in the title window by tagging the "load" and "edit" commands
@ -655,8 +662,12 @@ proc magic::cursorview {win} {
if {$win == {}} {
return
}
logcommands suspend
set framename [winfo parent $win]
if {[catch {set cr [*bypass cif scale out]}]} {return}
if {[catch {set cr [*bypass cif scale out]}]} {
logcommands resume
return
}
if {$cr == 0} {return}
set olst [${win} cursor internal]
@ -671,7 +682,10 @@ proc magic::cursorview {win} {
if {[catch {
set olstx [expr {$olstx * $cr}]
set olsty [expr {$olsty * $cr}]
}]} {return}
}]} {
logcommands resume
return
}
if {[${win} box exists]} {
set dlst [${win} box position]
@ -685,20 +699,24 @@ proc magic::cursorview {win} {
set titletext [format "(%+g %+g) microns" $olstx $olsty]
${framename}.titlebar.pos configure -text $titletext
}
logcommands resume
}
proc magic::toolupdate {win {yesno "yes"} {layerlist "none"}} {
global Winopts
if {[magic::display] == "NULL"} {return}
logcommands suspend
if {$win == {}} {
set win [magic::windownames]
}
# Wind3d has a "see" function, so make sure this is not a 3d window
if {$win == [magic::windownames wind3d]} {
logcommands resume
return
}
logcommands resume
set topname [winfo toplevel $win]
set framename [winfo parent $win]
@ -939,11 +957,15 @@ proc magic::techrebuild {winpath {cmdstr ""}} {
proc magic::setscrollvalues {win} {
global Opts
logcommands suspend
set svalues [${win} view get]
set bvalues [${win} view bbox]
set framename [winfo parent ${win}]
if {$framename == "."} {return}
if {$framename == "."} {
logcommands resume
return
}
set bwidth [expr {[lindex $bvalues 2] - [lindex $bvalues 0]}]
set bheight [expr {[lindex $bvalues 3] - [lindex $bvalues 1]}]
@ -985,6 +1007,7 @@ proc magic::setscrollvalues {win} {
proc magic::scrollupdate {win} {
logcommands suspend
if {[magic::display] == "NULL"} {return}
if {[info level] <= 1} {
@ -1001,6 +1024,7 @@ proc magic::scrollupdate {win} {
magic::setscrollvalues $win
}
}
logcommands resume
}
# scrollview: update the magic display to match the

View File

@ -38,6 +38,9 @@ extern unsigned char TxInputRedirect;
#endif
#define TX_LOG_UPDATE 1 /* Update display after every log command */
#define TX_LOG_SUSPEND 2 /* Suspend output logging */
extern int TxCurButtons;
/* These should really be defined by the application, not hard-coded */

View File

@ -28,6 +28,7 @@ static char rcsid[] __attribute__ ((unused)) ="$Header: /usr/cvsroot/magic-8.0/t
#include <sys/types.h>
#include <sys/time.h>
#include <signal.h>
#include <time.h>
#include "tcltk/tclmagic.h"
#include "utils/magsgtty.h"
@ -614,11 +615,11 @@ TxClearPoint()
}
static FILE *txLogFile = NULL;
bool txLogUpdate;
unsigned char txLogFlags;
/*
* ----------------------------------------------------------------------------
* TxLogCommands --
* TxLogStart --
*
* Log all further commands to the given file name. If the file is NULL,
* turn off logging.
@ -632,21 +633,165 @@ bool txLogUpdate;
*/
void
TxLogCommands(fileName, update)
TxLogStart(fileName, mw)
char *fileName;
bool update; /* Request a screen update after each command */
MagWindow *mw; /* Window commands are logged from */
{
if (txLogFile != NULL)
{
(void) fclose(txLogFile);
txLogFile = NULL;
TxError("There is already a log file (%s) open!\n", txLogFile);
return;
}
if (fileName == NULL) return;
txLogUpdate = update;
txLogFlags = 0;
txLogFile = fopen(fileName, "w");
if (txLogFile == NULL)
TxError("Could not open file '%s' for writing.\n", fileName);
else
{
time_t t_stamp = time((time_t *)NULL);
struct tm *clock = localtime(&t_stamp);
char *now = ctime(&t_stamp);
TxPrintf("Logging commands to file \"%s\"\n", fileName);
/* Write comment line header into command file and log the current
* window view position so that cursor positions in the file match
* the layout. If the cursor box is defined, then issue a "box
* values" command so that relative box adjustments are correct.
*/
#ifdef MAGIC_WRAPPER
fprintf(txLogFile, "# Magic command log file\n");
fprintf(txLogFile, "# Using technology: %s\n", DBTechName);
if (mw != NULL)
fprintf(txLogFile, "# Title: %s\n", mw->w_caption);
fprintf(txLogFile, "# Date: %s", now);
#endif
if (mw != NULL)
{
CellDef *rootBoxDef;
Rect rootBox;
#ifndef MAGIC_WRAPPER
// Colon preceeds commands in the non-Tcl verson of the log file.
fprintf(txLogFile, ":");
#endif
fprintf(txLogFile, "view %di %di %di %di\n",
mw->w_surfaceArea.r_xbot, mw->w_surfaceArea.r_ybot,
mw->w_surfaceArea.r_xtop, mw->w_surfaceArea.r_ytop);
if (ToolGetBox(&rootBoxDef, &rootBox))
{
fprintf(txLogFile, "box values %di %di %di %di\n",
rootBox.r_xbot, rootBox.r_ybot,
rootBox.r_xtop, rootBox.r_ytop);
}
}
}
}
/*
* ----------------------------------------------------------------------------
* TxLogStop --
*
* Turn off logging.
*
* Results:
* None.
*
* Side effects:
* File IO.
* ----------------------------------------------------------------------------
*/
void
TxLogStop()
{
if (txLogFile != NULL)
{
TxPrintf("Ending command logging to file.\n");
fclose(txLogFile);
txLogFile = NULL;
txLogFlags = 0;
}
}
/*
* ----------------------------------------------------------------------------
* TxLogUpdate --
*
* Set the log file flags to force a display refresh after logged commands
*
* Results:
* None.
*
* Side effects:
* File IO.
* ----------------------------------------------------------------------------
*/
void
TxLogUpdate()
{
if (txLogFile == NULL)
{
TxError("There is no log file to set an update flag on.\n");
return;
}
if (txLogFlags & TX_LOG_UPDATE)
{
txLogFlags &= ~TX_LOG_UPDATE;
TxPrintf("No display refresh after logged commands.\n");
}
else
{
txLogFlags |= TX_LOG_UPDATE;
TxPrintf("Forcing display refresh after logged commands.\n");
}
}
/*
* ----------------------------------------------------------------------------
* TxLogSuspend --
*
* Suspend command logging
*
* Results:
* None.
*
* Side effects:
* File IO.
* ----------------------------------------------------------------------------
*/
void
TxLogSuspend()
{
if (txLogFile == NULL)
return;
txLogFlags |= TX_LOG_SUSPEND;
}
/*
* ----------------------------------------------------------------------------
* TxLogResume --
*
* Resume command logging
*
* Results:
* None.
*
* Side effects:
* File IO.
* ----------------------------------------------------------------------------
*/
void
TxLogResume()
{
if (txLogFile == NULL)
return;
txLogFlags &= ~TX_LOG_SUSPEND;
}
/*
@ -681,25 +826,61 @@ txLogCommand(cmd)
0
};
#ifdef MAGIC_WRAPPER
char *pfix = "";
#else
char *pfix = ":";
#endif
/* Do not do anything if there is no log file */
if (txLogFile == (FILE *) NULL) return;
if (cmd->tx_wid >= 0) {
/* Command has a window associated with it. */
fprintf(txLogFile, ":setpoint %d %d %d\n",
cmd->tx_p.p_x, cmd->tx_p.p_y, cmd->tx_wid);
} else {
/* No window associated with the command. */
fprintf(txLogFile, ":setpoint %d %d\n",
cmd->tx_p.p_x, cmd->tx_p.p_y);
}
/* Do not write anything if the log file is suspended */
if (txLogFlags & TX_LOG_SUSPEND) return;
if (cmd->tx_argc > 0)
{
int i;
fprintf(txLogFile, ":%s", cmd->tx_argv[0]);
char *postns;
/* Do not output "logcommand" commands to the log file.
* Do not output "*bypass" commands to the log file.
*/
postns = strstr(cmd->tx_argv[0], "::");
if (postns == NULL)
postns = cmd->tx_argv[0];
else
postns += 2;
if (!strncmp(postns, "logc", 4))
return;
else if (!strcmp(postns, "*bypass"))
return;
/* Commands ending in "cursor" should be preceeded by a set point */
/* to indicate where the pointer was at the time of the command. */
if (!strcmp(cmd->tx_argv[cmd->tx_argc - 1], "cursor"))
{
if (cmd->tx_wid >= 0)
{
/* Command has a window associated with it. */
fprintf(txLogFile, "%ssetpoint %d %d %d\n",
pfix, cmd->tx_p.p_x, cmd->tx_p.p_y, cmd->tx_wid);
}
else
{
/* No window associated with the command. */
fprintf(txLogFile, "%ssetpoint %d %d\n",
pfix, cmd->tx_p.p_x, cmd->tx_p.p_y);
}
}
else if (!strcmp(postns, "setpoint")) return;
fprintf(txLogFile, "%s%s", pfix, cmd->tx_argv[0]);
for (i = 1; i < cmd->tx_argc; i++)
{
fprintf(txLogFile, " '%s'", cmd->tx_argv[i]);
fprintf(txLogFile, " %s", cmd->tx_argv[i]);
}
fprintf(txLogFile, "\n");
}
@ -722,12 +903,13 @@ txLogCommand(cmd)
default: {ASSERT(FALSE, "txLogCommand"); break; };
}
fprintf(txLogFile, ":pushbutton %s %s\n",
txButTable[but], txActTable[act]);
fprintf(txLogFile, "%spushbutton %s %s\n",
pfix, txButTable[but], txActTable[act]);
}
if (txLogUpdate)
fprintf(txLogFile, ":updatedisplay\n");
(void) fflush(txLogFile);
if (txLogFlags & TX_LOG_UPDATE)
fprintf(txLogFile, "%supdatedisplay\n", pfix);
fflush(txLogFile);
}
/*
@ -1183,6 +1365,8 @@ TxTclDispatch(clientData, argc, argv, quiet)
result = WindSendCommand((MagWindow *)clientData, tclcmd, quiet);
if (txLogFile != NULL) txLogCommand(tclcmd);
TxFreeCommand(tclcmd);
// Don't update the command number on bypass commands, or else

View File

@ -130,9 +130,13 @@ extern void TxSetPoint();
extern int TxGetPoint();
extern void TxClearPoint();
/* Routine to set up command logging.
/* Routines to handle command logging.
*/
extern void TxLogCommands();
extern void TxLogStart();
extern void TxLogStop();
extern void TxLogUpdate();
extern void TxLogSuspend();
extern void TxLogResume();
/* Routines for handling input events. A typical device driver in the

View File

@ -863,18 +863,23 @@ windHelpCmd(w, cmd)
ASSERT(FALSE, windHelpCmd);
}
static char *logKeywords[] =
{
"update",
0
};
/*
* ----------------------------------------------------------------------------
* windLogCommandsCmd --
*
* Log the commands and button pushes in a file.
*
* Syntax:
* logcommands start <filename> Open and start a new command log file
* logcommands stop End and close a command log file
* logcommands update Refresh display after each log command
* logcommands suspend Suspend logging of commands
* logcommands resume Resume logging of commands
*
* Legacy syntax:
* logcommands <filename> [update] Start a new command log file with updating
* logcommands End and close a command log file
*
* Results:
* None.
*
@ -883,35 +888,74 @@ static char *logKeywords[] =
* ----------------------------------------------------------------------------
*/
#define LOG_CMD_START 0 // Create a new log file (default behavior)
#define LOG_CMD_STOP 1 // End a log file and stop logging.
#define LOG_CMD_UPDATE 2 // Update display after every logged command.
#define LOG_CMD_SUSPEND 3 // Suspend command logging.
#define LOG_CMD_RESUME 4 // Resume command logging.
void
windLogCommandsCmd(w, cmd)
MagWindow *w;
TxCommand *cmd;
{
char *fileName;
bool update;
char *fileName = NULL;
unsigned char flags = 0;
int idx = LOG_CMD_STOP;
static char *logKeywords[] = {"start", "stop", "update", "suspend", "resume", 0};
if ((cmd->tx_argc < 1) || (cmd->tx_argc > 3)) goto usage;
update = FALSE;
if (cmd->tx_argc == 1)
fileName = NULL;
else
fileName = cmd->tx_argv[1];
if (cmd->tx_argc == 3) {
int i;
i = Lookup(cmd->tx_argv[cmd->tx_argc - 1], logKeywords);
if (i != 0) goto usage;
update = TRUE;
if (cmd->tx_argc > 1)
{
idx = Lookup(cmd->tx_argv[1], logKeywords);
if (idx < 0) fileName = cmd->tx_argv[1];
}
TxLogCommands(fileName, update);
return;
if (cmd->tx_argc == 3)
{
int i;
if (fileName == NULL)
fileName = cmd->tx_argv[2];
else
{
/* Legacy behavior: Allow "logcommands <filename> update"
* to mean "logcommands start <filename> ; logcommands update"
*/
i = Lookup(cmd->tx_argv[2], logKeywords);
if (i != LOG_CMD_UPDATE) goto usage;
flags = TX_LOG_UPDATE;
idx = LOG_CMD_START;
}
}
switch (idx)
{
case LOG_CMD_START:
if (fileName == NULL) break;
TxLogStart(fileName, w);
if (flags & TX_LOG_UPDATE)
TxLogUpdate();
return;
case LOG_CMD_STOP:
TxLogStop();
return;
case LOG_CMD_UPDATE:
TxLogUpdate();
return;
case LOG_CMD_SUSPEND:
TxLogSuspend();
return;
case LOG_CMD_RESUME:
TxLogResume();
return;
}
usage:
TxError("Usage: %s [filename [update]]\n", cmd->tx_argv[0]);
TxError("Usage: %s [start|stop|update|suspend|resume [filename]]\n",
cmd->tx_argv[0]);
}
/*

View File

@ -311,6 +311,24 @@ WindSendCommand(w, cmd, quiet)
if ((WindNewButtons == 0) && (windGrabber != (WindClient) NULL))
WindReleaseInput((WindClient) rc);
/* This is a bit of a hack, because it shouldn't be done in so many
* places, but the command logging needs to regenerate the setpoint
* positions as commands, and it needs the command's Y value to be
* correct for the command. It is okay to modify the command's
* recorded position value, because logging the command is the only
* other thing done with the structure.
*/
if (w != NULL)
{
switch (WindPackageType)
{
case WIND_X_WINDOWS:
/* Windows have origin at lower-left corner */
cmd->tx_p.p_y = w->w_allArea.r_ytop - cmd->tx_p.p_y;
break;
}
}
return 0;
}