Port eizoctltray to macOS
Also bump minimum CMake version for hidapi_ROOT, and don't try to run the help2man Perl script in MSYS2. AppKit is a very miserable thing.
This commit is contained in:
		@@ -1,4 +1,4 @@
 | 
				
			|||||||
cmake_minimum_required (VERSION 3.10)
 | 
					cmake_minimum_required (VERSION 3.12)
 | 
				
			||||||
project (usb-drivers VERSION 1.0.0
 | 
					project (usb-drivers VERSION 1.0.0
 | 
				
			||||||
	DESCRIPTION "User space USB drivers" LANGUAGES C)
 | 
						DESCRIPTION "User space USB drivers" LANGUAGES C)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -35,9 +35,62 @@ endif ()
 | 
				
			|||||||
# Dependencies
 | 
					# Dependencies
 | 
				
			||||||
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/liberty/cmake)
 | 
					set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/liberty/cmake)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# TODO(p): Shove this into IconUtils.cmake.
 | 
				
			||||||
 | 
					function (icon_to_iconset_size name svg size iconset outputs)
 | 
				
			||||||
 | 
						math (EXPR _size2x "${size} * 2")
 | 
				
			||||||
 | 
						set (_dimensions "${size}x${size}")
 | 
				
			||||||
 | 
						set (_png1x "${iconset}/icon_${_dimensions}.png")
 | 
				
			||||||
 | 
						set (_png2x "${iconset}/icon_${_dimensions}@2x.png")
 | 
				
			||||||
 | 
						set (${outputs} "${_png1x};${_png2x}" PARENT_SCOPE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						set (_find_program_REQUIRE)
 | 
				
			||||||
 | 
						if (NOT ${CMAKE_VERSION} VERSION_LESS 3.18.0)
 | 
				
			||||||
 | 
							set (_find_program_REQUIRE REQUIRED)
 | 
				
			||||||
 | 
						endif ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						find_program (rsvg_convert_EXECUTABLE rsvg-convert ${_find_program_REQUIRE})
 | 
				
			||||||
 | 
						add_custom_command (OUTPUT "${_png1x}" "${_png2x}"
 | 
				
			||||||
 | 
							COMMAND ${CMAKE_COMMAND} -E make_directory "${iconset}"
 | 
				
			||||||
 | 
							COMMAND ${rsvg_convert_EXECUTABLE} "--output=${_png1x}"
 | 
				
			||||||
 | 
								"--width=${size}" "--height=${size}" -- "${svg}"
 | 
				
			||||||
 | 
							COMMAND ${rsvg_convert_EXECUTABLE} "--output=${_png2x}"
 | 
				
			||||||
 | 
								"--width=${_size2x}" "--height=${_size2x}" -- "${svg}"
 | 
				
			||||||
 | 
							DEPENDS "${svg}"
 | 
				
			||||||
 | 
							COMMENT "Generating ${name} ${_dimensions} icons" VERBATIM)
 | 
				
			||||||
 | 
					endfunction ()
 | 
				
			||||||
 | 
					function (icon_to_icns svg output_basename output)
 | 
				
			||||||
 | 
						get_filename_component (_name "${output_basename}" NAME_WE)
 | 
				
			||||||
 | 
						set (_iconset "${PROJECT_BINARY_DIR}/${_name}.iconset")
 | 
				
			||||||
 | 
						set (_icon "${PROJECT_BINARY_DIR}/${output_basename}")
 | 
				
			||||||
 | 
						set (${output} "${_icon}" PARENT_SCOPE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						set (_icon_png_list)
 | 
				
			||||||
 | 
						foreach (_icon_size 16 32 128 256 512)
 | 
				
			||||||
 | 
							icon_to_iconset_size ("${_name}" "${svg}"
 | 
				
			||||||
 | 
								"${_icon_size}" "${_iconset}" _icon_pngs)
 | 
				
			||||||
 | 
							list (APPEND _icon_png_list ${_icon_pngs})
 | 
				
			||||||
 | 
						endforeach ()
 | 
				
			||||||
 | 
						add_custom_command (OUTPUT "${_icon}"
 | 
				
			||||||
 | 
							COMMAND iconutil -c icns -o "${_icon}" "${_iconset}"
 | 
				
			||||||
 | 
							DEPENDS ${_icon_png_list}
 | 
				
			||||||
 | 
							COMMENT "Generating ${_name} icon" VERBATIM)
 | 
				
			||||||
 | 
						set_source_files_properties ("${_icon}" PROPERTIES
 | 
				
			||||||
 | 
							MACOSX_PACKAGE_LOCATION Resources)
 | 
				
			||||||
 | 
					endfunction ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
find_package (PkgConfig REQUIRED)
 | 
					find_package (PkgConfig REQUIRED)
 | 
				
			||||||
pkg_check_modules (libusb libusb-1.0)
 | 
					pkg_check_modules (libusb libusb-1.0)
 | 
				
			||||||
pkg_search_module (hidapi hidapi hidapi-hidraw hidapi-libusb)
 | 
					
 | 
				
			||||||
 | 
					# On MSYS2, the CMake package cannot link statically, but pkg-config can.
 | 
				
			||||||
 | 
					# On macOS, we explicitly want to use the CMake package.
 | 
				
			||||||
 | 
					if (WIN32)
 | 
				
			||||||
 | 
						pkg_search_module (hidapi hidapi hidapi-hidraw hidapi-libusb)
 | 
				
			||||||
 | 
					else ()
 | 
				
			||||||
 | 
						find_package (hidapi)
 | 
				
			||||||
 | 
						set (hidapi_INCLUDE_DIRS)
 | 
				
			||||||
 | 
						set (hidapi_LIBRARY_DIRS)
 | 
				
			||||||
 | 
						set (hidapi_LIBRARIES hidapi::hidapi)
 | 
				
			||||||
 | 
					endif ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
option (WITH_LIBUSB "Compile with libusb-based utilities" ${libusb_FOUND})
 | 
					option (WITH_LIBUSB "Compile with libusb-based utilities" ${libusb_FOUND})
 | 
				
			||||||
option (WITH_HIDAPI "Compile with hidapi-based utilities" ${hidapi_FOUND})
 | 
					option (WITH_HIDAPI "Compile with hidapi-based utilities" ${hidapi_FOUND})
 | 
				
			||||||
@@ -97,7 +150,6 @@ if (WITH_HIDAPI AND WIN32)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	set (icon_ico ${PROJECT_BINARY_DIR}/eizoctltray.ico)
 | 
						set (icon_ico ${PROJECT_BINARY_DIR}/eizoctltray.ico)
 | 
				
			||||||
	icon_for_win32 (${icon_ico} "${icon_png_list}" "${icon_png}")
 | 
						icon_for_win32 (${icon_ico} "${icon_png_list}" "${icon_png}")
 | 
				
			||||||
	list (APPEND icon_ico_list )
 | 
					 | 
				
			||||||
	set_property (SOURCE eizoctltray.rc
 | 
						set_property (SOURCE eizoctltray.rc
 | 
				
			||||||
		APPEND PROPERTY OBJECT_DEPENDS ${icon_ico})
 | 
							APPEND PROPERTY OBJECT_DEPENDS ${icon_ico})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -108,9 +160,27 @@ if (WITH_HIDAPI AND WIN32)
 | 
				
			|||||||
	target_link_directories (eizoctltray PUBLIC ${hidapi_LIBRARY_DIRS})
 | 
						target_link_directories (eizoctltray PUBLIC ${hidapi_LIBRARY_DIRS})
 | 
				
			||||||
	target_link_libraries (eizoctltray ${hidapi_LIBRARIES} powrprof)
 | 
						target_link_libraries (eizoctltray ${hidapi_LIBRARIES} powrprof)
 | 
				
			||||||
endif ()
 | 
					endif ()
 | 
				
			||||||
 | 
					if (WITH_HIDAPI AND APPLE)
 | 
				
			||||||
 | 
						list (APPEND targets_gui eizoctltray)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# We override the language for the command line target as well,
 | 
				
			||||||
 | 
						# but that doesn't and must not pose any problems.
 | 
				
			||||||
 | 
						enable_language (OBJC)
 | 
				
			||||||
 | 
						set_source_files_properties (eizoctl.c PROPERTIES LANGUAGE OBJC)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						set (MACOSX_BUNDLE_GUI_IDENTIFIER name.janouch.eizoctltray)
 | 
				
			||||||
 | 
						set (MACOSX_BUNDLE_ICON_FILE eizoctltray.icns)
 | 
				
			||||||
 | 
						icon_to_icns (${PROJECT_SOURCE_DIR}/eizoctltray.svg
 | 
				
			||||||
 | 
							"${MACOSX_BUNDLE_ICON_FILE}" icon)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						add_executable (eizoctltray MACOSX_BUNDLE eizoctl.c "${icon}")
 | 
				
			||||||
 | 
						target_compile_definitions (eizoctltray PUBLIC -DTRAY)
 | 
				
			||||||
 | 
						target_compile_options (eizoctltray PUBLIC -fobjc-arc)
 | 
				
			||||||
 | 
						target_link_libraries (eizoctltray ${hidapi_LIBRARIES} "-framework Cocoa")
 | 
				
			||||||
 | 
					endif ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Generate documentation from help output
 | 
					# Generate documentation from help output
 | 
				
			||||||
if (NOT CMAKE_CROSSCOMPILING)
 | 
					if (NOT WIN32 AND NOT CMAKE_CROSSCOMPILING)
 | 
				
			||||||
	find_program (HELP2MAN_EXECUTABLE help2man)
 | 
						find_program (HELP2MAN_EXECUTABLE help2man)
 | 
				
			||||||
	if (NOT HELP2MAN_EXECUTABLE)
 | 
						if (NOT HELP2MAN_EXECUTABLE)
 | 
				
			||||||
		message (FATAL_ERROR "help2man not found")
 | 
							message (FATAL_ERROR "help2man not found")
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										28
									
								
								README.adoc
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								README.adoc
									
									
									
									
									
								
							@@ -19,11 +19,12 @@ and may not run at the same time, as it would contend for device access.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
eizoctltray
 | 
					eizoctltray
 | 
				
			||||||
~~~~~~~~~~~
 | 
					~~~~~~~~~~~
 | 
				
			||||||
_eizoctltray_ is a derived Windows utility that can stay in the systray.
 | 
					_eizoctltray_ is a derived Windows/macOS utility that can stay in the systray.
 | 
				
			||||||
When holding the Shift or Control keys while switching signal inputs,
 | 
					When holding the Shift or Control keys while switching signal inputs,
 | 
				
			||||||
it will also suspend or power off the system, respectively.
 | 
					it will also suspend or power off the system, respectively.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
image::eizoctltray.png["eizoctltray with expanded context menu", 343, 229]
 | 
					image:eizoctltray-win.png["eizoctltray on Windows with expanded menu", 343, 278]
 | 
				
			||||||
 | 
					image:eizoctltray-mac.png["eizoctltray on macOS with expanded menu", 343, 278]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
elksmart-comm
 | 
					elksmart-comm
 | 
				
			||||||
~~~~~~~~~~~~~
 | 
					~~~~~~~~~~~~~
 | 
				
			||||||
@@ -77,6 +78,29 @@ Or you can try telling CMake to make a package for you.  For Debian it is:
 | 
				
			|||||||
 $ cpack -G DEB
 | 
					 $ cpack -G DEB
 | 
				
			||||||
 # dpkg -i usb-drivers-*.deb
 | 
					 # dpkg -i usb-drivers-*.deb
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Windows
 | 
				
			||||||
 | 
					~~~~~~~
 | 
				
			||||||
 | 
					You can either build within an MSYS2 environment,
 | 
				
			||||||
 | 
					or cross-compile using Mingw-w64:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 $ sh -e cmake/Win64Depends.sh
 | 
				
			||||||
 | 
					 $ cmake -DCMAKE_TOOLCHAIN_FILE=liberty/cmake/toolchains/MinGW-w64-x64.cmake \
 | 
				
			||||||
 | 
					   -DCMAKE_BUILD_TYPE=Release -B build
 | 
				
			||||||
 | 
					 $ cmake --build build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					macOS
 | 
				
			||||||
 | 
					~~~~~
 | 
				
			||||||
 | 
					You can either build _eizoctltray_ against Homebrew,
 | 
				
			||||||
 | 
					or link hidapi statically for a standalone portable app:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 $ git clone https://github.com/libusb/hidapi.git
 | 
				
			||||||
 | 
					 $ cmake -S hidapi -DBUILD_SHARED_LIBS=OFF \
 | 
				
			||||||
 | 
					   -DCMAKE_INSTALL_PREFIX=$PWD/hidapi-build \
 | 
				
			||||||
 | 
					   -DCMAKE_BUILD_TYPE=Release -B hidapi-build
 | 
				
			||||||
 | 
					 $ cmake --build hidapi-build -- install
 | 
				
			||||||
 | 
					 $ cmake -Dhidapi_ROOT=$PWD/hidapi-build -DCMAKE_BUILD_TYPE=Release -B build
 | 
				
			||||||
 | 
					 $ cmake --build build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Contributing and Support
 | 
					Contributing and Support
 | 
				
			||||||
------------------------
 | 
					------------------------
 | 
				
			||||||
Use https://git.janouch.name/p/usb-drivers to report bugs, request features,
 | 
					Use https://git.janouch.name/p/usb-drivers to report bugs, request features,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										305
									
								
								eizoctl.c
									
									
									
									
									
								
							
							
						
						
									
										305
									
								
								eizoctl.c
									
									
									
									
									
								
							@@ -880,6 +880,20 @@ eizo_get_input_port(struct eizo_monitor *m, uint16_t *port)
 | 
				
			|||||||
	return true;
 | 
						return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					eizo_get_input_ports(struct eizo_monitor *m, uint16_t *ports, size_t size)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct eizo_profile_item *item = &m->profile[EIZO_PROFILE_KEY_INPUT_PORTS];
 | 
				
			||||||
 | 
						if (item->len) {
 | 
				
			||||||
 | 
							for (size_t i = 0; i < size && i < item->len / 4; i++)
 | 
				
			||||||
 | 
								ports[i] = peek_u16le(item->data + i * 4);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							const uint16_t *db = eizo_ports_by_product_name(m->product);
 | 
				
			||||||
 | 
							for (size_t i = 0; i < size && db && db[i]; i++)
 | 
				
			||||||
 | 
								ports[i] = db[i];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static uint16_t
 | 
					static uint16_t
 | 
				
			||||||
eizo_resolve_port(struct eizo_monitor *m, const char *port)
 | 
					eizo_resolve_port(struct eizo_monitor *m, const char *port)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -1114,7 +1128,7 @@ main(int argc, char *argv[])
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Windows -----------------------------------------------------------------
 | 
					// --- Windows -----------------------------------------------------------------
 | 
				
			||||||
#else
 | 
					#elif defined _WIN32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define WIN32_LEAN_AND_MEAN
 | 
					#define WIN32_LEAN_AND_MEAN
 | 
				
			||||||
#include <windows.h>
 | 
					#include <windows.h>
 | 
				
			||||||
@@ -1208,24 +1222,15 @@ append_monitor(struct eizo_monitor *m, HMENU menu, UINT_PTR base)
 | 
				
			|||||||
	AppendMenu(menu, flags_darker,   base + IDM_DARKER,   L"Darker");
 | 
						AppendMenu(menu, flags_darker,   base + IDM_DARKER,   L"Darker");
 | 
				
			||||||
	AppendMenu(menu, MF_SEPARATOR, 0, NULL);
 | 
						AppendMenu(menu, MF_SEPARATOR, 0, NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	uint16_t ports[16] = {0};
 | 
						uint16_t ports[16] = {0}, current = 0;
 | 
				
			||||||
	struct eizo_profile_item *item = &m->profile[EIZO_PROFILE_KEY_INPUT_PORTS];
 | 
						eizo_get_input_ports(m, ports, sizeof ports / sizeof ports[0] - 1);
 | 
				
			||||||
	if (item->len) {
 | 
					 | 
				
			||||||
		for (size_t i = 0; i < 15 && i < item->len / 4; i++)
 | 
					 | 
				
			||||||
			ports[i] = peek_u16le(item->data + i * 4);
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		const uint16_t *db = eizo_ports_by_product_name(m->product);
 | 
					 | 
				
			||||||
		for (size_t i = 0; i < 15 && db && db[i]; i++)
 | 
					 | 
				
			||||||
			ports[i] = db[i];
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	uint16_t current = 0;
 | 
					 | 
				
			||||||
	(void) eizo_get_input_port(m, ¤t);
 | 
						(void) eizo_get_input_port(m, ¤t);
 | 
				
			||||||
	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.
 | 
						// USB-C ports are a bit tricky, they only need to be /displayed/ as such.
 | 
				
			||||||
	item = &m->profile[EIZO_PROFILE_KEY_USB_C_INPUT_PORTS];
 | 
						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;
 | 
							uint8_t usb_c = 0;
 | 
				
			||||||
		for (size_t u = 0; u < item->len / 2; u++)
 | 
							for (size_t u = 0; u < item->len / 2; u++)
 | 
				
			||||||
@@ -1442,4 +1447,276 @@ wWinMain(
 | 
				
			|||||||
	return msg.wParam;
 | 
						return msg.wParam;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// --- macOS -------------------------------------------------------------------
 | 
				
			||||||
 | 
					#elif defined __APPLE__
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <AppKit/AppKit.h>
 | 
				
			||||||
 | 
					#include <AppKit/NSStatusBar.h>
 | 
				
			||||||
 | 
					#include <Foundation/Foundation.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void message_output(const char *format, ...) ATTRIBUTE_PRINTF(1, 2);
 | 
				
			||||||
 | 
					static void message_error(const char *format, ...) ATTRIBUTE_PRINTF(1, 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					message_output(const char *format, ...)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						va_list ap;
 | 
				
			||||||
 | 
						va_start(ap, format);
 | 
				
			||||||
 | 
						NSString *message = [[NSString alloc]
 | 
				
			||||||
 | 
							initWithFormat:[NSString stringWithUTF8String: format] arguments:ap];
 | 
				
			||||||
 | 
						va_end(ap);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						NSAlert *alert = [NSAlert new];
 | 
				
			||||||
 | 
						[alert setMessageText:message];
 | 
				
			||||||
 | 
						[alert setAlertStyle:NSAlertStyleInformational];
 | 
				
			||||||
 | 
						// XXX: How to make the OK button the first responder?
 | 
				
			||||||
 | 
						[alert addButtonWithTitle:@"OK"];
 | 
				
			||||||
 | 
						[NSApp activate];
 | 
				
			||||||
 | 
						[alert.window makeKeyAndOrderFront:nil];
 | 
				
			||||||
 | 
						[alert runModal];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					message_error(const char *format, ...)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						va_list ap;
 | 
				
			||||||
 | 
						va_start(ap, format);
 | 
				
			||||||
 | 
						NSString *message = [[NSString alloc]
 | 
				
			||||||
 | 
							initWithFormat:[NSString stringWithUTF8String: format] arguments:ap];
 | 
				
			||||||
 | 
						va_end(ap);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						NSAlert *alert = [NSAlert new];
 | 
				
			||||||
 | 
						[alert setMessageText:message];
 | 
				
			||||||
 | 
						[alert setAlertStyle:NSAlertStyleCritical];
 | 
				
			||||||
 | 
						[alert addButtonWithTitle:@"OK"];
 | 
				
			||||||
 | 
						[NSApp activate];
 | 
				
			||||||
 | 
						[alert.window makeKeyAndOrderFront:nil];
 | 
				
			||||||
 | 
						[alert runModal];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Monitor provides reference counting, and enables use of NSArray.
 | 
				
			||||||
 | 
					@interface Monitor : NSObject
 | 
				
			||||||
 | 
					@property (assign, nonatomic) struct eizo_monitor *monitor;
 | 
				
			||||||
 | 
					- (instancetype)initWithMonitor:(struct eizo_monitor *)monitor;
 | 
				
			||||||
 | 
					@end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@implementation Monitor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- (instancetype)initWithMonitor:(struct eizo_monitor *)monitor {
 | 
				
			||||||
 | 
						if (self = [super init]) {
 | 
				
			||||||
 | 
							_monitor = monitor;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return self;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- (void)dealloc {
 | 
				
			||||||
 | 
						if (_monitor) {
 | 
				
			||||||
 | 
							eizo_monitor_close(_monitor);
 | 
				
			||||||
 | 
							free(_monitor);
 | 
				
			||||||
 | 
							_monitor = NULL;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@interface ApplicationDelegate
 | 
				
			||||||
 | 
						: NSObject <NSApplicationDelegate, NSMenuDelegate>
 | 
				
			||||||
 | 
					@property (strong, nonatomic) NSStatusItem *statusItem;
 | 
				
			||||||
 | 
					@property (strong, nonatomic) NSMutableArray<Monitor *> *monitors;
 | 
				
			||||||
 | 
					@end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@implementation ApplicationDelegate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- (Monitor *)getMonitorFrom:(NSControl *)control {
 | 
				
			||||||
 | 
						NSInteger index = control.tag / 0x1000;
 | 
				
			||||||
 | 
						if (!self.monitors || index < 0 || index >= self.monitors.count)
 | 
				
			||||||
 | 
							return nil;
 | 
				
			||||||
 | 
						return self.monitors[index];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- (void)setBrightness:(NSControl *)sender {
 | 
				
			||||||
 | 
						Monitor *m = [self getMonitorFrom:sender];
 | 
				
			||||||
 | 
						if (!m)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						eizo_set_brightness(m.monitor, sender.doubleValue);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- (void)setInputPort:(NSControl *)sender {
 | 
				
			||||||
 | 
						Monitor *m = [self getMonitorFrom:sender];
 | 
				
			||||||
 | 
						NSUInteger input = sender.tag % 0x1000;
 | 
				
			||||||
 | 
						if (!m)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						eizo_set_input_port(m.monitor, input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						NSEventModifierFlags mods = [NSEvent modifierFlags];
 | 
				
			||||||
 | 
						if (mods & NSEventModifierFlagShift) {
 | 
				
			||||||
 | 
							NSTask *task = [[NSTask alloc] init];
 | 
				
			||||||
 | 
							task.launchPath = @"/usr/bin/pmset";
 | 
				
			||||||
 | 
							task.arguments = @[@"sleepnow"];
 | 
				
			||||||
 | 
							[task launch];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- (void)appendMonitor:(Monitor *)m toMenu:(NSMenu *)menu base:(NSInteger)base {
 | 
				
			||||||
 | 
						NSMenuItem *titleItem = [NSMenuItem new];
 | 
				
			||||||
 | 
						titleItem.attributedTitle = [[NSAttributedString alloc]
 | 
				
			||||||
 | 
							initWithString:[NSString stringWithFormat:@"%s %s",
 | 
				
			||||||
 | 
								m.monitor->product, m.monitor->serial]
 | 
				
			||||||
 | 
							attributes:@{ NSFontAttributeName: [NSFont boldSystemFontOfSize:0] }];
 | 
				
			||||||
 | 
						[menu addItem:titleItem];
 | 
				
			||||||
 | 
						[menu addItem:[NSMenuItem separatorItem]];
 | 
				
			||||||
 | 
						[menu addItem:[NSMenuItem sectionHeaderWithTitle:@"Brightness"]];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						double brightness = 0;
 | 
				
			||||||
 | 
						(void) eizo_get_brightness(m.monitor, &brightness);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// XXX: So, while having a slider is strictly more useful,
 | 
				
			||||||
 | 
						// this is not something you're supposed to do in AppKit, if only because:
 | 
				
			||||||
 | 
						//  - It does not respond to keyboard.
 | 
				
			||||||
 | 
						//  - Positioning it properly is dark magic.
 | 
				
			||||||
 | 
						NSSlider *slider = [NSSlider
 | 
				
			||||||
 | 
							sliderWithValue:brightness minValue:0. maxValue:1.
 | 
				
			||||||
 | 
							target:self action:@selector(setBrightness:)];
 | 
				
			||||||
 | 
						slider.tag = base;
 | 
				
			||||||
 | 
						slider.continuous = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						NSView *sliderView = [[NSView alloc]
 | 
				
			||||||
 | 
							initWithFrame:NSMakeRect(0, 0, 200., slider.knobThickness + 2.)];
 | 
				
			||||||
 | 
						[sliderView addSubview:slider];
 | 
				
			||||||
 | 
						slider.translatesAutoresizingMaskIntoConstraints = false;
 | 
				
			||||||
 | 
						[NSLayoutConstraint activateConstraints:@[
 | 
				
			||||||
 | 
					        [slider.leftAnchor
 | 
				
			||||||
 | 
								constraintEqualToAnchor:sliderView.leftAnchor constant:+23.],
 | 
				
			||||||
 | 
					        [slider.rightAnchor
 | 
				
			||||||
 | 
								constraintEqualToAnchor:sliderView.rightAnchor constant:-6.],
 | 
				
			||||||
 | 
					        [slider.centerYAnchor
 | 
				
			||||||
 | 
								constraintEqualToAnchor:sliderView.centerYAnchor]
 | 
				
			||||||
 | 
					    ]];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						NSMenuItem *brightnessItem = [[NSMenuItem alloc]
 | 
				
			||||||
 | 
							initWithTitle:@"" action:nil keyEquivalent:@""];
 | 
				
			||||||
 | 
						brightnessItem.view = sliderView;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[menu addItem:brightnessItem];
 | 
				
			||||||
 | 
						[menu addItem:[NSMenuItem separatorItem]];
 | 
				
			||||||
 | 
						[menu addItem:[NSMenuItem sectionHeaderWithTitle:@"Input ports"]];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint16_t ports[16] = {0}, current = 0;
 | 
				
			||||||
 | 
						eizo_get_input_ports(m.monitor, ports, sizeof ports / sizeof ports[0] - 1);
 | 
				
			||||||
 | 
						(void) eizo_get_input_port(m.monitor, ¤t);
 | 
				
			||||||
 | 
						if (!ports[0])
 | 
				
			||||||
 | 
							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++) {
 | 
				
			||||||
 | 
							uint8_t usb_c = 0;
 | 
				
			||||||
 | 
							for (size_t u = 0; u < item->len / 2; u++)
 | 
				
			||||||
 | 
								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]
 | 
				
			||||||
 | 
								initWithTitle:title action:@selector(setInputPort:)
 | 
				
			||||||
 | 
								keyEquivalent:@""];
 | 
				
			||||||
 | 
							inputPortItem.tag = base + ports[i];
 | 
				
			||||||
 | 
							if (ports[i] == current)
 | 
				
			||||||
 | 
								inputPortItem.state = NSControlStateValueOn;
 | 
				
			||||||
 | 
							[menu addItem:inputPortItem];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- (void)showMenu {
 | 
				
			||||||
 | 
						struct hid_device_info *devs = hid_enumerate(USB_VID_EIZO, 0);
 | 
				
			||||||
 | 
						NSMutableArray<Monitor *> *monitors = [NSMutableArray array];
 | 
				
			||||||
 | 
						NSMenu *menu = [NSMenu new];
 | 
				
			||||||
 | 
						[menu setDelegate:self];
 | 
				
			||||||
 | 
						for (struct hid_device_info *p = devs; p; p = p->next) {
 | 
				
			||||||
 | 
							struct eizo_monitor *m = calloc(1, sizeof *m);
 | 
				
			||||||
 | 
							if (!m)
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!eizo_monitor_open(m, p)) {
 | 
				
			||||||
 | 
								message_error("%s", m->error);
 | 
				
			||||||
 | 
								free(m);
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Monitor *monitor = [[Monitor alloc] initWithMonitor:m];
 | 
				
			||||||
 | 
							[self appendMonitor:monitor toMenu:menu base:0x1000 * monitors.count];
 | 
				
			||||||
 | 
							[menu addItem:[NSMenuItem separatorItem]];
 | 
				
			||||||
 | 
							[monitors addObject:monitor];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (!monitors.count) {
 | 
				
			||||||
 | 
							NSMenuItem *item = [[NSMenuItem alloc]
 | 
				
			||||||
 | 
								initWithTitle:@"No monitors found" action:nil keyEquivalent:@""];
 | 
				
			||||||
 | 
							item.enabled = false;
 | 
				
			||||||
 | 
							[menu addItem:item];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[menu addItem:[NSMenuItem separatorItem]];
 | 
				
			||||||
 | 
						[menu addItem:[[NSMenuItem alloc]
 | 
				
			||||||
 | 
							initWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"]];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						self.monitors = monitors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// XXX: Unfortunately, this is not how menus should behave,
 | 
				
			||||||
 | 
						// but we really want to generate the menu on demand.
 | 
				
			||||||
 | 
						self.statusItem.menu = menu;
 | 
				
			||||||
 | 
						[self.statusItem.button performClick:nil];
 | 
				
			||||||
 | 
						self.statusItem.menu = nil;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- (void)menuDidClose:(NSMenu *)menu {
 | 
				
			||||||
 | 
						// Close and free up the devices as soon as possible, but no sooner.
 | 
				
			||||||
 | 
						dispatch_async(dispatch_get_main_queue(), ^{
 | 
				
			||||||
 | 
							self.monitors = nil;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- (void)applicationDidFinishLaunching:(NSNotification *)notification {
 | 
				
			||||||
 | 
						NSStatusBar *systemBar = [NSStatusBar systemStatusBar];
 | 
				
			||||||
 | 
						self.statusItem = [systemBar statusItemWithLength:NSSquareStatusItemLength];
 | 
				
			||||||
 | 
						if (!self.statusItem.button)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Not bothering with templates,
 | 
				
			||||||
 | 
						// the icon would need to have a hole through it to look better.
 | 
				
			||||||
 | 
						NSImage *image = [NSApp applicationIconImage];
 | 
				
			||||||
 | 
						// One would expect the status bar to pick a reasonable size
 | 
				
			||||||
 | 
						// automatically, but that is not what happens.
 | 
				
			||||||
 | 
						image.size = NSMakeSize(systemBar.thickness, systemBar.thickness);
 | 
				
			||||||
 | 
						self.statusItem.button.image = image;
 | 
				
			||||||
 | 
						self.statusItem.button.action = @selector(showMenu);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int
 | 
				
			||||||
 | 
					main(int argc, char *argv[])
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						@autoreleasepool {
 | 
				
			||||||
 | 
							if (argc > 1)
 | 
				
			||||||
 | 
								return run(argc, argv, message_output, message_error, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							NSApplication *app = [NSApplication sharedApplication];
 | 
				
			||||||
 | 
							ApplicationDelegate *delegate = [ApplicationDelegate new];
 | 
				
			||||||
 | 
							app.delegate = delegate;
 | 
				
			||||||
 | 
							[app setActivationPolicy:NSApplicationActivationPolicyAccessory];
 | 
				
			||||||
 | 
							[app run];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								eizoctltray-mac.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								eizoctltray-mac.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 14 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								eizoctltray-win.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								eizoctltray-win.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 7.7 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								eizoctltray.png
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								eizoctltray.png
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 7.7 KiB  | 
		Reference in New Issue
	
	Block a user