/*
 * setleds.c - aeb, 940130, 940909, 991008
 *
 * Call: setleds [-L] [-D] [-F] [{+|-}{num|caps|scroll}]*
 * will set or clear the indicated flags on the stdin tty,
 * and report the settings before and after.
 * In particular, setleds without arguments will only report.
 */
#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/kd.h>
#include <sys/ioctl.h>
#include "nls.h"
#include "version.h"
#include "kbd_error.h"

static void __attribute__((noreturn))
usage(void)
{
	fprintf(stderr, _(
	                    "Usage:\n"
	                    "	setleds [-v] [-L] [-D] [-F] [[+|-][ num | caps | scroll %s]]\n"
	                    "Thus,\n"
	                    "	setleds +caps -num\n"
	                    "will set CapsLock, clear NumLock and leave ScrollLock unchanged.\n"
	                    "The settings before and after the change (if any) are reported\n"
	                    "when the -v option is given or when no change is requested.\n"
	                    "Normally, setleds influences the vt flag settings\n"
	                    "(and these are usually reflected in the leds).\n"
	                    "With -L, setleds only sets the leds, and leaves the flags alone.\n"
	                    "With -D, setleds sets both the flags and the default flags, so\n"
	                    "that a subsequent reset will not change the flags.\n"),
#ifdef __sparc__
	        "| compose "
#else
	        ""
#endif
	        );
	exit(EXIT_FAILURE);
}

#define onoff(a) ((a) ? _("on ") : _("off"))

/* report the bits, in the order seen on the (my) keyboard */
#define LED_NLOCK 1
#define LED_CMPOSE 2
#define LED_SCRLCK 4
#define LED_CLOCK 8

static void
sunreport(int leds)
{
	printf("NumLock %s   Compose %s   ScrollLock %s   CapsLock %s\n",
	       onoff(leds & LED_NLOCK),
	       onoff(leds & LED_CMPOSE),
	       onoff(leds & LED_SCRLCK),
	       onoff(leds & LED_CLOCK));
}

static void
report(int leds)
{
	printf("NumLock %s   CapsLock %s   ScrollLock %s\n",
	       onoff(leds & LED_NUM),
	       onoff(leds & LED_CAP),
	       onoff(leds & LED_SCR));
}

struct led {
	char *name;
	int bit;
	int sunbit;
} leds[] = {
	{ "scroll", LED_SCR, LED_SCRLCK },
	{ "num", LED_NUM, LED_NLOCK },
	{ "caps", LED_CAP, LED_CLOCK },
#ifdef __sparc__
	{ "compose", 0, LED_CMPOSE }
#endif
};

static void
getleds(char *cur_leds)
{
	if (ioctl(0, KDGETLED, cur_leds)) {
		kbd_error(EXIT_FAILURE, errno, _("Error reading current led setting. "
		                                 "Maybe stdin is not a VT?: "
		                                 "ioctl KDGETLED"));
	}
}

static int
setleds(char cur_leds)
{
	if (ioctl(0, KDSETLED, cur_leds)) {
		kbd_warning(errno, "ioctl KDSETLED");
		return -1;
	}
	return 0;
}

static void
getflags(char *flags)
{
	if (ioctl(0, KDGKBLED, flags)) {
		kbd_error(EXIT_FAILURE, errno, _("Error reading current flags setting. "
		                                 "Maybe you are not on the console?: "
		                                 "ioctl KDGKBLED"));
	}
}

static int sunkbdfd = -1;

#ifndef KIOCGLED
#define arg_state __attribute__((unused))
#else
#define arg_state
#endif

static void
sungetleds(arg_state char *cur_leds)
{
#ifdef KIOCGLED
	if (ioctl(sunkbdfd, KIOCGLED, cur_leds)) {
		kbd_error(EXIT_FAILURE, errno, _("Error reading current led setting from /dev/kbd: "
		                                 "ioctl KIOCGLED"));
	}
#else
	kbd_error(EXIT_FAILURE, 0, _("KIOCGLED unavailable?\n"));
#endif
}

#ifndef KIOCSLED
#define arg_state __attribute__((unused))
#else
#define arg_state
#endif

static void
sunsetleds(arg_state char *cur_leds)
{
#ifdef KIOCSLED
	if (ioctl(sunkbdfd, KIOCSLED, cur_leds)) {
		kbd_error(EXIT_FAILURE, errno, _("Error reading current led setting from /dev/kbd: "
		                                 "ioctl KIOCSLED"));
	}
#else
	kbd_error(EXIT_FAILURE, 0, _("KIOCSLED unavailable?\n"));
#endif
}

int main(int argc, char **argv)
{
	int optL = 0, optD = 0, optF = 0, verbose = 0;
	char oleds, nleds, oflags, nflags, odefflags, ndefflags;
	char nval, ndef, sign;
	char osunleds = 0, nsunleds, nsunval, nsundef;
	char *ap;
	struct led *lp;

	set_progname(argv[0]);

	setlocale(LC_ALL, "");
	bindtextdomain(PACKAGE_NAME, LOCALEDIR);
	textdomain(PACKAGE_NAME);

	if (argc == 2 && (!strcmp("-V", argv[1]) || !strcmp("--version", argv[1])))
		print_version_and_exit();

#ifdef __sparc__
	if ((sunkbdfd = open("/dev/kbd", O_RDONLY)) < 0) {
		kbd_error(EXIT_FAILURE, errno, "open /dev/kbd");
		/* exit(1); */
	}
#endif

	getflags(&oflags);
	getleds(&oleds);
	if (sunkbdfd >= 0)
		sungetleds(&osunleds);

	while (argc > 1) {
		if (!strcmp("-L", argv[1]))
			optL = 1;
		else if (!strcmp("-D", argv[1]))
			optD = 1;
		else if (!strcmp("-F", argv[1]))
			optF = 1;
		else if (!strcmp("-v", argv[1]))
			verbose = 1;
		else
			break;
		argc--;
		argv++;
	}

	odefflags = ndefflags = ((oflags >> 4) & 7);
	oflags = nflags = (oflags & 7);

	if (argc <= 1) {
		if (optL) {
			nleds = 0xff;
			if (setleds(nleds)) {
				kbd_error(EXIT_FAILURE, 0, _("Error resetting ledmode\n"));
			}
		}

		/* If nothing to do, report, even if not verbose */
		if (!optD && !optL && !optF)
			optD = optL = optF = 1;
		if (optD) {
			printf(_("Current default flags:  "));
			report(odefflags);
		}
		if (optF) {
			printf(_("Current flags:          "));
			report(oflags & 07);
		}
		if (optL) {
			printf(_("Current leds:           "));
			if (sunkbdfd >= 0)
				sunreport(osunleds);
			else
				report(oleds);
		}
		exit(EXIT_SUCCESS);
	}

	if (!optL)
		optF = 1;
	nval         = 0;
	ndef         = 0;
	nsunval      = 0;
	nsundef      = 0;

	while (--argc) {
		ap   = *++argv;
		sign = 1; /* by default: set */
		if (*ap == '+')
			ap++;
		else if (*ap == '-') {
			sign = 0;
			ap++;
		}
		for (lp = leds; (unsigned)(lp - leds) < sizeof(leds) / sizeof(leds[0]); lp++) {
			if (!strcmp(ap, lp->name)) {
				if (sign) {
					nval |= lp->bit;
					nsunval |= lp->sunbit;
				}
				ndef |= lp->bit;
				nsundef |= lp->sunbit;
				goto nxtarg;
			}
		}
		fprintf(stderr, _("unrecognized argument: _%s_\n\n"), ap);
		usage();

	nxtarg:;
	}

	if (optD) {
		ndefflags = (odefflags & ~ndef) | nval;
		if (verbose) {
			printf(_("Old default flags:    "));
			report(odefflags);
			printf(_("New default flags:    "));
			report(ndefflags);
		}
	}
	if (optF) {
		nflags = ((oflags & ~ndef) | nval);
		if (verbose) {
			printf(_("Old flags:            "));
			report(oflags & 07);
			printf(_("New flags:            "));
			report(nflags & 07);
		}
	}
	if (optD || optF) {
		if (ioctl(0, KDSKBLED, (ndefflags << 4) | nflags)) {
			kbd_error(EXIT_FAILURE, errno, "ioctl KDSKBLED");
		}
	}
	if (optL) {
		if (sunkbdfd >= 0) {
			nsunleds = (osunleds & ~nsundef) | nsunval;
			if (verbose) {
				printf(_("Old leds:             "));
				sunreport(osunleds);
				printf(_("New leds:             "));
				sunreport(nsunleds);
			}
			sunsetleds(&nsunleds);
		} else {
			nleds = (oleds & ~ndef) | nval;
			if (verbose) {
				printf(_("Old leds:             "));
				report(oleds);
				printf(_("New leds:             "));
				report(nleds);
			}
			if (setleds(nleds))
				exit(EXIT_FAILURE);
		}
	}
	exit(EXIT_SUCCESS);
}
