Compare commits

...

5 Commits

Author SHA1 Message Date
55984bc7ef
Bump copyright years
All checks were successful
Alpine 3.20 Success
2025-01-18 20:32:02 +01:00
dab190e857
eizoctl: fix message formatting on Windows
This increases the binary size, but at least we stop showing
Chinese characters instead of ASCII.
2025-01-18 20:32:02 +01:00
2594f8467d
eizoctl: also report USB-C from --input '?'
And related cleanup.
2025-01-18 20:32:02 +01:00
9039db44f6
eizoctl: make --events work with GUIs
Also add a --quiet option, and expand the program help.
2025-01-18 20:32:01 +01:00
89cab6fa39
eizoctl: display non-first numbers correctly
Humans count from 1, not from 0.
2025-01-18 18:57:16 +01:00
3 changed files with 131 additions and 66 deletions

View File

@ -1,4 +1,4 @@
Copyright (c) 2013, 2024, Přemysl Eric Janouch <p@janouch.name> Copyright (c) 2013, 2024 - 2025, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted. purpose with or without fee is hereby granted.

7
NEWS
View File

@ -1,3 +1,10 @@
Unreleased
* eizoctl: added a --quiet option to suppress information and/or errors
* eizoctl: fixed input port reporting
1.1.0 (2024-11-28) 1.1.0 (2024-11-28)
* Ported eizoctltray to macOS as well * Ported eizoctltray to macOS as well

188
eizoctl.c
View File

@ -4,7 +4,7 @@
* This program stays independent of the liberty library * This program stays independent of the liberty library
* in order to build on Windows. * in order to build on Windows.
* *
* Copyright (c) 2024, Přemysl Eric Janouch <p@janouch.name> * Copyright (c) 2025, Přemysl Eric Janouch <p@janouch.name>
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted. * purpose with or without fee is hereby granted.
@ -18,6 +18,14 @@
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
* *
*/ */
// On Windows, vswprintf() interprets %s in the width of the format string,
// and %hs is not really compliant with any standard:
// https://devblogs.microsoft.com/oldnewthing/20190830-00/?p=102823
#ifdef _WIN32
#define __USE_MINGW_ANSI_STDIO
#endif
#include <stdarg.h> #include <stdarg.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
@ -41,7 +49,9 @@
#define hid_init hidapi_hid_init #define hid_init hidapi_hid_init
#endif #endif
#if defined __GNUC__ #if defined __MINGW_GNU_PRINTF
#define ATTRIBUTE_PRINTF(x, y) __MINGW_GNU_PRINTF((x), (y))
#elif defined __GNUC__
#define ATTRIBUTE_PRINTF(x, y) __attribute__((format(printf, x, y))) #define ATTRIBUTE_PRINTF(x, y) __attribute__((format(printf, x, y)))
#else #else
#define ATTRIBUTE_PRINTF(x, y) #define ATTRIBUTE_PRINTF(x, y)
@ -803,7 +813,7 @@ eizo_port_by_name(const char *name)
return index; return index;
} }
static char * static const char *
eizo_port_to_name(uint16_t port) eizo_port_to_name(uint16_t port)
{ {
const char *stem = NULL; const char *stem = NULL;
@ -811,14 +821,14 @@ eizo_port_to_name(uint16_t port)
if (group && group < sizeof g_port_names / sizeof g_port_names[0]) if (group && group < sizeof g_port_names / sizeof g_port_names[0])
stem = g_port_names[group][0]; stem = g_port_names[group][0];
static char buffer[32] = ""; static char buf[32] = "";
if (!stem) if (!stem)
snprintf(buffer, sizeof buffer, "%x", port); snprintf(buf, sizeof buf, "%x", port);
else if (!number) else if (!number)
snprintf(buffer, sizeof buffer, "%s", stem); snprintf(buf, sizeof buf, "%s", stem);
else else
snprintf(buffer, sizeof buffer, "%s %d", stem, number); snprintf(buf, sizeof buf, "%s %d", stem, (number + 1));
return buffer; return buf;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -895,7 +905,7 @@ eizo_get_input_ports(struct eizo_monitor *m, uint16_t *ports, size_t size)
} }
static uint16_t static uint16_t
eizo_resolve_port(struct eizo_monitor *m, const char *port) eizo_resolve_port_by_name(struct eizo_monitor *m, const char *port)
{ {
uint8_t usb_c_index = 0; uint8_t usb_c_index = 0;
if (eizo_port_by_name_in_group(port, g_port_names_usb_c, &usb_c_index)) { if (eizo_port_by_name_in_group(port, g_port_names_usb_c, &usb_c_index)) {
@ -907,6 +917,26 @@ eizo_resolve_port(struct eizo_monitor *m, const char *port)
return eizo_port_by_name(port); return eizo_port_by_name(port);
} }
static const char *
eizo_resolve_port_to_name(struct eizo_monitor *m, uint16_t port)
{
// USB-C ports are a bit tricky, they only need to be /displayed/ as such.
struct eizo_profile_item *item =
&m->profile[EIZO_PROFILE_KEY_USB_C_INPUT_PORTS];
for (uint8_t i = 0; i < item->len / 2; i++) {
if (port != peek_u16le(item->data + i * 2))
continue;
static char buf[32] = "";
if (!i)
snprintf(buf, sizeof buf, "%s", g_port_names_usb_c[0]);
else
snprintf(buf, sizeof buf, "%s %u", g_port_names_usb_c[0], (i + 1));
return buf;
}
return eizo_port_to_name(port);
}
static bool static bool
eizo_set_input_port(struct eizo_monitor *m, uint16_t port) eizo_set_input_port(struct eizo_monitor *m, uint16_t port)
{ {
@ -934,8 +964,40 @@ eizo_restart(struct eizo_monitor *m)
// --- Main -------------------------------------------------------------------- // --- Main --------------------------------------------------------------------
struct catbuf {
char buf[4096];
size_t len;
};
static const char *
catf(struct catbuf *b, const char *format, ...) ATTRIBUTE_PRINTF(2, 3);
static const char *
catf(struct catbuf *b, const char *format, ...)
{
va_list ap;
va_start(ap, format);
int result = vsnprintf(b->buf + b->len, sizeof b->buf - b->len, format, ap);
va_end(ap);
if (result >= 0) {
b->len += result;
if (b->len >= sizeof b->buf)
b->len = sizeof b->buf - 1;
}
return b->buf;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
typedef void (*print_fn)(const char *format, ...) ATTRIBUTE_PRINTF(1, 2);
static void print_dummy(const char *format, ...)
{
(void) format;
}
static bool static bool
eizo_watch(struct eizo_monitor *m) eizo_watch(struct eizo_monitor *m, print_fn output, print_fn error)
{ {
uint8_t buf[1024] = {}; uint8_t buf[1024] = {};
int res = 0; int res = 0;
@ -945,57 +1007,72 @@ eizo_watch(struct eizo_monitor *m)
if (buf[0] != EIZO_REPORT_ID_GET && if (buf[0] != EIZO_REPORT_ID_GET &&
buf[0] != EIZO_REPORT_ID_GET_LONG) { buf[0] != EIZO_REPORT_ID_GET_LONG) {
printf("Unknown report ID\n"); error("Unknown report ID: %02x\n", buf[0]);
continue; continue;
} }
struct catbuf message = {{0}, 0};
uint16_t page = peek_u16le(&buf[1]), id = peek_u16le(&buf[3]); uint16_t page = peek_u16le(&buf[1]), id = peek_u16le(&buf[3]);
uint32_t usage = page << 16 | id; uint32_t usage = page << 16 | id;
printf("%08x", usage); catf(&message, "%08x", usage);
const struct parser_report *r = eizo_monitor_subreport(m, usage); const struct parser_report *r = eizo_monitor_subreport(m, usage);
if (!r) { if (!r) {
printf(" unknown usage\n"); output(catf(&message, " unknown usage\n"));
continue; continue;
} }
size_t rlen = r->report_size / 8 * r->report_count; size_t rlen = r->report_size / 8 * r->report_count;
if ((size_t) res < 7 + rlen) { if ((size_t) res < 7 + rlen) {
printf(" received data too short\n"); output(catf(&message, " received data too short\n"));
continue; continue;
} }
if (r->report_size == 16) if (r->report_size == 16)
for (size_t i = 0; i + 1 < rlen; i += 2) for (size_t i = 0; i + 1 < rlen; i += 2)
printf(" %04x", peek_u16le(&buf[7 + i])); catf(&message, " %04x", peek_u16le(&buf[7 + i]));
else else
for (size_t i = 0; i < rlen; i++) for (size_t i = 0; i < rlen; i++)
printf(" %02x", buf[7 + i]); catf(&message, " %02x", buf[7 + i]);
printf("\n"); output(catf(&message, "\n"));
} }
} }
typedef void (*print_fn)(const char *format, ...) ATTRIBUTE_PRINTF(1, 2); static const char *usage = "Usage: %s OPTION...\n\n"
" -b, --brightness [+-]BRIGHTNESS\n"
" Change monitor brightness; values go from 0 to 1 and may be relative.\n"
" -i, --input NAME\n"
" Change monitor input ports; use '?' to retrieve current values.\n"
" -r, --restart\n"
" Reboot monitors.\n"
" -e, --events\n"
" Watch for events reported by monitors.\n"
" -q, --quiet\n"
" Use once to suppress informative messages, twice to suppress errors.\n"
" -h, --help\n"
" Display this help and exit.\n"
" -V, --version\n"
" Output version information and exit.\n";
static int static int
run(int argc, char *argv[], print_fn output, print_fn error, bool verbose) run(int argc, char *argv[], print_fn output, print_fn error)
{ {
const char *name = argv[0]; const char *name = argv[0];
const char *usage = "Usage: %s [--brightness [+-]BRIGHTNESS] [--input NAME]"
" [--restart] [--events]\n";
static struct option opts[] = { static struct option opts[] = {
{"input", required_argument, NULL, 'i'},
{"brightness", required_argument, NULL, 'b'}, {"brightness", required_argument, NULL, 'b'},
{"input", required_argument, NULL, 'i'},
{"restart", no_argument, NULL, 'r'}, {"restart", no_argument, NULL, 'r'},
{"events", no_argument, NULL, 'e'}, {"events", no_argument, NULL, 'e'},
{"quiet", no_argument, NULL, 'q'},
{"help", no_argument, NULL, 'h'}, {"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'V'}, {"version", no_argument, NULL, 'V'},
{} {}
}; };
int quiet = 0;
double brightness = NAN; double brightness = NAN;
bool relative = false, restart = false, events = false; bool relative = false, restart = false, events = false;
const char *port = NULL; const char *port = NULL;
int c = 0; int c = 0;
while ((c = getopt_long(argc, argv, "b:i:h", opts, NULL)) != -1) while ((c = getopt_long(argc, argv, "b:i:reqhV", opts, NULL)) != -1)
switch (c) { switch (c) {
case 'b': case 'b':
relative = *optarg == '+' || *optarg == '-'; relative = *optarg == '+' || *optarg == '-';
@ -1013,6 +1090,9 @@ run(int argc, char *argv[], print_fn output, print_fn error, bool verbose)
case 'e': case 'e':
events = true; events = true;
break; break;
case 'q':
quiet++;
break;
case 'h': case 'h':
output(usage, name); output(usage, name);
return 0; return 0;
@ -1038,6 +1118,10 @@ run(int argc, char *argv[], print_fn output, print_fn error, bool verbose)
error("%ls\n", hid_error(NULL)); error("%ls\n", hid_error(NULL));
return 1; return 1;
} }
if (quiet > 0)
output = print_dummy;
if (quiet > 1)
error = print_dummy;
// It should be possible to choose a particular monitor, // It should be possible to choose a particular monitor,
// but it is generally more useful to operate on all of them. // but it is generally more useful to operate on all of them.
@ -1057,25 +1141,25 @@ run(int argc, char *argv[], print_fn output, print_fn error, bool verbose)
double next = relative ? brightness + prev : brightness; double next = relative ? brightness + prev : brightness;
if (!eizo_set_brightness(&m, next)) if (!eizo_set_brightness(&m, next))
error("Failed to set brightness: %s\n", m.error); error("Failed to set brightness: %s\n", m.error);
else if (verbose) else
output("%s %s: brightness: %.2f -> %.2f\n", output("%s %s: brightness: %.2f -> %.2f\n",
m.product, m.serial, prev, next); m.product, m.serial, prev, next);
} }
} }
if (port) { if (port) {
uint16_t prev = 0; uint16_t prev = 0;
uint16_t next = eizo_resolve_port(&m, port); uint16_t next = eizo_resolve_port_by_name(&m, port);
if (!eizo_get_input_port(&m, &prev)) { if (!eizo_get_input_port(&m, &prev)) {
error("Failed to get input port: %s\n", m.error); error("Failed to get input port: %s\n", m.error);
} else if (!strcmp(port, "?")) { } else if (!strcmp(port, "?")) {
output("%s %s: input: %s\n", output("%s %s: input: %s\n",
m.product, m.serial, eizo_port_to_name(prev)); m.product, m.serial, eizo_resolve_port_to_name(&m, prev));
} else if (!next) { } else if (!next) {
error("Failed to resolve port name: %s\n", port); error("Failed to resolve port name: %s\n", port);
} else { } else {
if (!eizo_set_input_port(&m, next)) if (!eizo_set_input_port(&m, next))
error("Failed to set input port: %s\n", m.error); error("Failed to set input port: %s\n", m.error);
else if (verbose) else
output("%s %s: input: %s -> %s\n", output("%s %s: input: %s -> %s\n",
m.product, m.serial, eizo_port_to_name(prev), port); m.product, m.serial, eizo_port_to_name(prev), port);
} }
@ -1083,13 +1167,13 @@ run(int argc, char *argv[], print_fn output, print_fn error, bool verbose)
if (restart) { if (restart) {
if (!eizo_restart(&m)) if (!eizo_restart(&m))
error("Failed to restart: %s\n", m.error); error("Failed to restart: %s\n", m.error);
else if (verbose) else
output("%s %s: restart\n", m.product, m.serial); output("%s %s: restart\n", m.product, m.serial);
} }
if (events) { if (events) {
if (!verbose) if (quiet)
error("Watching events is not possible in this mode\n"); error("Watching events is not possible in this mode\n");
else if (!eizo_watch(&m)) else if (!eizo_watch(&m, output, error))
error("%s\n", m.error); error("%s\n", m.error);
} }
@ -1124,7 +1208,7 @@ stdio_error(const char *format, ...)
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
return run(argc, argv, stdio_output, stdio_error, true); return run(argc, argv, stdio_output, stdio_error);
} }
// --- Windows ----------------------------------------------------------------- // --- Windows -----------------------------------------------------------------
@ -1167,8 +1251,8 @@ message_output(const char *format, ...)
wchar_t *message = message_printf(format, ap); wchar_t *message = message_printf(format, ap);
va_end(ap); va_end(ap);
if (message) { if (message) {
MessageBox( MessageBox(NULL, message,
NULL, message, NULL, MB_ICONINFORMATION | MB_OK | MB_APPLMODAL); L"Message", MB_ICONINFORMATION | MB_OK | MB_APPLMODAL);
free(message); free(message);
} }
} }
@ -1228,21 +1312,9 @@ append_monitor(struct eizo_monitor *m, HMENU menu, UINT_PTR base)
if (!ports[0]) if (!ports[0])
ports[0] = current; ports[0] = current;
// USB-C ports are a bit tricky, they only need to be /displayed/ as such.
struct eizo_profile_item *item =
&m->profile[EIZO_PROFILE_KEY_USB_C_INPUT_PORTS];
for (size_t i = 0; ports[i]; i++) { for (size_t i = 0; ports[i]; i++) {
uint8_t usb_c = 0; snwprintf(buf, sizeof buf, L"%s",
for (size_t u = 0; u < item->len / 2; u++) eizo_resolve_port_to_name(m, ports[i]));
if (ports[i] == peek_u16le(item->data + u * 2))
usb_c = u + 1;
if (!usb_c)
snwprintf(buf, sizeof buf, L"%s", eizo_port_to_name(ports[i]));
else if (usb_c == 1)
snwprintf(buf, sizeof buf, L"%s", g_port_names_usb_c[0]);
else
snwprintf(buf, sizeof buf, L"%s %u", g_port_names_usb_c[0], usb_c);
UINT flags = MF_STRING; UINT flags = MF_STRING;
if (ports[i] == current) if (ports[i] == current)
@ -1395,7 +1467,7 @@ wWinMain(
char *mb = mbargv[i + 1] = calloc(len, sizeof *mb); char *mb = mbargv[i + 1] = calloc(len, sizeof *mb);
wcstombs(mb, argv[i], len); wcstombs(mb, argv[i], len);
} }
return run(argc + 1, mbargv, message_output, message_error, false); return run(argc + 1, mbargv, message_output, message_error);
} }
LocalFree(argv); LocalFree(argv);
@ -1609,23 +1681,9 @@ message_error(const char *format, ...)
if (!ports[0]) if (!ports[0])
ports[0] = current; ports[0] = current;
// USB-C ports are a bit tricky, they only need to be /displayed/ as such.
struct eizo_profile_item *item =
&m.monitor->profile[EIZO_PROFILE_KEY_USB_C_INPUT_PORTS];
for (size_t i = 0; ports[i]; i++) { for (size_t i = 0; ports[i]; i++) {
uint8_t usb_c = 0; NSString *title = [NSString stringWithUTF8String:
for (size_t u = 0; u < item->len / 2; u++) eizo_resolve_port_to_name(m.monitor, ports[i])];
if (ports[i] == peek_u16le(item->data + u * 2))
usb_c = u + 1;
NSString *title = nil;
if (!usb_c)
title = [NSString stringWithUTF8String:eizo_port_to_name(ports[i])];
else if (usb_c == 1)
title = [NSString stringWithUTF8String:g_port_names_usb_c[0]];
else
title = [NSString stringWithFormat:@"%s %u",
g_port_names_usb_c[0], usb_c];
NSMenuItem *inputPortItem = [[NSMenuItem alloc] NSMenuItem *inputPortItem = [[NSMenuItem alloc]
initWithTitle:title action:@selector(setInputPort:) initWithTitle:title action:@selector(setInputPort:)
@ -1708,7 +1766,7 @@ main(int argc, char *argv[])
{ {
@autoreleasepool { @autoreleasepool {
if (argc > 1) if (argc > 1)
return run(argc, argv, message_output, message_error, true); return run(argc, argv, message_output, message_error);
NSApplication *app = [NSApplication sharedApplication]; NSApplication *app = [NSApplication sharedApplication];
ApplicationDelegate *delegate = [ApplicationDelegate new]; ApplicationDelegate *delegate = [ApplicationDelegate new];