diff --git a/meson.build b/meson.build index 3868c0e..9075adf 100644 --- a/meson.build +++ b/meson.build @@ -93,6 +93,9 @@ psy_deps = [ epoxy_dep, gstreamer_dep, gstreamer_app_dep, + cairo_dep, + pango_dep, + pangocairo_dep ] # add optional dependencies @@ -144,6 +147,12 @@ endif # psy_deps += kernel32_dep # endif +# to query WMI for hardware stuff on windows +wbemuuid_dep = cc.find_library('wbemuuid', static:true, required: false) +if wbemuuid_dep.found() + psy_deps += wbemuuid_dep +endif + message('the depencies are: ') foreach dep : psy_deps message(' - ', dep.name()) @@ -156,6 +165,7 @@ psy_deps += winmm_dep psy_deps += kernel32_dep gnome = import('gnome') +fs = import ('fs') subdir('psy') subdir('share') @@ -181,5 +191,3 @@ endif if get_option('documentation') subdir('doc') endif - - diff --git a/programs/meson.build b/programs/meson.build index 1a61b5f..434a2ca 100644 --- a/programs/meson.build +++ b/programs/meson.build @@ -6,3 +6,8 @@ psy_enum_fonts = executable ( dependencies : [psy_dep] ) +psy_enum_parallel = executable ( + 'psy-enum-parallel', + files('psy-enum-parallel.c'), + dependencies : [psy_dep] +) diff --git a/programs/psy-enum-parallel.c b/programs/psy-enum-parallel.c new file mode 100644 index 0000000..278e8e6 --- /dev/null +++ b/programs/psy-enum-parallel.c @@ -0,0 +1,22 @@ + +#include "hw/psy-parallel-port.h" +#include + +int +main(void) +{ + gint n = 0; + PsyParallelPortInfo **ports = NULL; + + PsyParallelPort *port = psy_parallel_port_new(); + + psy_parallel_port_enumerate(port, &ports, &n); + + for (int i = 0; i < n; i++) { + g_print("Port %d: %s\n", + psy_parallel_port_info_port_number(ports[i]), + psy_parallel_port_info_name(ports[i])); + } + + psy_parallel_port_free(port); +} diff --git a/psy/hw/inpout32.dll b/psy/hw/inpout32.dll new file mode 100644 index 0000000..8889280 Binary files /dev/null and b/psy/hw/inpout32.dll differ diff --git a/psy/hw/inpoutx64.dll b/psy/hw/inpoutx64.dll new file mode 100644 index 0000000..82c343f Binary files /dev/null and b/psy/hw/inpoutx64.dll differ diff --git a/psy/hw/meson.build b/psy/hw/meson.build index 10d4851..0a8ca99 100644 --- a/psy/hw/meson.build +++ b/psy/hw/meson.build @@ -13,3 +13,9 @@ if cdata.has('HAVE_LINUX_PARPORT_H') and cdata.has('HAVE_LINUX_PPDEV_H') libpsyfiles += files('psy-parport.c') libpsy_headers += files('psy-parport.h') endif + + +if host_machine.system() in ['windows', 'cygwin'] + libpsyfiles += files('psy-inpout-port.c') + libpsy_headers += files('psy-inpout-port.h') +endif \ No newline at end of file diff --git a/psy/hw/psy-inpout-port.c b/psy/hw/psy-inpout-port.c new file mode 100644 index 0000000..99e5443 --- /dev/null +++ b/psy/hw/psy-inpout-port.c @@ -0,0 +1,826 @@ + +#include + +#include "../psy-utils.h" +#include "psy-config.h" +#include "psy-inpout-port.h" +#include "psy-utils.h" + +#ifdef _WIN32 + #define WIN32_LEAN_AND_MEAN + #include + + #include + #include +#endif + +// Helper to link parallel port to the hardware I/O range resources +typedef struct { + BSTR port_name; + BSTR port_pnp_dev_id; +} WmiParportInfo; + +static WmiParportInfo * +wmi_parport_info_new(BSTR port_name, BSTR port_pnp_dev_id) +{ + WmiParportInfo *info = g_new(WmiParportInfo, 1); + info->port_name = port_name; + info->port_pnp_dev_id = port_pnp_dev_id; + return info; +} + +static void +wmi_parport_info_free(WmiParportInfo *self) +{ + SysFreeString(self->port_name); + SysFreeString(self->port_pnp_dev_id); + g_free(self); +} + +typedef struct InpoutPortInfo { + PsyParallelPortInfo *port_info; + guint64 port_address; +} InpoutPortInfo; + +InpoutPortInfo * +inpout_port_info_new(gint n, gchar *name, guint64 port_address) +{ + g_return_val_if_fail(n >= 0 && port_address != 0, NULL); + g_return_val_if_fail(name != NULL, NULL); + + InpoutPortInfo *new = g_new(InpoutPortInfo, 1); + new->port_info = psy_parallel_port_info_new(n, name); + new->port_address = port_address; + + return new; +} + +void +inpout_port_info_free(InpoutPortInfo *self) +{ + g_return_if_fail(self != NULL); + + psy_parallel_port_info_free(self->port_info); + g_free(self); +} + +/* ******* global state concerning loading of inpout dll ******** */ + +static gint g_open_count = 0; + +static HINSTANCE g_inpout_dll = NULL; +static GMutex g_open_mutex; + +/* ******* global state concerning Enumeration of parallel ports ***** */ + +static GRecMutex g_init_mutex; +static gint g_num_infos = 0; +static gint g_init_count = 0; +static PsyParallelPortInfo **g_port_infos = NULL; +static InpoutPortInfo **g_internal_port_infos = NULL; + +// Function should be called with g_init_mutex locked +static void +psy_inpout_port_clear_enum_cache(void) +{ + for (gint i = 0; i < g_num_infos; i++) { + psy_parallel_port_info_free(g_port_infos[i]); + inpout_port_info_free(g_internal_port_infos[i]); + } + g_clear_pointer(&g_port_infos, g_free); + g_clear_pointer(&g_internal_port_infos, g_free); +} + +/* ***** Enumerating parallel ports on windows ****** */ + +// Setup COM and WMI +static void +query_wmi_for_parallel_ports(void); + +// Query all parallel_ports: obtains LPTx and PNP_DeviceID's. +static GPtrArray * +proxy_get_parallel_ports(IWbemServices *proxy); + +static guint64 +proxy_get_port_resources(IWbemServices *proxy, BSTR port_pnp_id); + +static gpointer +enum_ports_thread(gpointer data) +{ + (void) data; + HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + char error_buf[1024]; + if (FAILED(hr)) { + g_critical("Unable to enum parallelports: CoInitialize failed"); + return NULL; + } + + hr = CoInitializeSecurity(NULL, + -1, + NULL, + NULL, + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, + NULL, + EOAC_NONE, + NULL); + if (FAILED(hr)) { + psy_strerr(HRESULT_CODE(hr), error_buf, sizeof(error_buf)); + g_critical( + "Unable to enum parallelports CoInitializeSecurity failed: %s", + error_buf); + } + + query_wmi_for_parallel_ports(); + + return NULL; +} + +static void +query_wmi_for_parallel_ports(void) +{ + IWbemLocator *locator = NULL; + IWbemServices *proxy = NULL; + GPtrArray *ports = NULL; + + BSTR namespace = SysAllocString(L"ROOT\\CIMV2"); + + char error_buf[1024] = {0}; + + HRESULT hr = CoCreateInstance(&CLSID_WbemLocator, + NULL, + CLSCTX_INPROC_SERVER, + &IID_IWbemLocator, + (LPVOID *) &locator); + + if (FAILED(hr)) { + psy_strerr(HRESULT_CODE(hr), error_buf, 1024); + g_critical( + "Unable to enum parallel ports, unable to create WbemLocator: %s", + error_buf); + goto fail; + } + + hr = locator->lpVtbl->ConnectServer( + locator, // self + namespace, // Object path of WMI namespace + NULL, // user: NULL = current user + NULL, // user_pass word, NULL is current password + NULL, // locale 0 current + 0, // security flags + NULL, // Authority e.g. Kerberos + NULL, // Context object + &proxy); // OUTPUT Pointer to IwbemServicesProxy + + if (FAILED(hr)) { + psy_strerr(HRESULT_CODE(hr), error_buf, 1024); + g_critical("Unable to enum parallel ports, unable to create proxy: %s", + error_buf); + goto fail; + } + + hr = CoSetProxyBlanket((IUnknown *) proxy, + RPC_C_AUTHN_WINNT, + RPC_C_AUTHZ_NONE, + NULL, + RPC_C_AUTHN_LEVEL_CALL, + RPC_C_IMP_LEVEL_IMPERSONATE, + NULL, + EOAC_NONE); + + if (FAILED(hr)) { + psy_strerr(HRESULT_CODE(hr), error_buf, 1024); + g_critical( + "Unable to enum parallel ports, unable to set proxy blanket: %s", + error_buf); + goto fail; + } + + ports = proxy_get_parallel_ports(proxy); + if (!ports) { + g_critical("proxy_get_parallel_ports returned NULL"); + goto fail; + } + + GPtrArray *internal_info + = g_ptr_array_new_full(4, (GDestroyNotify) inpout_port_info_free); + GPtrArray *enum_info + = g_ptr_array_new_full(4, (GDestroyNotify) psy_parallel_port_info_free); + + // Add ports to temp cache + for (guint i = 0; i < ports->len && i < G_MAXINT; i++) { + WmiParportInfo *info = g_ptr_array_index(ports, i); + guint64 resource + = proxy_get_port_resources(proxy, info->port_pnp_dev_id); + + gchar *name = g_convert((const gchar *) info->port_name, + SysStringByteLen(info->port_name), + "utf-8", + "utf-16", + NULL, + NULL, + NULL); // freed by port_info. + + InpoutPortInfo *port_info = inpout_port_info_new(i, name, resource); + PsyParallelPortInfo *enum_port_info + = psy_parallel_port_info_copy(port_info->port_info); + g_ptr_array_add(internal_info, port_info); + g_ptr_array_add(enum_info, enum_port_info); + } + + // Add ports to global cache + g_num_infos = enum_info->len; + if (g_num_infos > 0) { + g_port_infos + = (PsyParallelPortInfo **) g_ptr_array_free(enum_info, FALSE); + g_internal_port_infos + = (InpoutPortInfo **) g_ptr_array_free(internal_info, FALSE); + } + +fail: + + SysFreeString(namespace); + + if (ports) { + g_ptr_array_free(ports, TRUE); + } + + if (locator) { + locator->lpVtbl->Release(locator); + } + if (proxy) { + proxy->lpVtbl->Release(proxy); + } +} + +static GPtrArray * +proxy_get_parallel_ports(IWbemServices *proxy) +{ + BSTR wql = SysAllocString(L"WQL"); + BSTR query = SysAllocString(L"select * from Win32_ParallelPort"); + char err_buff[1024] = {0}; + HRESULT hr; + + GPtrArray *ret + = g_ptr_array_new_full(1, (GDestroyNotify) wmi_parport_info_free); + + IEnumWbemClassObject *ports = NULL; + + hr = proxy->lpVtbl->ExecQuery(proxy, + wql, + query, + WBEM_FLAG_FORWARD_ONLY + | WBEM_FLAG_RETURN_IMMEDIATELY, + NULL, + &ports); + if (FAILED(hr)) { + psy_strerr(HRESULT_CODE(hr), err_buff, sizeof(err_buff)); + g_critical("Unable to query Win32_ParallelPort from WMI"); + goto fail; // just return empty ports + } + + while (ports) { + + VARIANT value; + IWbemClassObject *port = NULL; + ULONG result; + + hr = ports->lpVtbl->Next(ports, WBEM_INFINITE, 1, &port, &result); + if (FAILED(hr)) { + psy_strerr(HRESULT_CODE(hr), err_buff, sizeof(err_buff)); + g_critical("Unable to loop over selected parallel ports: %s", + err_buff); + goto fail; // just return allready found ports. + } + + if (result == 0) + break; + + // These 2 BSTRs are added to wmi_parport_info, who will release + // these when the return GPtrArray is destroyed. + hr = port->lpVtbl->Get(port, L"DeviceID", 0, &value, NULL, NULL); + + BSTR device_id = SysAllocString(value.bstrVal); + VariantClear(&value); + + hr = port->lpVtbl->Get(port, L"PNPDeviceID", 0, &value, NULL, NULL); + + BSTR pnp_dev_id = SysAllocString(value.bstrVal); + + WmiParportInfo *info = wmi_parport_info_new(device_id, pnp_dev_id); + + // array will cleanup the bstr's. + g_ptr_array_add(ret, info); + } + + ports->lpVtbl->Release(ports); + +fail: + SysFreeString(wql); + SysFreeString(query); + return ret; +} + +static guint64 +proxy_get_port_resources(IWbemServices *proxy, BSTR pnp_dev_id) +{ + BSTR wql = SysAllocString(L"WQL"); + BSTR query = SysAllocString(L"select * from Win32_PNPAllocatedResource"); + char err_buff[1024] = {0}; + HRESULT hr; + + guint64 ret = 0; + + IEnumWbemClassObject *resources = NULL; + + hr = proxy->lpVtbl->ExecQuery(proxy, + wql, + query, + WBEM_FLAG_FORWARD_ONLY + | WBEM_FLAG_RETURN_IMMEDIATELY, + NULL, + &resources); + if (FAILED(hr)) { + psy_strerr(HRESULT_CODE(hr), err_buff, sizeof(err_buff)); + g_critical("Unable to query Win32_PNPAllocatedResource from WMI"); + goto fail; // just return null + } + + while (resources) { + + VARIANT value; + IWbemClassObject *pnp_resource = NULL; + IWbemClassObject *reference = NULL; + ULONG result; + + hr = resources->lpVtbl->Next( + resources, WBEM_INFINITE, 1, &pnp_resource, &result); + + if (FAILED(hr)) { + psy_strerr(HRESULT_CODE(hr), err_buff, sizeof(err_buff)); + g_critical("Unable to loop over selected Win32_PNPResource's: %s", + err_buff); + goto fail; // just return allready found ports. + } + + if (result == 0) + break; + + // It might be easier to get a reference from an IWBemClassObject + CIMTYPE cim_type; + hr = pnp_resource->lpVtbl->Get( + pnp_resource, L"Dependent", 0, &value, &cim_type, NULL); + + if (cim_type != CIM_REFERENCE) { + g_warning("Expected CIM_REFERENCE"); + VariantClear(&value); + continue; + } + + hr = proxy->lpVtbl->GetObject( + proxy, value.bstrVal, 0, NULL, &reference, NULL); + if (FAILED(hr)) { + g_critical("Unable to get Dependent object"); + } + + VariantClear(&value); + hr = reference->lpVtbl->Get( + reference, L"DeviceID", 0, &value, &cim_type, NULL); + + if (wcscmp(value.bstrVal, pnp_dev_id) != 0) { + // This is another device, ignore it + VariantClear(&value); + continue; + } + + // We've found the right port resource + reference->lpVtbl->Release(reference); + reference = NULL; + VariantClear(&value); + + hr = pnp_resource->lpVtbl->Get( + pnp_resource, L"Antecedent", 0, &value, &cim_type, NULL); + + g_assert(cim_type == CIM_REFERENCE); + + hr = proxy->lpVtbl->GetObject( + proxy, value.bstrVal, 0, NULL, &reference, NULL); + + VariantClear(&value); + + hr = reference->lpVtbl->Get( + reference, L"StartingAddress", 0, &value, &cim_type, NULL); + if (FAILED(hr)) { + g_critical("Failed to find StartingAddress"); + } + g_assert(cim_type == CIM_UINT64); + + // Although it is a UINT64, it's still encoded as bstr in the value, + // hence we need convert it to a number. + + GError *error = NULL; + + char *start_addr_utf8 = g_convert((const gchar *) value.bstrVal, + SysStringByteLen(value.bstrVal), + "UTF-8", + "UTF-16", + NULL, + NULL, + &error); + if (error) { + g_critical("Unable to convert from BSTR to utf8: %s", + error->message); + g_clear_error(&error); + } + + if (start_addr_utf8) { + ret = g_ascii_strtoull(start_addr_utf8, NULL, 10); + g_free(start_addr_utf8); + } + VariantClear(&value); + reference->lpVtbl->Release(reference); + + break; + } + + resources->lpVtbl->Release(resources); + +fail: + SysFreeString(wql); + SysFreeString(query); + return ret; +} + +static void +psy_parallel_enumeration_procedure(PsyParallelPort *self, + PsyParallelPortInfo ***result, + gint *num) +{ + (void) self; + + // Create thread for data, as COM must be initialized for each thread. + // And if we do it this way, it doesn't matter how the client does + // it. + GThread *t = g_thread_new("enum-parallel-ports", enum_ports_thread, NULL); + g_thread_join(t); + + if (g_port_infos) { + *result = g_port_infos; + *num = g_num_infos; + } +} + +/* ******* loading of inpout dll for controlling SPP registers ******** */ + +typedef void(__stdcall *lpOut32)(short, short); +typedef short(__stdcall *lpInp32)(short); +typedef BOOL(__stdcall *lpIsInpOutDriverOpen)(void); + +lpOut32 spp_write; +lpInp32 spp_read; +lpIsInpOutDriverOpen opened_dll; + +#if PSY_TARGET_ARCH_X86 +const gchar *g_dll_name = "inpout32.dll"; +#elif PSY_TARGET_ARCH_X86_64 +const gchar *g_dll_name = "inpoutx64.dll"; +#else + #error "Unsupported platform" +#endif + +static gboolean +init_win_parallel_port(GError **error) +{ + g_mutex_lock(&g_open_mutex); + + g_open_count++; + + if (g_open_count == 1) { + g_inpout_dll = LoadLibraryA(g_dll_name); + + if (g_inpout_dll == NULL) { + char buff[BUFSIZ]; + gint error_code = GetLastError(); + psy_strerr(error_code, buff, BUFSIZ); + + g_set_error(error, + PSY_PARALLEL_PORT_ERROR, + PSY_PARALLEL_PORT_ERROR_OPEN, + "Unable to load library \"%s\": %s", + g_dll_name, + buff); + goto error; + } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" + spp_write = (lpOut32) GetProcAddress(g_inpout_dll, "Out32"); + spp_read = (lpInp32) GetProcAddress(g_inpout_dll, "Inp32"); + opened_dll = (lpIsInpOutDriverOpen) GetProcAddress( + g_inpout_dll, "IsInpOutDriverOpen"); +#pragma GCC diagnostic pop + + g_assert(spp_write != NULL && spp_read != 0 && opened_dll != NULL); + + if (opened_dll == NULL || !opened_dll()) + g_set_error(error, + PSY_PARALLEL_PORT_ERROR, + PSY_PARALLEL_PORT_ERROR_OPEN, + "Loaded library, but dll isn't open"); + } + + g_mutex_unlock(&g_open_mutex); + return TRUE; + +error: + + g_open_count--; + + g_mutex_unlock(&g_open_mutex); + return FALSE; +} + +static gboolean +deinit_win_parallel_port(void) +{ + g_mutex_lock(&g_open_mutex); + + g_open_count--; + if (g_open_count < 0) { + g_critical("open count less than zero"); + } + + if (g_open_count == 0) { + FreeLibrary(g_inpout_dll); + g_inpout_dll = NULL; + + spp_write = NULL; + spp_read = NULL; + opened_dll = NULL; + } + + g_mutex_unlock(&g_open_mutex); + return TRUE; +} + +/** + * PsyInpoutPort: + * + * PsyInpoutPort is a final class for parallel ports on Windows. It derives from + * PsyParallelPort and implements it. Typically you can instantiate instances + * of this class with [ctor@ParallelPort.new] and that constructor + * determines the right backend for your system. + * + * The device is opened by a number, the numbers relate to addresses. + * The following table applies: + * - *0* = 0x378 + * - *1* = 0x278 + * - *2* = 0x3BC + * + * If you want to know which port it is use the windows device manager + * to see what port maps to the address above. The ports are known as + * LPTx where the x is 1, 2 or 3. To keep numbering consistent, psylib uses + * 0, 1, 2. + * + * In order to use PsyInpoutPort, you'll need inpout.dll. PsyLib tries to + * ship with this dll. You'll can find it online (November 2024) at: + * + * https://www.highrez.co.uk/Downloads/InpOut32/ + * + * this is a website from Phillip Gibbons. Back when I was young, one + * was able to write to the registers of a parallel port. This way you + * could bit-bang transmission between a pc and a printer. + * We (ab)use this in order to trigger interfaces. The dll makes this + * possible PsyLib does sanitize the addresses one can write to. + * + * Because, on windows were just writing to the registers, we cannot tell + * whether there actually is connected on that address, hence look up your + * device address in windows device manager. If you have multiple devices, + * they will appear on different addresses and you'll have to determine + * which address belongs to which port, before using the device. + */ + +struct LptAddressMap { + gchar *port_name; // Name of the port e.g. LPT1 + gint port_number; // for port LPTx port_number is x - 1. + gint port_address; // Address of the data_register +}; + +typedef struct _PsyInpoutPort { + PsyParallelPort parent; + gint16 data_register; + gint16 status_register; + gint16 control_register; +} PsyInpoutPort; + +G_DEFINE_TYPE(PsyInpoutPort, psy_inpout_port, PSY_TYPE_PARALLEL_PORT) + +static void +psy_inpout_port_init(PsyInpoutPort *self) +{ + (void) self; + g_rec_mutex_lock(&g_init_mutex); + + if (++g_init_count == 1) { + psy_parallel_port_enumerate( + PSY_PARALLEL_PORT(self), &g_port_infos, &g_num_infos); + } + + g_rec_mutex_unlock(&g_init_mutex); +} + +static void +inpout_port_dispose(GObject *obj) +{ + g_rec_mutex_lock(&g_init_mutex); + + if (--g_init_count == 0) { + psy_inpout_port_clear_enum_cache(); + } + + g_rec_mutex_unlock(&g_init_mutex); + + G_OBJECT_CLASS(psy_inpout_port_parent_class)->dispose(obj); +} + +static void +inpout_port_open(PsyParallelPort *self, gint port_num, GError **error) +{ + gchar buffer[64]; + PsyInpoutPort *pp = PSY_INPOUT_PORT(self); + PsyParallelPortInfo **ports = NULL; + gint num_ports = 0; + + PsyParallelPortClass *parallel_cls = PSY_PARALLEL_PORT_GET_CLASS(self); + + psy_parallel_port_enumerate(self, &ports, &num_ports); + if (port_num >= num_ports) { + g_set_error(error, + PSY_PARALLEL_PORT_ERROR, + PSY_PARALLEL_PORT_ERROR_OPEN, + "No device enumerated with port_num: %d", + port_num); + return; + } + + PsyParallelPortInfo *info = g_port_infos[port_num]; + InpoutPortInfo *port_reg_info + = g_internal_port_infos[psy_parallel_port_info_port_number(info)]; + + // assign port numbers + pp->data_register = port_reg_info->port_address; + pp->status_register = pp->data_register + 1; + pp->control_register = pp->data_register + 2; + + psy_parallel_port_close(self); + + init_win_parallel_port(error); + + if (*error != NULL) + return; + + PSY_PARALLEL_PORT_CLASS(psy_inpout_port_parent_class) + ->open(self, port_num, error); + + // Make sure every dataline is low. + psy_parallel_port_write(PSY_PARALLEL_PORT(self), 0, error); + if (*error != 0) + return; + + g_snprintf(buffer, sizeof(buffer), "0x%04X", pp->data_register); + parallel_cls->set_port_name(self, buffer); +} + +static void +inpout_port_close(PsyParallelPort *self) +{ + // close inpout.dll on close of last input/output device + // clear enumeration info + deinit_win_parallel_port(); + + PSY_PARALLEL_PORT_CLASS(psy_inpout_port_parent_class)->close(self); +} + +static void +inpout_port_write(PsyParallelPort *self, guint8 pins, GError **error) +{ + // TODO put duplicate code in psy-parallel-port.c + PsyInpoutPort *pp = PSY_INPOUT_PORT(self); + gboolean is_open = psy_parallel_port_is_open(self); + gboolean is_output + = psy_parallel_port_get_direction(self) == PSY_IO_DIRECTION_OUT; + + if (!is_open) { + g_set_error(error, + PSY_PARALLEL_PORT_ERROR, + PSY_PARALLEL_PORT_ERROR_DEV_CLOSED, + "Can't write to closed device"); + return; + } + + if (!is_output) { + g_set_error( + error, + PSY_PARALLEL_PORT_ERROR, + PSY_PARALLEL_PORT_ERROR_DIRECTION, + "Unable to write to a port that is not configured for output."); + } + + spp_write(pp->data_register, pins); + + psy_parallel_port_set_pins(self, pins); +} + +static void +inpout_port_write_pin(PsyParallelPort *self, + gint pin, + PsyIoLevel level, + GError **error) +{ + guint8 current = psy_parallel_port_get_pins(self); + guint8 final; + if (level == PSY_IO_LEVEL_HIGH) { + final = current | 1ul << pin; + } + else { + final = current & ~(1ul << pin); + } + + psy_parallel_port_write(self, final, error); +} + +static guint8 +inpout_port_read(PsyParallelPort *self, GError **error) +{ + PsyInpoutPort *pp = PSY_INPOUT_PORT(self); + gboolean is_open = psy_parallel_port_is_open(self); + gboolean is_input + = psy_parallel_port_get_direction(self) == PSY_IO_DIRECTION_IN; + guint8 lines = 0; + + if (!is_open) { + g_set_error(error, + PSY_PARALLEL_PORT_ERROR, + PSY_PARALLEL_PORT_ERROR_DEV_CLOSED, + "Can't read from closed device"); + return 0; + } + + if (!is_input) { + g_set_error( + error, + PSY_PARALLEL_PORT_ERROR, + PSY_PARALLEL_PORT_ERROR_DIRECTION, + "Unable to read from a port that is not configured as input."); + } + + lines = spp_read(pp->data_register); + + psy_parallel_port_set_pins(self, lines); + return lines; +} + +static PsyIoLevel +inpout_port_read_pin(PsyParallelPort *self, gint pin, GError **error) +{ + guint8 pins = psy_parallel_port_read(self, error); + if (error && (*error != NULL)) { + return PSY_IO_LEVEL_LOW; + } + + return pins & (1ul << pin) ? PSY_IO_LEVEL_HIGH : PSY_IO_LEVEL_LOW; +} + +static void +inpout_port_enumerate(PsyParallelPort *self, + PsyParallelPortInfo ***result, + gint *num) +{ + g_rec_mutex_lock(&g_init_mutex); + + if (g_port_infos != NULL) { + *result = g_port_infos; + *num = g_num_infos; + } + else { + psy_parallel_enumeration_procedure(self, result, num); + } + + g_rec_mutex_unlock(&g_init_mutex); +} + +static void +psy_inpout_port_class_init(PsyInpoutPortClass *cls) +{ + GObjectClass *obj_cls = G_OBJECT_CLASS(cls); + obj_cls->dispose = inpout_port_dispose; + + PsyParallelPortClass *parallel_cls = PSY_PARALLEL_PORT_CLASS(cls); + + parallel_cls->open = inpout_port_open; + parallel_cls->close = inpout_port_close; + parallel_cls->write = inpout_port_write; + parallel_cls->write_pin = inpout_port_write_pin; + parallel_cls->read = inpout_port_read; + parallel_cls->read_pin = inpout_port_read_pin; + parallel_cls->enumerate = inpout_port_enumerate; +} diff --git a/psy/hw/psy-inpout-port.h b/psy/hw/psy-inpout-port.h new file mode 100644 index 0000000..08019e7 --- /dev/null +++ b/psy/hw/psy-inpout-port.h @@ -0,0 +1,14 @@ + +#pragma once + +#include "psy-parallel-port.h" + +G_BEGIN_DECLS + +#define PSY_TYPE_INPOUT_PORT psy_inpout_port_get_type() + +G_MODULE_EXPORT +G_DECLARE_FINAL_TYPE( + PsyInpoutPort, psy_inpout_port, PSY, INPOUT_PORT, PsyParallelPort) + +G_END_DECLS diff --git a/psy/hw/psy-parallel-port.c b/psy/hw/psy-parallel-port.c index ee51eb7..14f54a7 100644 --- a/psy/hw/psy-parallel-port.c +++ b/psy/hw/psy-parallel-port.c @@ -5,12 +5,112 @@ #if defined(HAVE_LINUX_PARPORT_H) #include "psy-parport.h" #endif +#if defined(_WIN32) + #include "psy-inpout-port.h" +#endif + +struct PsyParallelPortInfo { + gint port_number; + gchar *port_name; +}; + +/** + * psy_parallel_port_info_new:(skip)(constructor) + * @port_number: the number to use to open this parallel port + * @name:(transfer full): the name it has on the current OS. + * + * Creates a new parallel port info. + * + * Stability: private + */ +PsyParallelPortInfo * +psy_parallel_port_info_new(gint port_number, gchar *name) +{ + PsyParallelPortInfo *new = g_new(PsyParallelPortInfo, 1); + + new->port_name = name; + new->port_number = port_number; + return new; +} + +/** + * psy_parallel_port_info_copy: + * @self: an instance of [struct@ParallelPortInfo] to copy + * + * Copies the parallel port info passed to this function. + * + * Returns:(transfer full): a copy of self. + */ +PsyParallelPortInfo * +psy_parallel_port_info_copy(PsyParallelPortInfo *self) +{ + PsyParallelPortInfo *new = g_new(PsyParallelPortInfo, 1); + + new->port_name = g_strdup(self->port_name); + new->port_number = self->port_number; + + return new; +} + +/** + * psy_parallel_port_info_free:(skip) + * @self: the parameter to free + * + * Frees the instance of [class@ParallelPortInfo]. + */ +void +psy_parallel_port_info_free(PsyParallelPortInfo *self) +{ + g_free(self->port_name); + g_free(self); +} + +/** + * psy_parallel_port_info_name: + * @self: an instance of [struct@ParallelPortInfo]. + * + * Obtain the name of the parallel port e.g. LPT1 or windows + * or /dev/parport0 for the first available parallel port. + */ +const gchar * +psy_parallel_port_info_name(PsyParallelPortInfo *self) +{ + g_return_val_if_fail(self != NULL, NULL); + return self->port_name; +} + +/** + * psy_parallel_port_info_number: + * @self: an instance of [struct@ParallelPortInfo]. + * + * Obtain the number of the parallelport. You should be able to + * use this number in order to open an instance of [class@ParallelPort]. + * + * Notice that on windows this number is one less than the number of the + * parallel port so if LPT1 is available, the number here would be 0. + * + * Returns: the number to pass to [func@ParallelPort.open] in order to + * open a parallel port. + */ +gint +psy_parallel_port_info_port_number(PsyParallelPortInfo *self) +{ + g_return_val_if_fail(self != NULL, -1); + return self->port_number; +} // clang-format off G_DEFINE_QUARK(psy-parallel-port-error-quark, psy_parallel_port_error) - // clang-format on +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" +G_DEFINE_BOXED_TYPE(PsyParallelPortInfo, + psy_parallel_port_info, + psy_parallel_port_info_copy, + psy_parallel_port_info_free) +#pragma GCC diagnostic pop + /** * PsyParallelPort: * @@ -20,10 +120,14 @@ G_DEFINE_QUARK(psy-parallel-port-error-quark, psy_parallel_port_error) * class derived from this class. * This class does provide the full API of communicating with a parallel port * ParallelPorts in psylib are identified by there number, the id 0 might be - * mapped to "/dev/parport0/" on linux but "LPT1" on windows. + * mapped to "/dev/parport0/" on linux but "0x378" on windows. * * TODO Most of these function work synchronous, hence, a class * needs to be designed that can read, write, open, close in an async fashion. + * + * PsyParallel is implemented fully by the classes PsyParport (Linux) and + * PsyInpoutPort (windows). Using [ctor@PsyParrallelPort.new], you'll get + * the device that is appropriate on your os, or NULL when not available. */ typedef struct { @@ -38,7 +142,7 @@ G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(PsyParallelPort, G_TYPE_OBJECT) typedef enum PsyParallelPortProperty { - PROP_NULL, + GPROP_NULL, PORT_NUM, PORT_NAME, PORT_DIRECTION, @@ -207,7 +311,7 @@ psy_parallel_port_class_init(PsyParallelPortClass *cls) * PsyParallelPort:port-name: * * This is the name of the device at the os level, at linux it might be - * "/dev/parport0" and at windows "LPT1". It should be set when the + * "/dev/parport0" and at windows "0x378". It should be set when the * device is open and should result in an empty string otherwise. */ port_properties[PORT_NAME] = g_param_spec_string( @@ -307,9 +411,9 @@ psy_parallel_port_new(void) { PsyParallelPort *port = NULL; #if defined(HAVE_LINUX_PARPORT_H) - port = g_object_new(PSY_TYPE_PARPORT, NULL); - +#elif defined(_WIN32) + port = g_object_new(PSY_TYPE_INPOUT_PORT, NULL); #else #pragma message "No instance for a parallel port" #endif @@ -362,6 +466,11 @@ void psy_parallel_port_close(PsyParallelPort *self) { g_return_if_fail(PSY_IS_PARALLEL_PORT(self)); + PsyParallelPortPrivate *priv = psy_parallel_port_get_instance_private(self); + + if (priv->port_num < 0) { + return; + } PsyParallelPortClass *klass = PSY_PARALLEL_PORT_GET_CLASS(self); g_return_if_fail(klass->open != NULL); @@ -633,3 +742,20 @@ psy_parallel_port_set_pins(PsyParallelPort *self, guint8 pins) priv->pins = pins; } + +void +psy_parallel_port_enumerate(PsyParallelPort *self, + PsyParallelPortInfo ***result, + gint *num) +{ + PsyParallelPortClass *cls; + g_return_if_fail(PSY_IS_PARALLEL_PORT(self)); + g_return_if_fail(result != NULL && *result == NULL); + g_return_if_fail(num != NULL); + + cls = PSY_PARALLEL_PORT_GET_CLASS(self); + + g_return_if_fail(cls->enumerate != NULL); + + cls->enumerate(self, result, num); +} diff --git a/psy/hw/psy-parallel-port.h b/psy/hw/psy-parallel-port.h index 10893c1..078dbe7 100644 --- a/psy/hw/psy-parallel-port.h +++ b/psy/hw/psy-parallel-port.h @@ -8,6 +8,34 @@ G_BEGIN_DECLS +/** + * PsyParallelPortInfo: + * + * A structure that holds information about port that exist on the + * current pc it is running on. + */ +typedef struct PsyParallelPortInfo PsyParallelPortInfo; + +G_MODULE_EXPORT GType +psy_parallel_port_info_get_type(); + +PsyParallelPortInfo * +psy_parallel_port_info_new(gint port_number, gchar *name); + +void +psy_parallel_port_info_free(PsyParallelPortInfo *self); + +G_MODULE_EXPORT const gchar * +psy_parallel_port_info_name(PsyParallelPortInfo *self); + +G_MODULE_EXPORT gint +psy_parallel_port_info_port_number(PsyParallelPortInfo *self); + +G_MODULE_EXPORT PsyParallelPortInfo * +psy_parallel_port_info_copy(PsyParallelPortInfo *self); + +#define PSY_TYPE_PARALLEL_PORT_INFO psy_parallel_port_info_get_type() + #define PSY_TYPE_PARALLEL_PORT psy_parallel_port_get_type() G_MODULE_EXPORT @@ -37,6 +65,7 @@ psy_parallel_port_error_quark(void); * sure that you can obtain the mask of the pins on the ParallelPort. * @read_pin: This should be implemented in the deriving class. This * function reads whether the signal is high or low. + * @enumerate: This function enumerates the devices that are available. */ typedef struct _PsyParallelPortClass { GObjectClass parent_class; @@ -55,6 +84,10 @@ typedef struct _PsyParallelPortClass { guint8 (*read)(PsyParallelPort *self, GError **error); PsyIoLevel (*read_pin)(PsyParallelPort *self, gint pin, GError **error); + void (*enumerate)(PsyParallelPort *self, + PsyParallelPortInfo ***result, + gint *num); + gpointer padding[8]; } PsyParallelPortClass; @@ -110,6 +143,11 @@ psy_parallel_port_read_pin(PsyParallelPort *self, gint pin, GError **error); G_MODULE_EXPORT guint8 psy_parallel_port_get_pins(PsyParallelPort *self); +G_MODULE_EXPORT void +psy_parallel_port_enumerate(PsyParallelPort *self, + PsyParallelPortInfo ***result, + gint *num); + // private void diff --git a/psy/hw/psy-parallel-trigger.c b/psy/hw/psy-parallel-trigger.c index 6c927ce..9611ac9 100644 --- a/psy/hw/psy-parallel-trigger.c +++ b/psy/hw/psy-parallel-trigger.c @@ -453,7 +453,7 @@ write_thread(GTask *task, end: - g_object_unref(end); + psy_time_point_free(end); } /** @@ -528,8 +528,8 @@ trigger_finished_cb(GObject *obj, GAsyncResult *result, gpointer data) g_error_free(error); } - g_object_unref(tfinish); - g_object_unref(tstart); + psy_time_point_free(tfinish); + psy_time_point_free(tstart); } /** diff --git a/psy/hw/psy-parport.c b/psy/hw/psy-parport.c index ee7d398..1166de9 100644 --- a/psy/hw/psy-parport.c +++ b/psy/hw/psy-parport.c @@ -1,5 +1,8 @@ #include "psy-parport.h" +#include "glibconfig.h" +#include "psy-enums.h" +#include "psy-parallel-port.h" #include #include @@ -9,6 +12,14 @@ #include #include +// Cache for PsyParallelPortInfo + +PsyParallelPortInfo **g_infos = NULL; +gint g_num_infos = 0; +gint g_init_count = 0; + +GRecMutex g_mutex; + /** * PsyParport: * @@ -28,7 +39,13 @@ G_DEFINE_TYPE(PsyParport, psy_parport, PSY_TYPE_PARALLEL_PORT) static void psy_parport_init(PsyParport *self) { - (void) self; + g_rec_mutex_lock(&g_mutex); + g_init_count++; + if (g_init_count == 1) { + psy_parallel_port_enumerate( + PSY_PARALLEL_PORT(self), &g_infos, &g_num_infos); + } + g_rec_mutex_unlock(&g_mutex); } static void @@ -217,6 +234,117 @@ parport_read_pin(PsyParallelPort *self, gint pin, GError **error) return pins & (1ul << pin) ? PSY_IO_LEVEL_HIGH : PSY_IO_LEVEL_LOW; } +static PsyParallelPortInfo * +parport_create_enumerated_port_info(const gchar *dev_name) +{ + char buffer[1024] = ""; + const char *prefix = "parport"; + gint64 number; + + if (strncmp(prefix, dev_name, strlen(prefix)) != 0) + return NULL; + + const gchar *start_number = &dev_name[strlen(prefix)]; + gchar *end_number = NULL; + + number = g_ascii_strtoll(start_number, &end_number, 10); + if (end_number == NULL) + return NULL; + + if (number < 0 || number > G_MAXINT) + return NULL; + + g_snprintf(buffer, sizeof(buffer), "/dev/parport%ld", number); + return psy_parallel_port_info_new((gint) number, g_strdup(buffer)); +} + +static void +parport_enumerate(PsyParallelPort *self, + PsyParallelPortInfo ***result, + gint *num) +{ + (void) self; + GError *error = NULL; + GFile *dev_folder = NULL; + GPtrArray *temp_infos = NULL; + + g_rec_mutex_lock(&g_mutex); + + if (g_infos != NULL) { + *result = g_infos; + *num = g_num_infos; + + g_rec_mutex_unlock(&g_mutex); + return; + } + + temp_infos = g_ptr_array_new(); + + dev_folder = g_file_new_build_filename("/", "dev", NULL); + + // put the outcome to valid values in case of error. + *num = 0; + *result = NULL; + + GFileEnumerator *enumerator + = g_file_enumerate_children(dev_folder, + G_FILE_ATTRIBUTE_STANDARD_NAME, + G_FILE_QUERY_INFO_NONE, + NULL, + &error); + if (error) { + g_critical("Unable to enumerate parallel port devices unable to create " + "file enumerator: %s", + error->message); + g_clear_error(&error); + if (enumerator) + g_object_unref(enumerator); + g_object_unref(dev_folder); + g_ptr_array_free(temp_infos, TRUE); + g_rec_mutex_unlock(&g_mutex); + + *result = NULL; + *num = 0; + + return; + } + + while (1) { + GFileInfo *info = NULL; + const gchar *name; + + if (!g_file_enumerator_iterate(enumerator, &info, NULL, NULL, &error)) + break; + if (!info) + break; + + name = g_file_info_get_name(info); + + PsyParallelPortInfo *port_info + = parport_create_enumerated_port_info(name); + if (port_info) { + g_ptr_array_add(temp_infos, port_info); + g_num_infos++; + } + if (g_num_infos > 1024) // I would consider more than 2 unlikely. + break; + } + + if (error) { + g_critical("Error while iterating devices: %s", error->message); + g_object_unref(enumerator); + g_rec_mutex_unlock(&g_mutex); + return; + } + + g_infos = (PsyParallelPortInfo **) g_ptr_array_free(temp_infos, FALSE); + + g_object_unref(enumerator); + g_object_unref(dev_folder); + + g_rec_mutex_unlock(&g_mutex); +} + static void psy_parport_class_init(PsyParportClass *cls) { @@ -231,4 +359,5 @@ psy_parport_class_init(PsyParportClass *cls) parallel_cls->write_pin = parport_write_pin; parallel_cls->read = parport_read; parallel_cls->read_pin = parport_read_pin; + parallel_cls->enumerate = parport_enumerate; } diff --git a/psy/meson.build b/psy/meson.build index a9117cd..da522ea 100644 --- a/psy/meson.build +++ b/psy/meson.build @@ -10,6 +10,13 @@ cdata.set_quoted('PSY_VERSION', meson.project_version()) cdata.set_quoted('PSY_SOURCE_ROOT', meson.project_source_root()) cdata.set_quoted('PSY_BUILD_ROOT', meson.project_build_root()) +if host_machine.cpu_family() == 'x86' + cdata.set('PSY_TARGET_ARCH_X86', 1) +elif host_machine.cpu_family() == 'x86_64' + cdata.set('PSY_TARGET_ARCH_X86_64', 1) +else + warning('Unanticipated cpu architecture') +endif # the folder where we are building psylib, hence the .so and .gir # are stored here @@ -316,4 +323,15 @@ if get_option('introspection') typelib_target = gir[1] endif - +# Copy dll for parallel port on windows to build dir +# this isn't in hw because, then the file is copied to +# meson.current_build_dir()/psy/hw, which isn't on the path and +# meson.current_build_dir()/psy is. +if host_machine.system() in ['windows', 'cygwin'] + cpu_family = host_machine.cpu_family() + if cpu_family == 'x86' + fs.copyfile('hw/inpout32.dll', 'inpout32.dll', install: true, install_dir: 'psy') + elif cpu_family == 'x86_64' + fs.copyfile('hw/inpoutx64.dll', 'inpoutx64.dll',install: true, install_dir : 'psy') + endif +endif diff --git a/psy/psy-config.h.in b/psy/psy-config.h.in index b7f853c..6432098 100644 --- a/psy/psy-config.h.in +++ b/psy/psy-config.h.in @@ -7,6 +7,9 @@ #mesondefine PSY_BUILD_ROOT #mesondefine PSY_PROJECT_NAME +#mesondefine PSY_TARGET_ARCH_X86 +#mesondefine PSY_TARGET_ARCH_X86_64 + // functions (or build in compiler additions) #mesondefine HAVE_BUILTIN_ADD_OVERFLOW #mesondefine HAVE_BUILTIN_SUB_OVERFLOW diff --git a/psy/psy-init.c b/psy/psy-init.c index e51021e..e7c94da 100644 --- a/psy/psy-init.c +++ b/psy/psy-init.c @@ -10,9 +10,23 @@ #include #endif +#ifdef WIN32 + #define WIN32_LEAN_AND_MEAN + #include + + #include +#endif + static gint init_count; static GMutex init_mutex; +// For when initializing using psy_init() and deinit() +static gint func_init_count; +static GMutex func_init_mutex; + +// Is used by psy_init() and -deinit() +static PsyInitializer *g_initializer; + typedef struct _PsyInitializer { GObject parent; #ifdef HAVE_GSTREAMER @@ -60,7 +74,14 @@ initializer_constructed(GObject *obj) g_info("Initializer::Initializing psylib count: %d", init_count); if (init_count == 1) { - +#ifdef WIN32 + // Increases accuracy of Sleep() to roughly 1ms instead of > 10ms. + if (timeBeginPeriod(1) != TIMERR_NOERROR) { + g_critical( + "Unable to improve the accuracy of the windows Sleep function, " + "timers may be inaccurate."); + } +#endif // stuff we always init timer_private_start_timer_thread(); @@ -95,8 +116,6 @@ initializer_finalize(GObject *obj) g_info("Initializer::Deinitializing psylib count: %d", init_count); if (init_count == 0) { - // stuff we always deinit - timer_private_stop_timer_thread(); // specific libs if (self->gstreamer) { @@ -109,6 +128,12 @@ initializer_finalize(GObject *obj) if (self->portaudio) { Pa_Terminate(); } + // stuff we always deinit + timer_private_stop_timer_thread(); + +#if WIN32 + timeEndPeriod(1); +#endif } else if (init_count <= 0) { g_warning("Deinitialized psylib more often than initialized."); @@ -230,28 +255,35 @@ psy_initializer_class_init(PsyInitializerClass *klass) obj_class, NUM_PROPS, initializer_properties); } -static void -initialize_psylib(void) +/** + * psy_initializer_new:(constructor) + * + * Returns an object, that initializes psylib, and all of its dependencies. + * You can use g_object_new(), yourself, inorder to set a number of properties, + * using the properties you can avoid loading of some dependencies. This + * typically is not recommended, however, it is possible if you know what + * librayies you really need. + * + * Returns:(transfer full): an instance of [class@Initializer], you should + * free this either with g_object_unref or [method@Psy.Initializer.free]. + * in many of the bindings, free will not be necessary. As the bindings + * will do this on your behalf. + */ +PsyInitializer * +psy_initializer_new(void) { - timer_private_start_timer_thread(); -#ifdef HAVE_GSTREAMER - gst_init(NULL, NULL); -#endif -#ifdef HAVE_PORTAUDIO - Pa_Initialize(); -#endif + return g_object_new(PSY_TYPE_INITIALIZER, NULL); } -static void -deinitialize_psylib(void) +/** + * psy_initializer_free:(skip) + * + * Destroys, the initializer and thereby undos the initialization of psylib. + */ +void +psy_initializer_free(PsyInitializer *self) { -#ifdef HAVE_PORTAUDIO - Pa_Terminate(); -#endif -#ifdef HAVE_GSTREAMER - gst_deinit(); -#endif - timer_private_stop_timer_thread(); + g_object_unref(self); } /** @@ -264,29 +296,28 @@ deinitialize_psylib(void) void psy_init(void) { - g_mutex_lock(&init_mutex); + g_mutex_lock(&func_init_mutex); - init_count++; + func_init_count++; - if (init_count == 1) { - g_info("Initializing psylib"); - initialize_psylib(); + if (func_init_count == 1) { + g_initializer = psy_initializer_new(); } - g_mutex_unlock(&init_mutex); + g_mutex_unlock(&func_init_mutex); } void psy_deinit(void) { - g_mutex_lock(&init_mutex); - init_count--; - if (init_count == 0) { - deinitialize_psylib(); + g_mutex_lock(&func_init_mutex); + func_init_count--; + if (func_init_count == 0) { + g_clear_object(&g_initializer); } - else if (init_count < 0) { + else if (func_init_count < 0) { g_warning("psylib: init_count = %d", init_count); } - g_mutex_unlock(&init_mutex); + g_mutex_unlock(&func_init_mutex); } diff --git a/psy/psy-timer-private.c b/psy/psy-timer-private.c index fa8bf46..3fe7108 100644 --- a/psy/psy-timer-private.c +++ b/psy/psy-timer-private.c @@ -111,6 +111,9 @@ psy_timer_thread_init(PsyTimerThread *self) self->thread = g_thread_new("TimerThread", timer_thread, self); #ifdef _WIN32 + // TODO This is now always called when initializing psylib at windows, + // hence we need to check if this isn't redundant. + // // On windows a Sleep(1) should sleep for 1 millisecond. In practice, this // can take a bit longer due to OS scheduling, the 1 ms is a minimal amount. // The scheduler might finish the current "quantum" for this process. Which diff --git a/psy/psy-utils.c b/psy/psy-utils.c index 41e0793..e50a84b 100644 --- a/psy/psy-utils.c +++ b/psy/psy-utils.c @@ -1,6 +1,11 @@ #include "psy-utils.h" +#ifdef _WIN32 + #define WIN32_LEAN_AND_MEAN + #include +#endif + /** * psy_coordinate_center_to_c: * @width: the width of the surface @@ -210,3 +215,19 @@ psy_coordinate_c_to_center_i( *x_out = x_in - width / 2; *y_out = -y_in + height / 2; } + +void +psy_strerr(int system_error_num, char *result, gsize result_size) +{ +#ifndef _WIN32 + strerror_r(system_error_num, result, result_size); +#else + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + 0, + system_error_num, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + result, + result_size, + NULL); +#endif +} diff --git a/psy/psy-utils.h b/psy/psy-utils.h index e88deab..ba51371 100644 --- a/psy/psy-utils.h +++ b/psy/psy-utils.h @@ -29,4 +29,7 @@ G_MODULE_EXPORT void psy_coordinate_center_to_c_i( gint width, gint height, gint x_in, gint y_in, gint *x_out, gint *y_out); +void +psy_strerr(int system_error_num, char *result, gsize result_size); + G_END_DECLS diff --git a/tests/main.c b/tests/main.c index 98c98c7..3536e70 100644 --- a/tests/main.c +++ b/tests/main.c @@ -115,11 +115,11 @@ add_suites_to_registry(void) if (error) return error; -#if !defined(_WIN32) error = add_parallel_suite(g_port_num); if (error) return error; +#if !defined(_WIN32) error = add_picture_suite(); if (error) return error; @@ -253,12 +253,8 @@ main(int argc, char **argv) } } - g_object_new( - PSY_TYPE_INITIALIZER, - "gstreamer", g_audio, - "portaudio", g_audio, - NULL); + PSY_TYPE_INITIALIZER, "gstreamer", g_audio, "portaudio", g_audio, NULL); set_save_images(g_save_images ? TRUE : FALSE); diff --git a/tests/parallel_trigger.c b/tests/parallel_trigger.c index 472a568..05c7501 100644 --- a/tests/parallel_trigger.c +++ b/tests/parallel_trigger.c @@ -1,6 +1,5 @@ -#include -#include +#include const gchar *g_option_str = "This is a small program to test triggers with a " "PsyParallelTriggerDevice"; @@ -44,13 +43,14 @@ finished(PsyParallelTrigger *trigger, } psy_duration_free(dur); - g_object_unref(newtp); + psy_time_point_free(newtp); } int main(int argc, char **argv) { - GError *error = NULL; + GError *error = NULL; + PsyInitializer *init = psy_initializer_new(); GOptionContext *option_context = g_option_context_new(g_option_str); g_option_context_add_main_entries(option_context, entries, NULL); @@ -121,4 +121,6 @@ main(int argc, char **argv) g_main_context_pop_thread_default(context); g_main_context_unref(context); + + psy_initializer_free(init); } diff --git a/tests/parallel_write.c b/tests/parallel_write.c index d3e6cf8..df79cfc 100644 --- a/tests/parallel_write.c +++ b/tests/parallel_write.c @@ -2,30 +2,39 @@ #include #ifdef _WIN32 -#include + #include #endif #include -void test_sleep(int ms) { -#ifndef _WIN32 - usleep(ms * 1000); -#else - Sleep(ms); -#endif -} +int g_port_num = 0; + +// clang-format off +GOptionEntry entries[] = { + {"port-num", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_INT, &g_port_num, "Specify a port num [0,1,2]", NULL}, + {0}, +}; + +// clang-format on int main(int argc, char **argv) { - (void) argc; - (void) argv; + GError *error = NULL; - PsyParallelPort *pp = psy_parallel_port_new(); + GOptionContext *opts = g_option_context_new("Open a parallelport"); + g_option_context_add_main_entries(opts, entries, NULL); - GError *error = NULL; + g_option_context_parse(opts, &argc, &argv, &error); + g_option_context_free(opts); + if (error) { + g_printerr("Unable to parse cmd arguments: %s\n", error->message); + return EXIT_FAILURE; + } + + PsyParallelPort *pp = psy_parallel_port_new(); - psy_parallel_port_open(pp, 0, &error); + psy_parallel_port_open(pp, g_port_num, &error); if (error) { fprintf(stderr, "%s\n", error->message); goto the_end; @@ -35,10 +44,11 @@ main(int argc, char **argv) psy_parallel_port_write(pp, 0, &error); if (error) break; + g_usleep(1000); psy_parallel_port_write(pp, 255, &error); if (error) break; - test_sleep(1); + g_usleep(1000); } psy_parallel_port_write(pp, 0, &error); diff --git a/tests/test-parallel.c b/tests/test-parallel.c index 2e7f28c..5441ccc 100644 --- a/tests/test-parallel.c +++ b/tests/test-parallel.c @@ -81,23 +81,29 @@ parallel_port_open(void) gchar expected_name[BUFSIZ]; #if defined(HAVE_LINUX_PARPORT_H) g_snprintf(expected_name, BUFSIZ, "/dev/parport%d", g_port_num); -#else - g_snprintf(expected_name, BUFSIZ, "LPT%d", g_port_num + 1); +#elif defined(WIN32) + const gchar *win_address; + if (g_port_num == 0) + win_address = "0x378"; + else if (g_port_num == 1) + win_address = "0x278"; + else if (g_port_num == 2) + win_address = "0x3BC"; + else + win_address = ""; + g_snprintf(expected_name, BUFSIZ, "%s", win_address); #endif psy_parallel_port_open(port, g_port_num, &error); gboolean open = psy_parallel_port_is_open(port); CU_ASSERT_TRUE(open); + CU_ASSERT_PTR_NULL(error); if (!open) { fprintf(stderr, "Unable to open port: %s", error->message); - g_error_free(error); + g_clear_error(&error); g_object_unref(port); return; } - CU_ASSERT_PTR_NULL(error); - if (error) { - g_print("Error = %s\n", error->message); - } g_object_get(port, "port-num", &port_num, "port-name", &name, NULL); @@ -120,7 +126,8 @@ parallel_port_open(void) int add_parallel_suite(gint port_num) { -#if defined(HAVE_LINUX_PARPORT_H) // Check for other port implementations here + // Check for other port implementations here +#if defined(HAVE_LINUX_PARPORT_H) || defined(_WIN32) CU_Suite *suite = CU_add_suite("parallel port tests", NULL, NULL); CU_Test *test = NULL; diff --git a/toys/meson.build b/toys/meson.build index 6015562..7edecc3 100644 --- a/toys/meson.build +++ b/toys/meson.build @@ -1,2 +1,10 @@ - -sleep_time = executable('sleep-time', files('sleep-time.c'), dependencies:[psy_dep]) \ No newline at end of file + +sleep_time = executable('sleep-time', files('sleep-time.c'), dependencies:[psy_dep]) + +wbemuuid_dep = cc.find_library('wbemuuid', static:true) +wmi_parport= executable( + 'wmi_parallel_port', + files('wmi-parallel-port.c'), + dependencies: wbemuuid_dep +) + diff --git a/toys/wmi-parallel-port.c b/toys/wmi-parallel-port.c new file mode 100644 index 0000000..e533900 --- /dev/null +++ b/toys/wmi-parallel-port.c @@ -0,0 +1,135 @@ + +#include + +#define _WIN32_DCOM + +#include +#include + +int g_port_num = 1; + +int +main(int argc, char **argv) +{ + HRESULT hr; + IWbemLocator *pLoc = NULL; + IWbemServices *pSvc = NULL; + + hr = CoInitializeEx(0, COINIT_MULTITHREADED); + + if (FAILED(hr)) { + fprintf(stderr, "Unable to init COM: 0x%lx", hr); + return EXIT_FAILURE; + } + + hr = CoInitializeSecurity(NULL, + -1, + NULL, + NULL, + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, + NULL, + EOAC_NONE, + NULL); + if (FAILED(hr)) { + fprintf(stderr, "Unable to setup security: 0x%lx", hr); + goto end; + } + + hr = CoCreateInstance(&CLSID_WbemLocator, + NULL, + CLSCTX_INPROC_SERVER, + &IID_IWbemLocator, + (LPVOID *) &pLoc); + BSTR namespace + = SysAllocString(L"ROOT\\CIMV2"); // Object path of WMI namespace + + hr = pLoc->lpVtbl->ConnectServer( + pLoc, // self + namespace, // Object pat of WMI namespace + NULL, // user: NULL = current user + NULL, // user_pass word, NULL is current password + NULL, // locale 0 current + 0, // security flags + NULL, // Authority e.g. Kerberos + NULL, // Context object + &pSvc); // OUTPUT Pointer to IwbemServicesProxy + + if (FAILED(hr)) { + fprintf(stderr, "Unable to connect: 0x%lx", hr); + goto end; + } + + hr = CoSetProxyBlanket((IUnknown *) pSvc, + RPC_C_AUTHN_WINNT, + RPC_C_AUTHZ_NONE, + NULL, + RPC_C_AUTHN_LEVEL_CALL, + RPC_C_IMP_LEVEL_IMPERSONATE, + NULL, + EOAC_NONE); + if (FAILED(hr)) { + fprintf(stderr, "Unable to set proxy blanket: 0x%lx", hr); + goto end; + } + + IEnumWbemClassObject *pPortEnumerator = NULL; + + BSTR wql = SysAllocString(L"WQL"); + wchar_t query_plain[1024]; + swprintf(query_plain, + 1024, + L"select * from Win32_ParallelPort where DeviceID=\"LPT%d\"", + g_port_num); + BSTR port_query = SysAllocString(query_plain); + + hr = pSvc->lpVtbl->ExecQuery(pSvc, // self + wql, // use WQL for queries + port_query, // the actual query + WBEM_FLAG_FORWARD_ONLY + | WBEM_FLAG_RETURN_IMMEDIATELY, + NULL, + &pPortEnumerator); + if (FAILED(hr)) { + fprintf(stderr, "Unable to query: 0x%lx\n", hr); + goto end; + } + + IWbemClassObject *pClsObject; + ULONG result; + VARIANT value; + BSTR pnp_dev_id = NULL; + + while (pPortEnumerator) { + hr = pPortEnumerator->lpVtbl->Next( + pPortEnumerator, WBEM_INFINITE, 1, &pClsObject, &result); + if (result == 0) { + break; + } + + if (FAILED(hr)) { + fprintf(stderr, "Unable to enumerate parallel ports\n"); + goto end; + } + + hr = pClsObject->lpVtbl->Get( + pClsObject, L"PNPDeviceID", 0, &value, NULL, NULL); + pnp_dev_id = SysAllocString(value.bstrVal); + VariantClear(&value); + } + + if (pnp_dev_id) { + if (fprintf(stdout, "dev-id = %ls\n", pnp_dev_id) < 0) { + perror("fprintf"); + } + } + else { + fprintf(stdout, "LPT%d, not found\n", g_port_num); + } + +end: + SysFreeString(wql); + pSvc->lpVtbl->Release(pSvc); + pLoc->lpVtbl->Release(pLoc); + CoUninitialize(); +} \ No newline at end of file