Keep track of the current value you're sending to the parallel port. Each keypress modifies this value (via bitwise or to set it / bitwise and to clear it / bitwise xor to toggle it), which is then sent to the parallel port after each keypress. Since you're keeping track of the current value already sent, you modify this value as needed based on keypresses so you can combine various states into one outportb call.
An example with numbers 1 to 8 to set bits 1 through 8, and shift-1 (!) through shift-8 (*) to clear the same bits.
Code:
unsigned char port_val = 0;
int ch;
do
{
ch = getch();
switch (ch)
{
case '1':
port_val |= 0x01;
break;
case '2':
port_val |= 0x02;
break;
...
case '8' :
port_val |= 0x80;
break;
case '!' : /* use shift-1 to clear bit 1 */
port_val &= ~0x01;
break;
case '@' :
port_val &= ~0x02;
break;
...
case '*' :
port_val &= ~0x80;
break;
}
outportb(0x378, port_val);
}
while (ch != 'e');