magic/textio/txCommands.c

1842 lines
45 KiB
C
Raw Normal View History

/*
* txCommands.c --
*
* Reads commands from devices and sends them to the window package,
* which sends them to a particular window.
*
* *********************************************************************
* * Copyright (C) 1985, 1990 Regents of the University of California. *
* * Permission to use, copy, modify, and distribute this *
* * software and its documentation for any purpose and without *
* * fee is hereby granted, provided that the above copyright *
* * notice appear in all copies. The University of California *
* * makes no representations about the suitability of this *
* * software for any purpose. It is provided "as is" without *
* * express or implied warranty. Export of this software outside *
* * of the United States of America may require an export license. *
* *********************************************************************
*/
#ifndef lint
static char rcsid[] __attribute__ ((unused)) ="$Header: /usr/cvsroot/magic-8.0/textio/txCommands.c,v 1.6 2009/09/11 16:09:49 tim Exp $";
#endif /* not lint */
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <signal.h>
#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#include "tcltk/tclmagic.h"
#include "utils/magsgtty.h"
#include "utils/magic.h"
#include "textio/textio.h"
#include "utils/geometry.h"
#include "textio/txcommands.h"
#include "textio/textioInt.h"
#include "utils/macros.h"
#include "windows/windows.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "dbwind/dbwind.h"
#include "drc/drc.h"
#include "utils/signals.h"
#include "graphics/graphics.h"
#include "utils/dqueue.h"
#include "utils/malloc.h"
#include "utils/utils.h"
#ifdef SCHEME_INTERPRETER
#include "lisp/lisp.h"
#endif
/* C99 compat */
#include "windows/windows.h"
#include "utils/main.h"
/* Turning this flag on prints out input events and commands as they
* are processed.
*/
bool TxDebug = FALSE;
/* A mask of the file descriptors for all input devices.
*/
static fd_set txInputDescriptors;
txCommands.c txInputDevRec FD select() usage overhaul There are numerous concerns with the original code from a read through. The #define TX_MAX_OPEN_FILES 20 is badly named, the txCommand.c uses fd_set to hold active FD for each device, and makes no attempt to bounds check the 'fd' or 'fd_set' of any TxAdd1InputDevice() is in range. The real name of this variable should be TX_MAX_INPUT_DEVICES as it related to the fixed compile time slots available for devices, each input device must have at least one active FD and it can be in the range that fd_set allows, which is 0..1023 on linux. So based on this being the original intention, due to the way the code is constructed and API calls made available, the file has been reworked to allow all these considerations at the same time. Now the design should be FD clean for FDs in the range 0..1023 instead of being artificially clamped to the first 20 FDs being valid input devices. Renaming of variable 'i' to 'fd' when it relates to an fd number, so all uses of FD_XXXX() should use fd, this helps clarify the different domain the number relates to. When 'i' is used it relates to the index into the txInputDevRec array. A large part of the concerns relate to trying to mix the two numbering domains interchangeably, just using a different name really clears up understanding to the reader. Other items that look like errors TxDelete1InputDevice() will shuffle-remove entries with no active FD, but it is iterating an array it is also modifying, so it looks like it would have incorrectly skipped the next item that should be inspected just after a shuffle-removal occurred. The various iterators that works from 0..TX_MAX_OPEN_FILES incorrectly limited the visibility of the routines to the first 20 FDs instead of the full FD_SETSIZE the TxAddInputDevice API call allows to be registered. Passing in TxAdd1InputDevice with an fd above 19 would have resulted in everything looking successful until select() was used, then the kernel would likely not sleep/wait because the input fd_set would look blank due to being clipped by nfds=TX_MAX_OPEN_FILES (instead of that plus 1). The use of TX_MAX_OPEN_FILES in select instead of TX_MAX_OPEN_FILES+1 for the 'nfds' field would have meant a device with fd=19 would not work as the design intended. The define definition has been removed from the module public header, I assume it was there for use by another module, but the incorrect select() usage has been corrected over there in a previous commit. So now the define can be maintained near the array it relates to. While the code might looks less 'efficient' as it is now sweeping all 1024 FDs when input devices during add/remove (hopefully there maybe some compiler auto-vectorization/tzcnt use there). The main event loop is slightly more 'efficient' (especially for the single device with input fd=0 case) as it will only check the 1 FD each input event loop iteration, not check all 20 FDs for data readyness everytime.
2025-02-24 09:49:07 +01:00
/* Used for the 'nfds' field for select() syscall, the highest
* fd number that is set in the txInputDescriptors bitmask.
* Don't forget to plus 1 for select().
*/
static int txInputDescriptors_nfds;
txCommands.c txInputDevRec FD select() usage overhaul There are numerous concerns with the original code from a read through. The #define TX_MAX_OPEN_FILES 20 is badly named, the txCommand.c uses fd_set to hold active FD for each device, and makes no attempt to bounds check the 'fd' or 'fd_set' of any TxAdd1InputDevice() is in range. The real name of this variable should be TX_MAX_INPUT_DEVICES as it related to the fixed compile time slots available for devices, each input device must have at least one active FD and it can be in the range that fd_set allows, which is 0..1023 on linux. So based on this being the original intention, due to the way the code is constructed and API calls made available, the file has been reworked to allow all these considerations at the same time. Now the design should be FD clean for FDs in the range 0..1023 instead of being artificially clamped to the first 20 FDs being valid input devices. Renaming of variable 'i' to 'fd' when it relates to an fd number, so all uses of FD_XXXX() should use fd, this helps clarify the different domain the number relates to. When 'i' is used it relates to the index into the txInputDevRec array. A large part of the concerns relate to trying to mix the two numbering domains interchangeably, just using a different name really clears up understanding to the reader. Other items that look like errors TxDelete1InputDevice() will shuffle-remove entries with no active FD, but it is iterating an array it is also modifying, so it looks like it would have incorrectly skipped the next item that should be inspected just after a shuffle-removal occurred. The various iterators that works from 0..TX_MAX_OPEN_FILES incorrectly limited the visibility of the routines to the first 20 FDs instead of the full FD_SETSIZE the TxAddInputDevice API call allows to be registered. Passing in TxAdd1InputDevice with an fd above 19 would have resulted in everything looking successful until select() was used, then the kernel would likely not sleep/wait because the input fd_set would look blank due to being clipped by nfds=TX_MAX_OPEN_FILES (instead of that plus 1). The use of TX_MAX_OPEN_FILES in select instead of TX_MAX_OPEN_FILES+1 for the 'nfds' field would have meant a device with fd=19 would not work as the design intended. The define definition has been removed from the module public header, I assume it was there for use by another module, but the incorrect select() usage has been corrected over there in a previous commit. So now the define can be maintained near the array it relates to. While the code might looks less 'efficient' as it is now sweeping all 1024 FDs when input devices during add/remove (hopefully there maybe some compiler auto-vectorization/tzcnt use there). The main event loop is slightly more 'efficient' (especially for the single device with input fd=0 case) as it will only check the 1 FD each input event loop iteration, not check all 20 FDs for data readyness everytime.
2025-02-24 09:49:07 +01:00
#define TX_MAX_INPUT_DEVICES 20
/* A table of all input devices.
*/
txCommands.c txInputDevRec FD select() usage overhaul There are numerous concerns with the original code from a read through. The #define TX_MAX_OPEN_FILES 20 is badly named, the txCommand.c uses fd_set to hold active FD for each device, and makes no attempt to bounds check the 'fd' or 'fd_set' of any TxAdd1InputDevice() is in range. The real name of this variable should be TX_MAX_INPUT_DEVICES as it related to the fixed compile time slots available for devices, each input device must have at least one active FD and it can be in the range that fd_set allows, which is 0..1023 on linux. So based on this being the original intention, due to the way the code is constructed and API calls made available, the file has been reworked to allow all these considerations at the same time. Now the design should be FD clean for FDs in the range 0..1023 instead of being artificially clamped to the first 20 FDs being valid input devices. Renaming of variable 'i' to 'fd' when it relates to an fd number, so all uses of FD_XXXX() should use fd, this helps clarify the different domain the number relates to. When 'i' is used it relates to the index into the txInputDevRec array. A large part of the concerns relate to trying to mix the two numbering domains interchangeably, just using a different name really clears up understanding to the reader. Other items that look like errors TxDelete1InputDevice() will shuffle-remove entries with no active FD, but it is iterating an array it is also modifying, so it looks like it would have incorrectly skipped the next item that should be inspected just after a shuffle-removal occurred. The various iterators that works from 0..TX_MAX_OPEN_FILES incorrectly limited the visibility of the routines to the first 20 FDs instead of the full FD_SETSIZE the TxAddInputDevice API call allows to be registered. Passing in TxAdd1InputDevice with an fd above 19 would have resulted in everything looking successful until select() was used, then the kernel would likely not sleep/wait because the input fd_set would look blank due to being clipped by nfds=TX_MAX_OPEN_FILES (instead of that plus 1). The use of TX_MAX_OPEN_FILES in select instead of TX_MAX_OPEN_FILES+1 for the 'nfds' field would have meant a device with fd=19 would not work as the design intended. The define definition has been removed from the module public header, I assume it was there for use by another module, but the incorrect select() usage has been corrected over there in a previous commit. So now the define can be maintained near the array it relates to. While the code might looks less 'efficient' as it is now sweeping all 1024 FDs when input devices during add/remove (hopefully there maybe some compiler auto-vectorization/tzcnt use there). The main event loop is slightly more 'efficient' (especially for the single device with input fd=0 case) as it will only check the 1 FD each input event loop iteration, not check all 20 FDs for data readyness everytime.
2025-02-24 09:49:07 +01:00
static txInputDevRec txInputDevice[TX_MAX_INPUT_DEVICES];
static int txLastInputEntry;
/* The current point -- reset by the 'setpoint' command and for each
* interactive command. Calls to TxClearPoint clear previous setpoints,
*
* Each input point is associated with a window, as windows may use
* different coordinate systems.
*
* Also, keep around the last input event.
*/
static bool txHaveCurrentPoint = FALSE;
static Point txCurrentPoint = {100, 100};
static int txCurrentWindowID = WIND_UNKNOWN_WINDOW;
TxInputEvent txLastEvent;
/* Input queues. We have an input queue for low-level input events, and
* a queue for assembled interactive commands and file commands. Also, there
* are free lists for these structures.
*/
#ifdef MAGIC_WRAPPER
extern void send_interpreter(char *);
char *TxBuffer; /* For use with special stdin processing */
unsigned char TxInputRedirect; /* For redirecting Tk events to the terminal */
#endif
DQueue txInputEvents;
DQueue txFreeEvents;
int txNumInputEvents; /* Number of events recieved by Magic so far. */
DQueue txFreeCommands;
/* A zero time value, used for doing a poll via 'select()'.
*/
static struct timeval txZeroTime;
/* Mask of buttons down, as of the last command in the queue (not the last
* command executed).
*/
int TxCurButtons = 0;
/*
* Commands are numbered sequentially starting at zero. This number says
* which command we are collecting or executing.
*/
int TxCommandNumber = 0;
/*
* The "cmd" structure is shared by lisp and eval using this pointer
*/
static TxCommand *lisp_cur_cmd = NULL;
/*
* ----------------------------------------------------------------------------
*
* FD_IsZero --
* FD_OrSet --
txCommands.c txInputDevRec FD select() usage overhaul There are numerous concerns with the original code from a read through. The #define TX_MAX_OPEN_FILES 20 is badly named, the txCommand.c uses fd_set to hold active FD for each device, and makes no attempt to bounds check the 'fd' or 'fd_set' of any TxAdd1InputDevice() is in range. The real name of this variable should be TX_MAX_INPUT_DEVICES as it related to the fixed compile time slots available for devices, each input device must have at least one active FD and it can be in the range that fd_set allows, which is 0..1023 on linux. So based on this being the original intention, due to the way the code is constructed and API calls made available, the file has been reworked to allow all these considerations at the same time. Now the design should be FD clean for FDs in the range 0..1023 instead of being artificially clamped to the first 20 FDs being valid input devices. Renaming of variable 'i' to 'fd' when it relates to an fd number, so all uses of FD_XXXX() should use fd, this helps clarify the different domain the number relates to. When 'i' is used it relates to the index into the txInputDevRec array. A large part of the concerns relate to trying to mix the two numbering domains interchangeably, just using a different name really clears up understanding to the reader. Other items that look like errors TxDelete1InputDevice() will shuffle-remove entries with no active FD, but it is iterating an array it is also modifying, so it looks like it would have incorrectly skipped the next item that should be inspected just after a shuffle-removal occurred. The various iterators that works from 0..TX_MAX_OPEN_FILES incorrectly limited the visibility of the routines to the first 20 FDs instead of the full FD_SETSIZE the TxAddInputDevice API call allows to be registered. Passing in TxAdd1InputDevice with an fd above 19 would have resulted in everything looking successful until select() was used, then the kernel would likely not sleep/wait because the input fd_set would look blank due to being clipped by nfds=TX_MAX_OPEN_FILES (instead of that plus 1). The use of TX_MAX_OPEN_FILES in select instead of TX_MAX_OPEN_FILES+1 for the 'nfds' field would have meant a device with fd=19 would not work as the design intended. The define definition has been removed from the module public header, I assume it was there for use by another module, but the incorrect select() usage has been corrected over there in a previous commit. So now the define can be maintained near the array it relates to. While the code might looks less 'efficient' as it is now sweeping all 1024 FDs when input devices during add/remove (hopefully there maybe some compiler auto-vectorization/tzcnt use there). The main event loop is slightly more 'efficient' (especially for the single device with input fd=0 case) as it will only check the 1 FD each input event loop iteration, not check all 20 FDs for data readyness everytime.
2025-02-24 09:49:07 +01:00
* FD_MaxFd --
* Returns the highest 'fd' in the mask for select(2) nfds argument, or
* -1 when 'fdmask' is empty.
*
* Routines for manipulating set of file descriptors.
*
* ----------------------------------------------------------------------------
*/
bool
FD_IsZero(
const fd_set *fdmask)
{
txCommands.c txInputDevRec FD select() usage overhaul There are numerous concerns with the original code from a read through. The #define TX_MAX_OPEN_FILES 20 is badly named, the txCommand.c uses fd_set to hold active FD for each device, and makes no attempt to bounds check the 'fd' or 'fd_set' of any TxAdd1InputDevice() is in range. The real name of this variable should be TX_MAX_INPUT_DEVICES as it related to the fixed compile time slots available for devices, each input device must have at least one active FD and it can be in the range that fd_set allows, which is 0..1023 on linux. So based on this being the original intention, due to the way the code is constructed and API calls made available, the file has been reworked to allow all these considerations at the same time. Now the design should be FD clean for FDs in the range 0..1023 instead of being artificially clamped to the first 20 FDs being valid input devices. Renaming of variable 'i' to 'fd' when it relates to an fd number, so all uses of FD_XXXX() should use fd, this helps clarify the different domain the number relates to. When 'i' is used it relates to the index into the txInputDevRec array. A large part of the concerns relate to trying to mix the two numbering domains interchangeably, just using a different name really clears up understanding to the reader. Other items that look like errors TxDelete1InputDevice() will shuffle-remove entries with no active FD, but it is iterating an array it is also modifying, so it looks like it would have incorrectly skipped the next item that should be inspected just after a shuffle-removal occurred. The various iterators that works from 0..TX_MAX_OPEN_FILES incorrectly limited the visibility of the routines to the first 20 FDs instead of the full FD_SETSIZE the TxAddInputDevice API call allows to be registered. Passing in TxAdd1InputDevice with an fd above 19 would have resulted in everything looking successful until select() was used, then the kernel would likely not sleep/wait because the input fd_set would look blank due to being clipped by nfds=TX_MAX_OPEN_FILES (instead of that plus 1). The use of TX_MAX_OPEN_FILES in select instead of TX_MAX_OPEN_FILES+1 for the 'nfds' field would have meant a device with fd=19 would not work as the design intended. The define definition has been removed from the module public header, I assume it was there for use by another module, but the incorrect select() usage has been corrected over there in a previous commit. So now the define can be maintained near the array it relates to. While the code might looks less 'efficient' as it is now sweeping all 1024 FDs when input devices during add/remove (hopefully there maybe some compiler auto-vectorization/tzcnt use there). The main event loop is slightly more 'efficient' (especially for the single device with input fd=0 case) as it will only check the 1 FD each input event loop iteration, not check all 20 FDs for data readyness everytime.
2025-02-24 09:49:07 +01:00
int fd;
for (fd = 0; fd < FD_SETSIZE; fd++)
if (FD_ISSET(fd, fdmask)) return FALSE;
return TRUE;
}
void
FD_OrSet(
const fd_set *fdmask,
fd_set *dst)
{
txCommands.c txInputDevRec FD select() usage overhaul There are numerous concerns with the original code from a read through. The #define TX_MAX_OPEN_FILES 20 is badly named, the txCommand.c uses fd_set to hold active FD for each device, and makes no attempt to bounds check the 'fd' or 'fd_set' of any TxAdd1InputDevice() is in range. The real name of this variable should be TX_MAX_INPUT_DEVICES as it related to the fixed compile time slots available for devices, each input device must have at least one active FD and it can be in the range that fd_set allows, which is 0..1023 on linux. So based on this being the original intention, due to the way the code is constructed and API calls made available, the file has been reworked to allow all these considerations at the same time. Now the design should be FD clean for FDs in the range 0..1023 instead of being artificially clamped to the first 20 FDs being valid input devices. Renaming of variable 'i' to 'fd' when it relates to an fd number, so all uses of FD_XXXX() should use fd, this helps clarify the different domain the number relates to. When 'i' is used it relates to the index into the txInputDevRec array. A large part of the concerns relate to trying to mix the two numbering domains interchangeably, just using a different name really clears up understanding to the reader. Other items that look like errors TxDelete1InputDevice() will shuffle-remove entries with no active FD, but it is iterating an array it is also modifying, so it looks like it would have incorrectly skipped the next item that should be inspected just after a shuffle-removal occurred. The various iterators that works from 0..TX_MAX_OPEN_FILES incorrectly limited the visibility of the routines to the first 20 FDs instead of the full FD_SETSIZE the TxAddInputDevice API call allows to be registered. Passing in TxAdd1InputDevice with an fd above 19 would have resulted in everything looking successful until select() was used, then the kernel would likely not sleep/wait because the input fd_set would look blank due to being clipped by nfds=TX_MAX_OPEN_FILES (instead of that plus 1). The use of TX_MAX_OPEN_FILES in select instead of TX_MAX_OPEN_FILES+1 for the 'nfds' field would have meant a device with fd=19 would not work as the design intended. The define definition has been removed from the module public header, I assume it was there for use by another module, but the incorrect select() usage has been corrected over there in a previous commit. So now the define can be maintained near the array it relates to. While the code might looks less 'efficient' as it is now sweeping all 1024 FDs when input devices during add/remove (hopefully there maybe some compiler auto-vectorization/tzcnt use there). The main event loop is slightly more 'efficient' (especially for the single device with input fd=0 case) as it will only check the 1 FD each input event loop iteration, not check all 20 FDs for data readyness everytime.
2025-02-24 09:49:07 +01:00
int fd;
for (fd = 0; fd < FD_SETSIZE; fd++)
if (FD_ISSET(fd, fdmask)) FD_SET(fd, dst);
}
/* A bitmask find max bit set operation */
int
FD_MaxFd(fdmask)
const fd_set *fdmask;
{
int fd;
for (fd = FD_SETSIZE-1; fd >= 0; fd--) /* backwards */
if (FD_ISSET(fd, fdmask)) return fd;
return -1;
}
/* update txInputDescriptors_nfds with the correct value
* call this everytime txInputDescriptors is modified
*/
static void
TxInputDescriptorsRecalc(void)
{
int nfds = FD_MaxFd(&txInputDescriptors);
txInputDescriptors_nfds = (nfds >= 0) ? nfds : 0;
}
/*
* ----------------------------------------------------------------------------
*
* TxReleaseButton --
*
* Pretend that a certain button is up, even though we think that it is
* down. Used only in rare circumstances, such as when SunWindows
* reads a button push behind our back.
*
* Results:
* None.
*
* Side Effects:
* None.
*
* ----------------------------------------------------------------------------
*/
void
TxReleaseButton(
int but)
{
TxCurButtons &= ~but;
}
/*
* ----------------------------------------------------------------------------
*
* TxPrintEvent --
*
* Print an event's contents to stderr.
*
* Results:
* None.
*
* Side Effects:
* Text appears on the terminal.
*
* ----------------------------------------------------------------------------
*/
void
TxPrintEvent(
TxInputEvent *event)
{
TxError("Input event at %p\n ", (void *) event);
if (event->txe_button == TX_EOF) {
TxError("EOF event");
} else if (event->txe_button == TX_BYPASS) {
TxError("Bypass event");
} else if (event->txe_button == TX_CHARACTER) {
char *strc = MacroName(event->txe_ch);
TxError("Character '%s'", strc);
freeMagic(strc);
} else {
switch (event->txe_button) {
case TX_LEFT_BUTTON: {TxError("Left button"); break;};
case TX_MIDDLE_BUTTON: {TxError("Middle button"); break;};
case TX_RIGHT_BUTTON: {TxError("Right button"); break;};
default: {TxError("UNKNOWN button"); break;};
}
switch (event->txe_buttonAction) {
case TX_BUTTON_UP: {TxError(" up"); break;};
case TX_BUTTON_DOWN: {TxError(" down"); break;};
default: {TxError(" UNKNOWN-ACTION"); break;};
}
}
TxError(" at (%d, %d)\n Window: ", event->txe_p.p_x, event->txe_p.p_y);
switch (event->txe_wid) {
case WIND_UNKNOWN_WINDOW: {TxError("unknown\n"); break;};
case WIND_NO_WINDOW: {TxError("none\n"); break;};
default: {TxError("%d\n", event->txe_wid); break;};
}
}
/*
* ----------------------------------------------------------------------------
*
* TxPrintCommand --
*
* Print a command's contents to stderr.
*
* Results:
* None.
*
* Side Effects:
* Text appears on the terminal.
* Repaired so that unprintable text does NOT appear (March 2000, Tim)
*
* ----------------------------------------------------------------------------
*/
void
TxPrintCommand(
TxCommand *cmd)
{
int i, j;
char TxTemp[200];
TxError("Command at %p\n ", (void *) cmd);
if (cmd->tx_button == TX_CHARACTER) {
TxError("Text command with %d words: ", cmd->tx_argc);
for (i = 0; i < cmd->tx_argc; i++) {
for (j = 0; cmd->tx_argv[i][j] && (j < 199); j++)
TxTemp[j] = isprint(cmd->tx_argv[i][j]) ? cmd->tx_argv[i][j] : '*';
TxTemp[j] = '\0';
TxError(" \"%s\"", TxTemp);
}
} else {
switch (cmd->tx_button) {
case TX_LEFT_BUTTON: {TxError("Left button"); break;};
case TX_MIDDLE_BUTTON: {TxError("Middle button"); break;};
case TX_RIGHT_BUTTON: {TxError("Right button"); break;};
default: {TxError("UNKNOWN button"); break;};
}
switch (cmd->tx_buttonAction) {
case TX_BUTTON_UP: {TxError(" up"); break;};
case TX_BUTTON_DOWN: {TxError(" down"); break;};
default: {TxError(" UNKNOWN-ACTION"); break;};
}
}
TxError(" at (%d, %d)\n Window: ", cmd->tx_p.p_x, cmd->tx_p.p_y);
switch (cmd->tx_wid) {
case WIND_UNKNOWN_WINDOW: {TxError("unknown\n"); break;};
case WIND_NO_WINDOW: {TxError("none\n"); break;};
default: {TxError("%d\n", cmd->tx_wid); break;};
}
}
/*
* ----------------------------------------------------------------------------
*
* TxNewEvent --
*
* Get a new event, ready to be filled in.
*
* Results:
* A pointer to a new event.
*
* Side Effects:
* None.
*
* ----------------------------------------------------------------------------
*/
TxInputEvent *
TxNewEvent(void)
{
TxInputEvent *event;
event = (TxInputEvent *) DQPopFront(&txFreeEvents);
if (event == NULL) event = (TxInputEvent *) mallocMagic(sizeof(TxInputEvent));
event->txe_button = TX_CHARACTER;
event->txe_buttonAction = TX_BUTTON_UP;
event->txe_wid = WIND_UNKNOWN_WINDOW;
event->txe_p.p_x = GR_CURSOR_X;
event->txe_p.p_y = GR_CURSOR_Y;
event->txe_ch = 0;
return event;
}
/*
* ----------------------------------------------------------------------------
*
* TxAddEvent --
*
* Add a new event into our input queue.
*
* Results:
* None.
*
* Side Effects:
* None.
*
* ----------------------------------------------------------------------------
*/
void
TxAddEvent(
TxInputEvent *event)
{
ASSERT(event != NULL, "TxAddEvent");
DQPushRear(&txInputEvents, (ClientData) event);
txNumInputEvents++;
}
/*
* ----------------------------------------------------------------------------
*
* TxFreeEvent --
*
* Free an event.
*
* Results:
* None.
*
* Side Effects:
* None.
*
* ----------------------------------------------------------------------------
*/
void
TxFreeEvent(
TxInputEvent *event)
{
ASSERT(event != NULL, "TxFreeEvent");
DQPushRear(&txFreeEvents, (ClientData) event);
}
/*
* ----------------------------------------------------------------------------
*
* TxNewCommand --
*
* Get a new command, ready to be filled in.
*
* Results:
* A pointer to a new command.
*
* Side Effects:
* None.
*
* ----------------------------------------------------------------------------
*/
TxCommand *
TxNewCommand(void)
{
TxCommand *command;
command = (TxCommand *) DQPopFront(&txFreeCommands);
if (command == NULL)
command = (TxCommand *) mallocMagic(sizeof(TxCommand));
command->tx_button = TX_CHARACTER;
return command;
}
/*
* ----------------------------------------------------------------------------
*
* TxFreeCommand --
*
* Free a command.
*
* Results:
* None.
*
* Side Effects:
* None.
*
* ----------------------------------------------------------------------------
*/
void
TxFreeCommand(
TxCommand *command)
{
ASSERT(command != NULL, "TxFreeCommand");
#ifdef MAGIC_WRAPPER
/* No Tx Queue in MAGIC_WRAPPER; call is just to free memory */
freeMagic((char *)command);
#else
DQPushRear(&txFreeCommands, (ClientData) command);
#endif
}
/*
* ----------------------------------------------------------------------------
* TxAddInputDevice --
* TxAdd1InputDevice --
*
* Add a device which is able to deliever commands to magic.
* The caller must ensure that SIGIO signals will be send whenever
* this file descriptor becomes ready.
*
* The usual form of inputProc is:
*
* proc(fd, cdata)
* int fd; -- file descriptor that is ready
* ClientData; -- ClientData as passed to TxAddInputDevice()
* {
* TxInputEvent *event;
* event = TxNewEvent();
* -- read fd here, and fill in event
* TxAddEvent(event);
* -- might do this more than once for multiple events
* }
*
* Results:
* None.
*
* Side effects:
* Modifies internal tables.
* ----------------------------------------------------------------------------
*/
void
TxAddInputDevice(
const fd_set *fdmask, /* A mask of file descriptors that this
* device will handle.
*/
const cb_textio_input_t inputProc,
/* A routine to call. This routine will
* be passed a single file descriptor that
* is ready, and should read that file and
* add events(s) by calling TxNewEvent()
* followed by TxAddEvent().
*/
ClientData cdata) /* Will be passed back to the proc whenever
* it is called.
*/
{
TxDeleteInputDevice(fdmask);
txCommands.c txInputDevRec FD select() usage overhaul There are numerous concerns with the original code from a read through. The #define TX_MAX_OPEN_FILES 20 is badly named, the txCommand.c uses fd_set to hold active FD for each device, and makes no attempt to bounds check the 'fd' or 'fd_set' of any TxAdd1InputDevice() is in range. The real name of this variable should be TX_MAX_INPUT_DEVICES as it related to the fixed compile time slots available for devices, each input device must have at least one active FD and it can be in the range that fd_set allows, which is 0..1023 on linux. So based on this being the original intention, due to the way the code is constructed and API calls made available, the file has been reworked to allow all these considerations at the same time. Now the design should be FD clean for FDs in the range 0..1023 instead of being artificially clamped to the first 20 FDs being valid input devices. Renaming of variable 'i' to 'fd' when it relates to an fd number, so all uses of FD_XXXX() should use fd, this helps clarify the different domain the number relates to. When 'i' is used it relates to the index into the txInputDevRec array. A large part of the concerns relate to trying to mix the two numbering domains interchangeably, just using a different name really clears up understanding to the reader. Other items that look like errors TxDelete1InputDevice() will shuffle-remove entries with no active FD, but it is iterating an array it is also modifying, so it looks like it would have incorrectly skipped the next item that should be inspected just after a shuffle-removal occurred. The various iterators that works from 0..TX_MAX_OPEN_FILES incorrectly limited the visibility of the routines to the first 20 FDs instead of the full FD_SETSIZE the TxAddInputDevice API call allows to be registered. Passing in TxAdd1InputDevice with an fd above 19 would have resulted in everything looking successful until select() was used, then the kernel would likely not sleep/wait because the input fd_set would look blank due to being clipped by nfds=TX_MAX_OPEN_FILES (instead of that plus 1). The use of TX_MAX_OPEN_FILES in select instead of TX_MAX_OPEN_FILES+1 for the 'nfds' field would have meant a device with fd=19 would not work as the design intended. The define definition has been removed from the module public header, I assume it was there for use by another module, but the incorrect select() usage has been corrected over there in a previous commit. So now the define can be maintained near the array it relates to. While the code might looks less 'efficient' as it is now sweeping all 1024 FDs when input devices during add/remove (hopefully there maybe some compiler auto-vectorization/tzcnt use there). The main event loop is slightly more 'efficient' (especially for the single device with input fd=0 case) as it will only check the 1 FD each input event loop iteration, not check all 20 FDs for data readyness everytime.
2025-02-24 09:49:07 +01:00
if (txLastInputEntry == TX_MAX_INPUT_DEVICES)
{
TxError("Too many input devices.\n");
return;
}
txCommands.c txInputDevRec FD select() usage overhaul There are numerous concerns with the original code from a read through. The #define TX_MAX_OPEN_FILES 20 is badly named, the txCommand.c uses fd_set to hold active FD for each device, and makes no attempt to bounds check the 'fd' or 'fd_set' of any TxAdd1InputDevice() is in range. The real name of this variable should be TX_MAX_INPUT_DEVICES as it related to the fixed compile time slots available for devices, each input device must have at least one active FD and it can be in the range that fd_set allows, which is 0..1023 on linux. So based on this being the original intention, due to the way the code is constructed and API calls made available, the file has been reworked to allow all these considerations at the same time. Now the design should be FD clean for FDs in the range 0..1023 instead of being artificially clamped to the first 20 FDs being valid input devices. Renaming of variable 'i' to 'fd' when it relates to an fd number, so all uses of FD_XXXX() should use fd, this helps clarify the different domain the number relates to. When 'i' is used it relates to the index into the txInputDevRec array. A large part of the concerns relate to trying to mix the two numbering domains interchangeably, just using a different name really clears up understanding to the reader. Other items that look like errors TxDelete1InputDevice() will shuffle-remove entries with no active FD, but it is iterating an array it is also modifying, so it looks like it would have incorrectly skipped the next item that should be inspected just after a shuffle-removal occurred. The various iterators that works from 0..TX_MAX_OPEN_FILES incorrectly limited the visibility of the routines to the first 20 FDs instead of the full FD_SETSIZE the TxAddInputDevice API call allows to be registered. Passing in TxAdd1InputDevice with an fd above 19 would have resulted in everything looking successful until select() was used, then the kernel would likely not sleep/wait because the input fd_set would look blank due to being clipped by nfds=TX_MAX_OPEN_FILES (instead of that plus 1). The use of TX_MAX_OPEN_FILES in select instead of TX_MAX_OPEN_FILES+1 for the 'nfds' field would have meant a device with fd=19 would not work as the design intended. The define definition has been removed from the module public header, I assume it was there for use by another module, but the incorrect select() usage has been corrected over there in a previous commit. So now the define can be maintained near the array it relates to. While the code might looks less 'efficient' as it is now sweeping all 1024 FDs when input devices during add/remove (hopefully there maybe some compiler auto-vectorization/tzcnt use there). The main event loop is slightly more 'efficient' (especially for the single device with input fd=0 case) as it will only check the 1 FD each input event loop iteration, not check all 20 FDs for data readyness everytime.
2025-02-24 09:49:07 +01:00
txInputDevice[txLastInputEntry].tx_fdmask = *fdmask;
txInputDevice[txLastInputEntry].tx_inputProc = inputProc;
txInputDevice[txLastInputEntry].tx_cdata = cdata;
txCommands.c txInputDevRec FD select() usage overhaul There are numerous concerns with the original code from a read through. The #define TX_MAX_OPEN_FILES 20 is badly named, the txCommand.c uses fd_set to hold active FD for each device, and makes no attempt to bounds check the 'fd' or 'fd_set' of any TxAdd1InputDevice() is in range. The real name of this variable should be TX_MAX_INPUT_DEVICES as it related to the fixed compile time slots available for devices, each input device must have at least one active FD and it can be in the range that fd_set allows, which is 0..1023 on linux. So based on this being the original intention, due to the way the code is constructed and API calls made available, the file has been reworked to allow all these considerations at the same time. Now the design should be FD clean for FDs in the range 0..1023 instead of being artificially clamped to the first 20 FDs being valid input devices. Renaming of variable 'i' to 'fd' when it relates to an fd number, so all uses of FD_XXXX() should use fd, this helps clarify the different domain the number relates to. When 'i' is used it relates to the index into the txInputDevRec array. A large part of the concerns relate to trying to mix the two numbering domains interchangeably, just using a different name really clears up understanding to the reader. Other items that look like errors TxDelete1InputDevice() will shuffle-remove entries with no active FD, but it is iterating an array it is also modifying, so it looks like it would have incorrectly skipped the next item that should be inspected just after a shuffle-removal occurred. The various iterators that works from 0..TX_MAX_OPEN_FILES incorrectly limited the visibility of the routines to the first 20 FDs instead of the full FD_SETSIZE the TxAddInputDevice API call allows to be registered. Passing in TxAdd1InputDevice with an fd above 19 would have resulted in everything looking successful until select() was used, then the kernel would likely not sleep/wait because the input fd_set would look blank due to being clipped by nfds=TX_MAX_OPEN_FILES (instead of that plus 1). The use of TX_MAX_OPEN_FILES in select instead of TX_MAX_OPEN_FILES+1 for the 'nfds' field would have meant a device with fd=19 would not work as the design intended. The define definition has been removed from the module public header, I assume it was there for use by another module, but the incorrect select() usage has been corrected over there in a previous commit. So now the define can be maintained near the array it relates to. While the code might looks less 'efficient' as it is now sweeping all 1024 FDs when input devices during add/remove (hopefully there maybe some compiler auto-vectorization/tzcnt use there). The main event loop is slightly more 'efficient' (especially for the single device with input fd=0 case) as it will only check the 1 FD each input event loop iteration, not check all 20 FDs for data readyness everytime.
2025-02-24 09:49:07 +01:00
txLastInputEntry++;
FD_OrSet(fdmask, &txInputDescriptors);
txCommands.c txInputDevRec FD select() usage overhaul There are numerous concerns with the original code from a read through. The #define TX_MAX_OPEN_FILES 20 is badly named, the txCommand.c uses fd_set to hold active FD for each device, and makes no attempt to bounds check the 'fd' or 'fd_set' of any TxAdd1InputDevice() is in range. The real name of this variable should be TX_MAX_INPUT_DEVICES as it related to the fixed compile time slots available for devices, each input device must have at least one active FD and it can be in the range that fd_set allows, which is 0..1023 on linux. So based on this being the original intention, due to the way the code is constructed and API calls made available, the file has been reworked to allow all these considerations at the same time. Now the design should be FD clean for FDs in the range 0..1023 instead of being artificially clamped to the first 20 FDs being valid input devices. Renaming of variable 'i' to 'fd' when it relates to an fd number, so all uses of FD_XXXX() should use fd, this helps clarify the different domain the number relates to. When 'i' is used it relates to the index into the txInputDevRec array. A large part of the concerns relate to trying to mix the two numbering domains interchangeably, just using a different name really clears up understanding to the reader. Other items that look like errors TxDelete1InputDevice() will shuffle-remove entries with no active FD, but it is iterating an array it is also modifying, so it looks like it would have incorrectly skipped the next item that should be inspected just after a shuffle-removal occurred. The various iterators that works from 0..TX_MAX_OPEN_FILES incorrectly limited the visibility of the routines to the first 20 FDs instead of the full FD_SETSIZE the TxAddInputDevice API call allows to be registered. Passing in TxAdd1InputDevice with an fd above 19 would have resulted in everything looking successful until select() was used, then the kernel would likely not sleep/wait because the input fd_set would look blank due to being clipped by nfds=TX_MAX_OPEN_FILES (instead of that plus 1). The use of TX_MAX_OPEN_FILES in select instead of TX_MAX_OPEN_FILES+1 for the 'nfds' field would have meant a device with fd=19 would not work as the design intended. The define definition has been removed from the module public header, I assume it was there for use by another module, but the incorrect select() usage has been corrected over there in a previous commit. So now the define can be maintained near the array it relates to. While the code might looks less 'efficient' as it is now sweeping all 1024 FDs when input devices during add/remove (hopefully there maybe some compiler auto-vectorization/tzcnt use there). The main event loop is slightly more 'efficient' (especially for the single device with input fd=0 case) as it will only check the 1 FD each input event loop iteration, not check all 20 FDs for data readyness everytime.
2025-02-24 09:49:07 +01:00
TxInputDescriptorsRecalc();
}
void
TxAdd1InputDevice(
int fd,
const cb_textio_input_t inputProc,
ClientData cdata)
{
fd_set fs;
ASSERT(fd >= 0 && fd < FD_SETSIZE, "fd>=0&&fd<FD_SETSIZE");
if (fd < 0 || fd >= FD_SETSIZE)
{
TxError("WARNING: TxAdd1InputDevice(fd=%d) called with fd out of range 0..%d\n", fd, FD_SETSIZE-1);
return; /* allowing things to continue is UB */
}
FD_ZERO(&fs);
FD_SET(fd, &fs);
TxAddInputDevice(&fs, inputProc, cdata);
}
/*
* ----------------------------------------------------------------------------
* TxDeleteInputDevice --
*
* Delete an input device from our tables.
*
* Results:
* None.
*
* Side effects:
* modifies internal tables.
* ----------------------------------------------------------------------------
*/
void
TxDeleteInputDevice(
const fd_set *fdmask) /* A mask of file descriptors that are
* no longer active.
*/
{
txCommands.c txInputDevRec FD select() usage overhaul There are numerous concerns with the original code from a read through. The #define TX_MAX_OPEN_FILES 20 is badly named, the txCommand.c uses fd_set to hold active FD for each device, and makes no attempt to bounds check the 'fd' or 'fd_set' of any TxAdd1InputDevice() is in range. The real name of this variable should be TX_MAX_INPUT_DEVICES as it related to the fixed compile time slots available for devices, each input device must have at least one active FD and it can be in the range that fd_set allows, which is 0..1023 on linux. So based on this being the original intention, due to the way the code is constructed and API calls made available, the file has been reworked to allow all these considerations at the same time. Now the design should be FD clean for FDs in the range 0..1023 instead of being artificially clamped to the first 20 FDs being valid input devices. Renaming of variable 'i' to 'fd' when it relates to an fd number, so all uses of FD_XXXX() should use fd, this helps clarify the different domain the number relates to. When 'i' is used it relates to the index into the txInputDevRec array. A large part of the concerns relate to trying to mix the two numbering domains interchangeably, just using a different name really clears up understanding to the reader. Other items that look like errors TxDelete1InputDevice() will shuffle-remove entries with no active FD, but it is iterating an array it is also modifying, so it looks like it would have incorrectly skipped the next item that should be inspected just after a shuffle-removal occurred. The various iterators that works from 0..TX_MAX_OPEN_FILES incorrectly limited the visibility of the routines to the first 20 FDs instead of the full FD_SETSIZE the TxAddInputDevice API call allows to be registered. Passing in TxAdd1InputDevice with an fd above 19 would have resulted in everything looking successful until select() was used, then the kernel would likely not sleep/wait because the input fd_set would look blank due to being clipped by nfds=TX_MAX_OPEN_FILES (instead of that plus 1). The use of TX_MAX_OPEN_FILES in select instead of TX_MAX_OPEN_FILES+1 for the 'nfds' field would have meant a device with fd=19 would not work as the design intended. The define definition has been removed from the module public header, I assume it was there for use by another module, but the incorrect select() usage has been corrected over there in a previous commit. So now the define can be maintained near the array it relates to. While the code might looks less 'efficient' as it is now sweeping all 1024 FDs when input devices during add/remove (hopefully there maybe some compiler auto-vectorization/tzcnt use there). The main event loop is slightly more 'efficient' (especially for the single device with input fd=0 case) as it will only check the 1 FD each input event loop iteration, not check all 20 FDs for data readyness everytime.
2025-02-24 09:49:07 +01:00
int fd;
for (fd = 0; fd < FD_SETSIZE; fd++)
if (FD_ISSET(fd, fdmask)) TxDelete1InputDevice(fd);
}
void
TxDelete1InputDevice(
int fd)
{
ASSERT(fd >= 0 && fd < FD_SETSIZE, "fd>=0&&fd<FD_SETSIZE");
if (fd < 0 || fd >= FD_SETSIZE)
{
TxError("WARNING: TxDelete1InputDevice(fd=%d) called with fd out of range 0..%d\n", fd, FD_SETSIZE-1);
return; /* allowing things to continue is UB */
}
txCommands.c txInputDevRec FD select() usage overhaul There are numerous concerns with the original code from a read through. The #define TX_MAX_OPEN_FILES 20 is badly named, the txCommand.c uses fd_set to hold active FD for each device, and makes no attempt to bounds check the 'fd' or 'fd_set' of any TxAdd1InputDevice() is in range. The real name of this variable should be TX_MAX_INPUT_DEVICES as it related to the fixed compile time slots available for devices, each input device must have at least one active FD and it can be in the range that fd_set allows, which is 0..1023 on linux. So based on this being the original intention, due to the way the code is constructed and API calls made available, the file has been reworked to allow all these considerations at the same time. Now the design should be FD clean for FDs in the range 0..1023 instead of being artificially clamped to the first 20 FDs being valid input devices. Renaming of variable 'i' to 'fd' when it relates to an fd number, so all uses of FD_XXXX() should use fd, this helps clarify the different domain the number relates to. When 'i' is used it relates to the index into the txInputDevRec array. A large part of the concerns relate to trying to mix the two numbering domains interchangeably, just using a different name really clears up understanding to the reader. Other items that look like errors TxDelete1InputDevice() will shuffle-remove entries with no active FD, but it is iterating an array it is also modifying, so it looks like it would have incorrectly skipped the next item that should be inspected just after a shuffle-removal occurred. The various iterators that works from 0..TX_MAX_OPEN_FILES incorrectly limited the visibility of the routines to the first 20 FDs instead of the full FD_SETSIZE the TxAddInputDevice API call allows to be registered. Passing in TxAdd1InputDevice with an fd above 19 would have resulted in everything looking successful until select() was used, then the kernel would likely not sleep/wait because the input fd_set would look blank due to being clipped by nfds=TX_MAX_OPEN_FILES (instead of that plus 1). The use of TX_MAX_OPEN_FILES in select instead of TX_MAX_OPEN_FILES+1 for the 'nfds' field would have meant a device with fd=19 would not work as the design intended. The define definition has been removed from the module public header, I assume it was there for use by another module, but the incorrect select() usage has been corrected over there in a previous commit. So now the define can be maintained near the array it relates to. While the code might looks less 'efficient' as it is now sweeping all 1024 FDs when input devices during add/remove (hopefully there maybe some compiler auto-vectorization/tzcnt use there). The main event loop is slightly more 'efficient' (especially for the single device with input fd=0 case) as it will only check the 1 FD each input event loop iteration, not check all 20 FDs for data readyness everytime.
2025-02-24 09:49:07 +01:00
restart:
{
txCommands.c txInputDevRec FD select() usage overhaul There are numerous concerns with the original code from a read through. The #define TX_MAX_OPEN_FILES 20 is badly named, the txCommand.c uses fd_set to hold active FD for each device, and makes no attempt to bounds check the 'fd' or 'fd_set' of any TxAdd1InputDevice() is in range. The real name of this variable should be TX_MAX_INPUT_DEVICES as it related to the fixed compile time slots available for devices, each input device must have at least one active FD and it can be in the range that fd_set allows, which is 0..1023 on linux. So based on this being the original intention, due to the way the code is constructed and API calls made available, the file has been reworked to allow all these considerations at the same time. Now the design should be FD clean for FDs in the range 0..1023 instead of being artificially clamped to the first 20 FDs being valid input devices. Renaming of variable 'i' to 'fd' when it relates to an fd number, so all uses of FD_XXXX() should use fd, this helps clarify the different domain the number relates to. When 'i' is used it relates to the index into the txInputDevRec array. A large part of the concerns relate to trying to mix the two numbering domains interchangeably, just using a different name really clears up understanding to the reader. Other items that look like errors TxDelete1InputDevice() will shuffle-remove entries with no active FD, but it is iterating an array it is also modifying, so it looks like it would have incorrectly skipped the next item that should be inspected just after a shuffle-removal occurred. The various iterators that works from 0..TX_MAX_OPEN_FILES incorrectly limited the visibility of the routines to the first 20 FDs instead of the full FD_SETSIZE the TxAddInputDevice API call allows to be registered. Passing in TxAdd1InputDevice with an fd above 19 would have resulted in everything looking successful until select() was used, then the kernel would likely not sleep/wait because the input fd_set would look blank due to being clipped by nfds=TX_MAX_OPEN_FILES (instead of that plus 1). The use of TX_MAX_OPEN_FILES in select instead of TX_MAX_OPEN_FILES+1 for the 'nfds' field would have meant a device with fd=19 would not work as the design intended. The define definition has been removed from the module public header, I assume it was there for use by another module, but the incorrect select() usage has been corrected over there in a previous commit. So now the define can be maintained near the array it relates to. While the code might looks less 'efficient' as it is now sweeping all 1024 FDs when input devices during add/remove (hopefully there maybe some compiler auto-vectorization/tzcnt use there). The main event loop is slightly more 'efficient' (especially for the single device with input fd=0 case) as it will only check the 1 FD each input event loop iteration, not check all 20 FDs for data readyness everytime.
2025-02-24 09:49:07 +01:00
int i;
for (i = 0; i < txLastInputEntry; i++)
{
txCommands.c txInputDevRec FD select() usage overhaul There are numerous concerns with the original code from a read through. The #define TX_MAX_OPEN_FILES 20 is badly named, the txCommand.c uses fd_set to hold active FD for each device, and makes no attempt to bounds check the 'fd' or 'fd_set' of any TxAdd1InputDevice() is in range. The real name of this variable should be TX_MAX_INPUT_DEVICES as it related to the fixed compile time slots available for devices, each input device must have at least one active FD and it can be in the range that fd_set allows, which is 0..1023 on linux. So based on this being the original intention, due to the way the code is constructed and API calls made available, the file has been reworked to allow all these considerations at the same time. Now the design should be FD clean for FDs in the range 0..1023 instead of being artificially clamped to the first 20 FDs being valid input devices. Renaming of variable 'i' to 'fd' when it relates to an fd number, so all uses of FD_XXXX() should use fd, this helps clarify the different domain the number relates to. When 'i' is used it relates to the index into the txInputDevRec array. A large part of the concerns relate to trying to mix the two numbering domains interchangeably, just using a different name really clears up understanding to the reader. Other items that look like errors TxDelete1InputDevice() will shuffle-remove entries with no active FD, but it is iterating an array it is also modifying, so it looks like it would have incorrectly skipped the next item that should be inspected just after a shuffle-removal occurred. The various iterators that works from 0..TX_MAX_OPEN_FILES incorrectly limited the visibility of the routines to the first 20 FDs instead of the full FD_SETSIZE the TxAddInputDevice API call allows to be registered. Passing in TxAdd1InputDevice with an fd above 19 would have resulted in everything looking successful until select() was used, then the kernel would likely not sleep/wait because the input fd_set would look blank due to being clipped by nfds=TX_MAX_OPEN_FILES (instead of that plus 1). The use of TX_MAX_OPEN_FILES in select instead of TX_MAX_OPEN_FILES+1 for the 'nfds' field would have meant a device with fd=19 would not work as the design intended. The define definition has been removed from the module public header, I assume it was there for use by another module, but the incorrect select() usage has been corrected over there in a previous commit. So now the define can be maintained near the array it relates to. While the code might looks less 'efficient' as it is now sweeping all 1024 FDs when input devices during add/remove (hopefully there maybe some compiler auto-vectorization/tzcnt use there). The main event loop is slightly more 'efficient' (especially for the single device with input fd=0 case) as it will only check the 1 FD each input event loop iteration, not check all 20 FDs for data readyness everytime.
2025-02-24 09:49:07 +01:00
FD_CLR(fd, &txInputDevice[i].tx_fdmask);
if (FD_IsZero(&txInputDevice[i].tx_fdmask))
{
int j;
for (j = i+1; j < txLastInputEntry; j++)
txInputDevice[j-1] = txInputDevice[j];
txLastInputEntry--;
/* we shuffled entries down one, so txInputDevice[i] now
* looks at potential next one to inspect, but we're about
* to i++ (if we continue) and that will incorrectly skip
* inspection of it.
* lets just start over
*/
goto restart;
}
}
}
FD_CLR(fd, &txInputDescriptors);
txCommands.c txInputDevRec FD select() usage overhaul There are numerous concerns with the original code from a read through. The #define TX_MAX_OPEN_FILES 20 is badly named, the txCommand.c uses fd_set to hold active FD for each device, and makes no attempt to bounds check the 'fd' or 'fd_set' of any TxAdd1InputDevice() is in range. The real name of this variable should be TX_MAX_INPUT_DEVICES as it related to the fixed compile time slots available for devices, each input device must have at least one active FD and it can be in the range that fd_set allows, which is 0..1023 on linux. So based on this being the original intention, due to the way the code is constructed and API calls made available, the file has been reworked to allow all these considerations at the same time. Now the design should be FD clean for FDs in the range 0..1023 instead of being artificially clamped to the first 20 FDs being valid input devices. Renaming of variable 'i' to 'fd' when it relates to an fd number, so all uses of FD_XXXX() should use fd, this helps clarify the different domain the number relates to. When 'i' is used it relates to the index into the txInputDevRec array. A large part of the concerns relate to trying to mix the two numbering domains interchangeably, just using a different name really clears up understanding to the reader. Other items that look like errors TxDelete1InputDevice() will shuffle-remove entries with no active FD, but it is iterating an array it is also modifying, so it looks like it would have incorrectly skipped the next item that should be inspected just after a shuffle-removal occurred. The various iterators that works from 0..TX_MAX_OPEN_FILES incorrectly limited the visibility of the routines to the first 20 FDs instead of the full FD_SETSIZE the TxAddInputDevice API call allows to be registered. Passing in TxAdd1InputDevice with an fd above 19 would have resulted in everything looking successful until select() was used, then the kernel would likely not sleep/wait because the input fd_set would look blank due to being clipped by nfds=TX_MAX_OPEN_FILES (instead of that plus 1). The use of TX_MAX_OPEN_FILES in select instead of TX_MAX_OPEN_FILES+1 for the 'nfds' field would have meant a device with fd=19 would not work as the design intended. The define definition has been removed from the module public header, I assume it was there for use by another module, but the incorrect select() usage has been corrected over there in a previous commit. So now the define can be maintained near the array it relates to. While the code might looks less 'efficient' as it is now sweeping all 1024 FDs when input devices during add/remove (hopefully there maybe some compiler auto-vectorization/tzcnt use there). The main event loop is slightly more 'efficient' (especially for the single device with input fd=0 case) as it will only check the 1 FD each input event loop iteration, not check all 20 FDs for data readyness everytime.
2025-02-24 09:49:07 +01:00
TxInputDescriptorsRecalc();
}
/*
* ----------------------------------------------------------------------------
* TxSetPoint --
*
* Set the point and window that will be used for the next command.
*
* Results:
* None.
*
* Side effects:
* Changes global set point variables.
* ----------------------------------------------------------------------------
*/
void
TxSetPoint(
int x,
int y,
int wid)
{
txHaveCurrentPoint = TRUE;
txCurrentPoint.p_x = x;
txCurrentPoint.p_y = y;
txCurrentWindowID = wid;
}
/*
* ----------------------------------------------------------------------------
* TxGetPoint --
*
* Return the window ID and set the point (if non-NULL) set for the
* next command.
*
* Results:
* integer ID of the current window, or -1 if txHaveCurrentPoint is
* TX_POINT_CLEAR
*
* Side effects:
* Current point goes into
*
* ----------------------------------------------------------------------------
*/
int
TxGetPoint(
Point *tx_p)
{
if (txHaveCurrentPoint)
{
if (tx_p != (Point *)NULL)
*tx_p = txCurrentPoint;
return txCurrentWindowID;
}
return -1;
}
/*
* ----------------------------------------------------------------------------
* TxClearPoint --
*
* Clear the point that will be used for the next command, forcing it to
* be read from the mouse or button push.
*
* Results:
* None.
*
* Side effects:
* Nullifies the effect of any previous TxSetPoint() call.
* ----------------------------------------------------------------------------
*/
void
TxClearPoint(void)
{
txHaveCurrentPoint = FALSE;
}
static FILE *txLogFile = NULL;
unsigned char txLogFlags;
/*
* ----------------------------------------------------------------------------
* TxLogStart --
*
* Log all further commands to the given file name. If the file is NULL,
* turn off logging.
*
* Results:
* None.
*
* Side effects:
* File IO.
* ----------------------------------------------------------------------------
*/
void
TxLogStart(
2025-01-31 18:15:29 +01:00
const char *fileName,
MagWindow *mw) /* Window commands are logged from */
{
if (txLogFile != NULL)
{
TxError("There is already a log file open!\n");
return;
}
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(void)
{
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(void)
{
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(void)
{
if (txLogFile == NULL)
return;
txLogFlags |= TX_LOG_SUSPEND;
}
/*
* ----------------------------------------------------------------------------
* TxLogResume --
*
* Resume command logging
*
* Results:
* None.
*
* Side effects:
* File IO.
* ----------------------------------------------------------------------------
*/
void
TxLogResume(void)
{
if (txLogFile == NULL)
return;
txLogFlags &= ~TX_LOG_SUSPEND;
}
/*
* ----------------------------------------------------------------------------
* txLogCommand --
*
* Log a command in the log file.
*
* Results:
* None.
*
* Side effects:
* None.
* ----------------------------------------------------------------------------
*/
void
txLogCommand(
TxCommand *cmd)
{
2025-01-31 18:18:27 +01:00
static const char *txButTable[] =
{
"left",
"middle",
"right",
0
};
2025-01-31 18:18:27 +01:00
static const char *txActTable[] =
{
"down",
"up",
0
};
#ifdef MAGIC_WRAPPER
2025-01-31 18:18:27 +01:00
const char *pfix = "";
#else
2025-01-31 18:18:27 +01:00
const char *pfix = ":";
#endif
/* Do not do anything if there is no log file */
if (txLogFile == (FILE *) NULL) return;
/* Do not write anything if the log file is suspended */
if (txLogFlags & TX_LOG_SUSPEND) return;
if (cmd->tx_argc > 0)
{
int i;
char *postns;
/* Do not output "logcommand" commands to the log file.
* Do not output "*bypass" commands to the log file.
* Do not output "setpoint" commands to the log file.
* Do not output "wire show" commands to the log file (excessive output).
*/
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;
else if (!strcmp(postns, "setpoint"))
return;
else if (!strcmp(postns, "wire") && !strcmp(cmd->tx_argv[1], "show"))
return;
fprintf(txLogFile, "%s%s", pfix, cmd->tx_argv[0]);
for (i = 1; i < cmd->tx_argc; i++)
{
bool needQuotes = (strchr(cmd->tx_argv[i], ' ') == NULL) ? FALSE : TRUE;
fprintf(txLogFile, " ");
if (needQuotes) fprintf(txLogFile, "\"");
fprintf(txLogFile, "%s", cmd->tx_argv[i]);
if (needQuotes) fprintf(txLogFile, "\"");
}
fprintf(txLogFile, "\n");
}
else if (cmd->tx_button == TX_CHARACTER) {
/* its a no-op command */
return;
}
else {
int but, act;
switch(cmd->tx_button) {
case TX_LEFT_BUTTON: { but = 0; break; };
case TX_MIDDLE_BUTTON: { but = 1; break; };
case TX_RIGHT_BUTTON: { but = 2; break; };
default: {ASSERT(FALSE, "txLogCommand"); but = -1; break; };
}
switch(cmd->tx_buttonAction) {
case TX_BUTTON_DOWN: { act = 0; break; };
case TX_BUTTON_UP: { act = 1; break; };
default: {ASSERT(FALSE, "txLogCommand"); act = -1; break; };
}
if (but >= 0 && act >= 0)
fprintf(txLogFile, "%spushbutton %s %s\n",
pfix, txButTable[but], txActTable[act]);
}
if (txLogFlags & TX_LOG_UPDATE)
fprintf(txLogFile, "%supdatedisplay\n", pfix);
fflush(txLogFile);
}
/*
* ----------------------------------------------------------------------------
*
* TxGetInputEvent --
*
* Get some events and put them into the event queue. If returnOnSigWinch
* is on, we will return early if some event (such as a SigWinch) requires
* immediate attention. In that case, the input event queue may be empty
* even if block is TRUE.
*
* Results:
* "TRUE" if it got some input.
*
* Side Effects:
* Some IO may be done, and things may appear in the event queue.
*
* ----------------------------------------------------------------------------
*/
bool
TxGetInputEvent(
bool block, /* If TRUE, we will wait for an event. Otherwise, we
* just poll.
*/
bool returnOnSigWinch)
/* If we get a Sig-Winch signal, should we abondon
* our quest to read an input event and return
* immediately instead?
*/
{
struct timeval *waitTime;
fd_set inputs;
int numReady;
bool gotSome;
int i, fd, lastNum;
ASSERT(!FD_IsZero(&txInputDescriptors), "TxGetInputEvent");
if (block)
waitTime = NULL;
else
waitTime = &txZeroTime;
gotSome = FALSE;
do {
do
{
if (returnOnSigWinch && SigGotSigWinch) return gotSome;
inputs = txInputDescriptors;
txCommands.c txInputDevRec FD select() usage overhaul There are numerous concerns with the original code from a read through. The #define TX_MAX_OPEN_FILES 20 is badly named, the txCommand.c uses fd_set to hold active FD for each device, and makes no attempt to bounds check the 'fd' or 'fd_set' of any TxAdd1InputDevice() is in range. The real name of this variable should be TX_MAX_INPUT_DEVICES as it related to the fixed compile time slots available for devices, each input device must have at least one active FD and it can be in the range that fd_set allows, which is 0..1023 on linux. So based on this being the original intention, due to the way the code is constructed and API calls made available, the file has been reworked to allow all these considerations at the same time. Now the design should be FD clean for FDs in the range 0..1023 instead of being artificially clamped to the first 20 FDs being valid input devices. Renaming of variable 'i' to 'fd' when it relates to an fd number, so all uses of FD_XXXX() should use fd, this helps clarify the different domain the number relates to. When 'i' is used it relates to the index into the txInputDevRec array. A large part of the concerns relate to trying to mix the two numbering domains interchangeably, just using a different name really clears up understanding to the reader. Other items that look like errors TxDelete1InputDevice() will shuffle-remove entries with no active FD, but it is iterating an array it is also modifying, so it looks like it would have incorrectly skipped the next item that should be inspected just after a shuffle-removal occurred. The various iterators that works from 0..TX_MAX_OPEN_FILES incorrectly limited the visibility of the routines to the first 20 FDs instead of the full FD_SETSIZE the TxAddInputDevice API call allows to be registered. Passing in TxAdd1InputDevice with an fd above 19 would have resulted in everything looking successful until select() was used, then the kernel would likely not sleep/wait because the input fd_set would look blank due to being clipped by nfds=TX_MAX_OPEN_FILES (instead of that plus 1). The use of TX_MAX_OPEN_FILES in select instead of TX_MAX_OPEN_FILES+1 for the 'nfds' field would have meant a device with fd=19 would not work as the design intended. The define definition has been removed from the module public header, I assume it was there for use by another module, but the incorrect select() usage has been corrected over there in a previous commit. So now the define can be maintained near the array it relates to. While the code might looks less 'efficient' as it is now sweeping all 1024 FDs when input devices during add/remove (hopefully there maybe some compiler auto-vectorization/tzcnt use there). The main event loop is slightly more 'efficient' (especially for the single device with input fd=0 case) as it will only check the 1 FD each input event loop iteration, not check all 20 FDs for data readyness everytime.
2025-02-24 09:49:07 +01:00
numReady = select(txInputDescriptors_nfds + 1, &inputs, (fd_set *)NULL,
(fd_set *)NULL, waitTime);
if (numReady <= 0) FD_ZERO(&inputs); /* no fd is ready */
} while ((numReady <= 0) && (errno == EINTR));
if ((numReady < 0) && (errno != EINTR))
{
perror("magic");
}
/* 0..1023 using numReady==0 to terminate early */
for (fd = 0; numReady && fd <= txInputDescriptors_nfds; fd++)
{
if (!FD_ISSET(fd, &inputs))
continue; /* this fd is was not monitored or is not ready */
/* find the input device receiver entry */
for (i = 0; i < txLastInputEntry; i++)
{
if (!FD_ISSET(fd, &txInputDevice[i].tx_fdmask))
continue;
/* This device has data on its file descriptor, call
* it so that it can add events to the input queue.
*/
lastNum = txNumInputEvents;
(*txInputDevice[i].tx_inputProc)(fd, txInputDevice[i].tx_cdata);
/* Did this driver choose to add an event? */
if (txNumInputEvents != lastNum) gotSome = TRUE;
numReady--;
/* original code would FD_CLR() which would effectively break here
* so this only allows the first found receiver to receive the data
*/
break;
}
}
/*
* At this point we have handled all the bits in 'inputs' -- almost.
* It is possible for an input handler to remove or add other handlers
* via calls to TxDeleteDevice or TxAddDevice. Therefore, there may be
* bits in inputs that haven't been turned off.
*/
} while (block && !gotSome);
return gotSome;
}
#ifndef MAGIC_WRAPPER
/*
* ----------------------------------------------------------------------------
*
* TxParseString_internal --
*
* Parse a string into commands, and add them to the rear of a queue.
* The commands in the queue should eventually be freed by the caller
* via TxFreeCommand().
*
* Results:
* None.
*
* Side Effects:
* None.
*
* Module internal API. See TxParseString() for public version.
*
* ----------------------------------------------------------------------------
*/
static void
TxParseString_internal(
2025-01-31 18:15:29 +01:00
const char *str, /* The string to be parsed. */
DQueue *q, /* Add to the tail of this queue. */
TxInputEvent *event) /* An event to supply the point, window ID,
* etc. . If NULL, we will use the last
* event processed.
*/
{
char *remainder;
TxCommand *cmd;
if (event == NULL) event = &txLastEvent;
remainder = str;
while (remainder != NULL) {
/* Parse a single command. */
cmd = TxNewCommand();
strncpy(cmd->tx_argstring, remainder, TX_MAX_CMDLEN);
cmd->tx_argstring[TX_MAX_CMDLEN - 1] = '\0';
if (ParsSplit(cmd->tx_argstring, TX_MAXARGS,
&(cmd->tx_argc), cmd->tx_argv, &remainder) )
{
if (event == NULL) event = &txLastEvent;
cmd->tx_button = TX_CHARACTER;
cmd->tx_p = event->txe_p;
cmd->tx_wid = event->txe_wid;
DQPushRear(q, (ClientData) cmd);
}
else
{
TxError("Unable to completely parse command line '%s'\n",
str);
TxFreeCommand(cmd);
return;
}
}
}
/*
* ----------------------------------------------------------------------------
*
* TxParseString --
*
* Parse a string into commands, and add them to the rear of a queue.
* The commands in the queue should eventually be freed by the caller
* via TxFreeCommand().
*
* Results:
* None.
*
* Side Effects:
* None.
*
* Public API. See also TxParseString_internal().
*
* ----------------------------------------------------------------------------
*/
void
TxParseString(
const char *str) /* The string to be parsed. */
{
TxParseString_internal(str, NULL, NULL);
}
#else
/* tclmagic.c defines TxParseString() so we need a shim to other way
*
* FIXME it feels like there is some design error here, only this
* file cares about DQueue/TxInputEvent arguments but they are
* passed but not used by tclmagic.c TxParseString()
*
* It must be that !MAGIC_WRAPPER needs the DQueue/TxInputEvent for
* the other builds, such as WASM.
*
*/
static void
TxParseString_internal(
const char *str, /* The string to be parsed. */
DQueue *q,
TxInputEvent *event)
{
TxParseString(str);
}
#endif /* !MAGIC_WRAPPER */
/*
* ----------------------------------------------------------------------------
* txGetInteractiveCommand:
*
* Get one or more commands from one of the interactive input devices,
* and put it into the input queue.
*
* Results:
* None.
*
* Side effects:
* None.
* ----------------------------------------------------------------------------
*/
void
txGetInteractiveCommand(
bool block, /* If TRUE, then wait until we have a command.
* If FALSE, then get one only if input is
* available.
*/
DQueue *queue) /* Queue to receive the new commands. */
{
static char inputLine[TX_MAX_CMDLEN] = "";
TxInputEvent *event;
TxCommand *cmd;
int ch;
/* Get one new event and return (except for text commands, then collect
* an entire line.
*/
if (DQIsEmpty(&txInputEvents)) TxGetInputEvent(block, TRUE);
if (DQIsEmpty(&txInputEvents)) return;
event = (TxInputEvent *) DQPopFront(&txInputEvents);
txLastEvent = *event;
if (TxDebug) TxPrintEvent(event);
ASSERT(event != NULL, "txGetInteractiveCommand");
if (event->txe_button == TX_EOF) {
/* We have an end of file. Put it at the end of our command queue,
* but only if we are waiting for a command. We don't want to
* put in EOF's if we are just polling.
*/
if (block)
{
cmd = TxNewCommand();
cmd->tx_button = TX_EOF;
cmd->tx_p = event->txe_p;
cmd->tx_wid = event->txe_wid;
cmd->tx_argc = 0;
cmd->tx_argv[0] = NULL;
DQPushRear(queue, (ClientData) cmd);
}
TxFreeEvent(event);
} else if ((TxCurButtons != 0) && (event->txe_button == TX_CHARACTER)) {
/* We have a keyboard character, but buttons are still down.
* Push fake button up commands into the queue in front of this
* event, so that all buttons will be up by the time that we get
* to the character event. This is needed to avoid strange things
* when the user pushes a buttons down on a Sun 160 (or other device
* with a non-Magic window package) and then releases it outside of
* the Magic windows.
*/
TxInputEvent *newEvent;
int ourbuts;
ourbuts = TxCurButtons;
DQPushFront(&txInputEvents, event);
while (ourbuts != 0) {
newEvent = TxNewEvent();
newEvent->txe_p = event->txe_p;
newEvent->txe_wid = event->txe_wid;
/* Get rightmost '1' bit of ourbuts. */
newEvent->txe_button = LAST_BIT_OF(ourbuts);
newEvent->txe_buttonAction = TX_BUTTON_UP;
newEvent->txe_ch = 0;
ourbuts &= ~newEvent->txe_button;
DQPushFront(&txInputEvents, newEvent);
}
/* Now, go around the loop and process these things just like
* the user had typed them.
*/
} else if (event->txe_button == TX_CHARACTER) {
/* We have a text command, go into text collection mode */
/* need to check for long command or macro, then assemble line */
ch = event->txe_ch;
TxFreeEvent(event);
if ((ch == (int)TX_LONG_CMD) || (ch == (int)TX_LONG_CMD2))
{
(void) TxGetLinePrompt(inputLine, TX_MAX_CMDLEN, TX_CMD_PROMPT);
if (inputLine[0] != '\0') MacroDefine(DBWclientID, (int)'.',
inputLine, NULL, FALSE);
TxParseString_internal(inputLine, queue, (TxInputEvent* ) NULL);
}
else
{
bool iMacro; /* Interactive macro */
char *macroDef;
macroDef = MacroRetrieve(DBWclientID, ch, &iMacro);
if (macroDef == NULL)
{
/* Cases macroDef may be NULL: */
/* 1) Return 2) Ctrl-C 3) Invalid macro */
if (ch == (int)'\n')
{
if (TxInteractive) TxPrintf("%c\n", TX_PROMPT);
}
else
{
/* not a valid command */
char *vis = MacroName(ch);
TxError("Unknown macro or short command: '%s'\n", vis);
freeMagic(vis);
}
}
else
{
if (iMacro)
{
(void) TxGetLineWPrompt(inputLine,
TX_MAX_CMDLEN, TX_CMD_PROMPT, macroDef);
if (inputLine[0] != '\0') MacroDefine(DBWclientID, (int)'.',
inputLine, NULL, FALSE);
TxParseString_internal(inputLine, queue, (TxInputEvent *) NULL);
}
else
{
TxParseString_internal(macroDef, queue, (TxInputEvent *) NULL);
}
freeMagic(macroDef);
}
}
} else if ((event->txe_button & TX_LEFT_BUTTON) ||
(event->txe_button & TX_MIDDLE_BUTTON) ||
(event->txe_button & TX_RIGHT_BUTTON)) {
/* We have a button command, but ignore double ups & downs. */
int oldButtons;
oldButtons = TxCurButtons;
if (event->txe_buttonAction == TX_BUTTON_UP)
TxCurButtons &= ~(event->txe_button);
else
TxCurButtons |= event->txe_button;
if (oldButtons == TxCurButtons) {
TxFreeEvent(event);
} else {
/* We have a valid button command. */
cmd = TxNewCommand();
cmd->tx_button = event->txe_button;
cmd->tx_buttonAction = event->txe_buttonAction;
cmd->tx_p = event->txe_p;
cmd->tx_wid = event->txe_wid;
cmd->tx_argc = 0;
cmd->tx_argv[0] = NULL;
DQPushRear(queue, (ClientData) cmd);
TxFreeEvent(event);
}
} else { /* TX_BYPASS */
/* Ignore this event */
TxFreeEvent(event);
}
}
/*
* ----------------------------------------------------------------------------
* txGetFileCommand --
*
* Get a line from a file and stick the commands into the input queue.
*
* Results:
* None.
*
* Side effects:
* Reads the file and puts a command into the queue.
* ----------------------------------------------------------------------------
*/
void
txGetFileCommand(
FILE *f, /* File to read. */
DQueue *queue) /* Queue to receive the new commands. */
{
char inputLine[TX_MAX_CMDLEN];
char *linep;
char *current;
int spaceleft;
/* Generate a line by patching together successive lines ending
* in '\'.
*/
do {
current = inputLine;
spaceleft = TX_MAX_CMDLEN - 1;
while (TRUE)
{
if (fgets(current, spaceleft, f) == NULL)
return;
while (*current != 0)
{
current++;
spaceleft--;
}
if ((*(current-1) != '\n') || (*(current-2) != '\\')) goto gotline;
current--;
spaceleft++;
}
gotline: *current = 0;
/* If the line is empty, or contains a hash mark as
* the first non-blank character, then skip over it.
*/
current = inputLine;
while (isspace(*current)) current++;
} while ((*current == 0) || (*current == '#'));
linep = inputLine;
#ifdef MAGIC_WRAPPER
/* Don't muck with Tcl's interpretation of semicolon as a */
/* comment character. Watch for single-colon entries for */
/* backwards-compatibility with the old format, but watch */
/* for the double-colon, which is a Tcl namespace marker. */
if ((inputLine[0] == ':') && (inputLine[1] != ':')) linep++;
#else
if ((inputLine[0] == ':') || (inputLine[1] == ';')) linep++;
#endif
TxParseString_internal(linep, queue, (TxInputEvent *) NULL);
}
/*
* ----------------------------------------------------------------------------
* TxRebuildCommand:
*
* Rebuild the arguments of a TxCommand structure. This assumes
* that a routine has rewritten the tx_argstring record with a
* modified command. Tokenize the tx_argstring and update the
* tx_argc count and tx_argv pointers.
*
* The purpose of this routine is to allow some command callbacks
* to change the command from one implying the use of pointer
* coordinates to an equivalent command that uses database units,
* for the purpose of logging the command.
*
* ----------------------------------------------------------------------------
*/
void
TxRebuildCommand(
TxCommand *cmd)
{
char *cptr, *tptr, c;
cmd->tx_argc = 0;
tptr = cptr = cmd->tx_argstring;
do
{
c = *cptr;
if ((c == ' ') || (c == '\0'))
{
cmd->tx_argv[cmd->tx_argc] = tptr;
cmd->tx_argc++;
*cptr = '\0';
tptr = cptr + 1;
}
cptr++;
}
while (c != '\0');
}
#ifdef MAGIC_WRAPPER
/*
* ----------------------------------------------------------------------------
* TxTclDispatch:
*
* Command dispatcher which relies on the Tcl interpreter for most
* of the work. Replaces TxDispatch except for file input during
* startup.
*
* ----------------------------------------------------------------------------
*/
int
TxTclDispatch(
ClientData clientData,
int argc,
char *argv[],
bool quiet)
{
int result;
int n, asize;
TxCommand *tclcmd;
unsigned char lastdrc;
if (argc > TX_MAXARGS)
{
TxError("Error: number of command arguments exceeds %d!\n", TX_MAXARGS);
return -1;
}
SigIOReady = FALSE;
if (SigInterruptOnSigIO >= 0) SigInterruptOnSigIO = 1;
SigInterruptPending = FALSE;
tclcmd = TxNewCommand();
tclcmd->tx_argc = argc;
asize = 0;
for (n = 0; n < argc; n++)
{
if (asize + strlen(argv[n]) >= TX_MAX_CMDLEN)
{
TxError("Error: command length exceeds %d characters!\n", TX_MAX_CMDLEN);
TxFreeCommand(tclcmd);
return -1;
}
strcpy(&tclcmd->tx_argstring[asize], argv[n]);
tclcmd->tx_argv[n] = &tclcmd->tx_argstring[asize];
asize += (1 + strlen(argv[n]));
}
tclcmd->tx_p = txCurrentPoint;
if (txHaveCurrentPoint)
tclcmd->tx_wid = txCurrentWindowID;
else
tclcmd->tx_wid = WIND_UNKNOWN_WINDOW;
/* Prevent DRCContinuous from running in the middle of a command */
/* (Note that Tcl_CancelIdleCall() does *not* work. . .) */
lastdrc = DRCBackGround;
if (DRCBackGround != DRC_SET_OFF) DRCBackGround = DRC_NOT_SET;
result = WindSendCommand((MagWindow *)clientData, tclcmd, quiet);
if (txLogFile != NULL) txLogCommand(tclcmd);
TxFreeCommand(tclcmd);
// Don't update the command number on bypass commands, or else
// the selection mechanism gets broken by the background DRC.
if (argc > 0 && strcmp(argv[0], "*bypass")) TxCommandNumber++;
if (SigInterruptPending)
TxPrintf("[Interrupted]\n");
if (result == 0) WindUpdate();
SigInterruptPending = FALSE;
if (SigInterruptOnSigIO >= 0) SigInterruptOnSigIO = 0;
SigIOReady = FALSE;
/* Force a break of DRCContinuous() so we don't run DRC on an */
/* invalid database. */
/* Avoid breaking on "*bypass" commands. Technically, one could */
/* set up a command to *bypass that alters the database and crashes */
/* magic. This is a total hack. We should flag an invalid */
/* database instead. */
if (DRCBackGround == DRC_NOT_SET) DRCBackGround = lastdrc;
if (argc > 0 && strcmp(argv[0], "*bypass") && strcmp(argv[0], "windownames"))
DRCBreak();
/* Reinstate the idle call */
if (result == 0) Tcl_DoWhenIdle(DRCContinuous, (ClientData)NULL);
return result;
}
#else /* !MAGIC_WRAPPER */
/*
* ----------------------------------------------------------------------------
* TxDispatch:
*
* Read input from the interactive devices (keyboard or mouse) or from
* a file. Send a command off to WindSendCommand for processing, and
* then repeat.
*
* Results:
* None.
*
* Side effects:
* Commands are read and executed, one per cycle of this procedure's loop.
* ----------------------------------------------------------------------------
*/
void
TxDispatch(
FILE *f) /* Read commands from this file instead of
* from the mouse or keyboard.
*/
{
bool inFile;
DQueue inputCommands; /* A queue of commands from our input
* sources.
*/
DQInit(&inputCommands, 4);
inFile = (f != (FILE *) NULL);
while (TRUE)
{
if (!inFile)
{
/* Update the screen info of the window package and all of
* its clients. If there's already input waiting, skip this...
* it can get done at the end, after all pending commands have
* been processed.
*/
if (SigInterruptOnSigIO >= 0) SigInterruptOnSigIO = 0;
SigInterruptPending = FALSE;
txGetInteractiveCommand(FALSE, &inputCommands);
if (DQIsEmpty(&inputCommands))
{
WindUpdate();
if (SigInterruptPending)
TxPrintf("[Redisplay Interrupted]\n");
txGetInteractiveCommand(FALSE, &inputCommands);
}
/* Give the DRC a chance to work. */
if (!SigGotSigWinch && DQIsEmpty(&inputCommands)) {
if (DRCHasWork) {
TxSetPrompt(']');
TxPrompt();
(void) GrEnableTablet();
GrFlush();
/* Call background DRC.
* It will return if it is not enabled, there is no work to
* be done, or it learns that the user has entered a
* command.
*/
SigIOReady = FALSE;
if (SigInterruptOnSigIO >= 0) SigInterruptOnSigIO = 1;
SigInterruptPending = FALSE;
DRCContinuous();
TxUnPrompt();
SigIOReady = FALSE;
if (SigInterruptOnSigIO >= 0) SigInterruptOnSigIO = 0;
SigInterruptPending = FALSE;
(void) GrDisableTablet();
WindUpdate();
if (SigInterruptPending)
TxPrintf("[DRC Redisplay Interrupted]\n");
}
TxSetPrompt(TX_PROMPT);
TxPrompt();
(void) GrEnableTablet();
GrFlush();
txGetInteractiveCommand(TRUE, &inputCommands);
TxUnPrompt();
(void) GrDisableTablet();
GrFlush();
SigInterruptPending = FALSE;
SigIOReady = FALSE;
}
}
else
{
if (feof(f)) goto done;
if (SigInterruptPending && inFile)
{
TxPrintf("[Read-in of command file aborted]\n");
goto done;
}
if (!SigGotSigWinch) txGetFileCommand(f, &inputCommands);
}
if (SigGotSigWinch) {
/* WindUpdate will take care of SigWinch and set it to FALSE */
WindUpdate();
}
/* Now send off the commands in the queue. */
while (!DQIsEmpty(&inputCommands))
{
TxCommand *cmd;
cmd = (TxCommand *) DQPopFront(&inputCommands);
if (TxDebug) TxPrintCommand(cmd);
if (cmd->tx_button == TX_EOF)
{
/* End of our input stream. */
TxError("EOF encountered on input stream -- Magic exiting.\n");
MainExit(1);
}
/****
ASSERT(cmd->tx_p.p_x > 0 && cmd->tx_p.p_x < 10000, "TxDispatch");
ASSERT(cmd->tx_p.p_y > 0 && cmd->tx_p.p_y < 10000, "TxDispatch");
****/
/* force the point, if that was requested */
if (txHaveCurrentPoint) {
cmd->tx_p = txCurrentPoint;
cmd->tx_wid = txCurrentWindowID;
if (!inFile) txHaveCurrentPoint = FALSE;
};
/****
ASSERT(cmd->tx_argc >= 0 && cmd->tx_argc <= TX_MAXARGS,
"TxDispatch");
if (cmd->tx_argc != 0) {
ASSERT(cmd->tx_button == TX_CHARACTER, "TxDispatch");
}
else {
ASSERT((cmd->tx_buttonAction == TX_BUTTON_DOWN) || \
(cmd->tx_buttonAction == TX_BUTTON_UP),
"TxDispatch");
ASSERT((cmd->tx_button == TX_LEFT_BUTTON) || \
(cmd->tx_button == TX_MIDDLE_BUTTON) || \
(cmd->tx_button == TX_RIGHT_BUTTON) || \
(cmd->tx_button == TX_CHARACTER),
"TxDispatch");
};
****/
if (!inFile && (txLogFile != NULL)) txLogCommand(cmd);
/*
* Add lisp interpreter here. We trap the character
* commands and call the lisp interpreter that traps
* any lisp-like behavior and returns a stream of "cmd"
* functions which are then sent to the appropriate window
* using the usual WindSendCommand() call.
*
* (rajit@cs.caltech.edu)
*/
#ifdef SCHEME_INTERPRETER
if (cmd->tx_button == TX_CHARACTER)
{
lisp_cur_cmd = cmd;
LispEvaluate (cmd->tx_argc, cmd->tx_argv, inFile);
TxFreeCommand (cmd);
lisp_cur_cmd = NULL;
}
else {
#endif
(void) WindSendCommand((MagWindow *) NULL, cmd, FALSE);
TxFreeCommand(cmd);
TxCommandNumber++;
#ifdef SCHEME_INTERPRETER
}
#endif
}
/* ?? */
WindUpdate();
} /* while */
done:
while (!DQIsEmpty(&inputCommands))
TxFreeCommand((TxCommand *) DQPopFront(&inputCommands));
DQFree(&inputCommands);
}
#endif /* !MAGIC_WRAPPER */
/*
* ----------------------------------------------------------------------------
*
* txCommandsInit --
*
* Initialize the things in this file
*
* Results:
* None.
*
* Side Effects:
* Variables are set up.
*
* ----------------------------------------------------------------------------
*/
void
txCommandsInit(void)
{
txZeroTime.tv_sec = 0;
txZeroTime.tv_usec = 0;
FD_ZERO(&txInputDescriptors);
txCommands.c txInputDevRec FD select() usage overhaul There are numerous concerns with the original code from a read through. The #define TX_MAX_OPEN_FILES 20 is badly named, the txCommand.c uses fd_set to hold active FD for each device, and makes no attempt to bounds check the 'fd' or 'fd_set' of any TxAdd1InputDevice() is in range. The real name of this variable should be TX_MAX_INPUT_DEVICES as it related to the fixed compile time slots available for devices, each input device must have at least one active FD and it can be in the range that fd_set allows, which is 0..1023 on linux. So based on this being the original intention, due to the way the code is constructed and API calls made available, the file has been reworked to allow all these considerations at the same time. Now the design should be FD clean for FDs in the range 0..1023 instead of being artificially clamped to the first 20 FDs being valid input devices. Renaming of variable 'i' to 'fd' when it relates to an fd number, so all uses of FD_XXXX() should use fd, this helps clarify the different domain the number relates to. When 'i' is used it relates to the index into the txInputDevRec array. A large part of the concerns relate to trying to mix the two numbering domains interchangeably, just using a different name really clears up understanding to the reader. Other items that look like errors TxDelete1InputDevice() will shuffle-remove entries with no active FD, but it is iterating an array it is also modifying, so it looks like it would have incorrectly skipped the next item that should be inspected just after a shuffle-removal occurred. The various iterators that works from 0..TX_MAX_OPEN_FILES incorrectly limited the visibility of the routines to the first 20 FDs instead of the full FD_SETSIZE the TxAddInputDevice API call allows to be registered. Passing in TxAdd1InputDevice with an fd above 19 would have resulted in everything looking successful until select() was used, then the kernel would likely not sleep/wait because the input fd_set would look blank due to being clipped by nfds=TX_MAX_OPEN_FILES (instead of that plus 1). The use of TX_MAX_OPEN_FILES in select instead of TX_MAX_OPEN_FILES+1 for the 'nfds' field would have meant a device with fd=19 would not work as the design intended. The define definition has been removed from the module public header, I assume it was there for use by another module, but the incorrect select() usage has been corrected over there in a previous commit. So now the define can be maintained near the array it relates to. While the code might looks less 'efficient' as it is now sweeping all 1024 FDs when input devices during add/remove (hopefully there maybe some compiler auto-vectorization/tzcnt use there). The main event loop is slightly more 'efficient' (especially for the single device with input fd=0 case) as it will only check the 1 FD each input event loop iteration, not check all 20 FDs for data readyness everytime.
2025-02-24 09:49:07 +01:00
txInputDescriptors_nfds = 0;
DQInit(&txInputEvents, 4);
DQInit(&txFreeEvents, 4);
DQInit(&txFreeCommands, 4);
}
#ifdef SCHEME_INTERPRETER
/*
* ----------------------------------------------------------------------------
*
* txLispDispatch --
*
* Send command to magic window.
*
* Results:
* Returns TRUE on success, FALSE on failure.
*
* Side Effects:
* whatever the command does.
*
* ----------------------------------------------------------------------------
*/
bool
TxLispDispatch (argc,argv,trace, inFile)
int argc;
char **argv;
int trace;
int inFile;
{
TxCommand *cmd = lisp_cur_cmd;
int i,j,k;
bool ret;
Point tx_p;
int wid;
cmd->tx_argc = argc;
for (i=0,j=0; i < argc; i++) {
cmd->tx_argv[i] = cmd->tx_argstring+j;
k=0;
while (cmd->tx_argstring[j] = argv[i][k])
j++,k++;
j++;
}
tx_p = cmd->tx_p;
wid = cmd->tx_wid;
if (txHaveCurrentPoint) {
cmd->tx_p = txCurrentPoint;
cmd->tx_wid = txCurrentWindowID;
if (!inFile) txHaveCurrentPoint = FALSE;
}
if (trace)
TxPrintCommand (cmd);
ret = WindSendCommand((MagWindow *) NULL, cmd, FALSE);
/* restore the original values */
cmd->tx_p = tx_p;
cmd->tx_wid = wid;
TxCommandNumber++;
return ret;
}
#endif /* SCHEME_INTERPRETER */