Implemented Process working directory methods on windows, and fixed file system details.
This commit is contained in:
425
readme.md
425
readme.md
@@ -1,213 +1,212 @@
|
||||
|
||||
# Native-platform: Java bindings for various native APIs
|
||||
|
||||
A collection of cross-platform Java APIs for various native APIs. Supports OS X, Linux, Solaris and Windows.
|
||||
|
||||
These APIs support Java 5 and later. Some of these APIs overlap with APIs available in later Java versions.
|
||||
|
||||
## Available bindings
|
||||
|
||||
### System information
|
||||
|
||||
* Get kernel name and version.
|
||||
* Get machine architecture.
|
||||
|
||||
### Processes
|
||||
|
||||
* Get the PID of the current process.
|
||||
* Get and set the process working directory.
|
||||
|
||||
### Terminal and console
|
||||
|
||||
These bindings work for both the UNIX terminal and the Windows console:
|
||||
|
||||
* Determine if stdout/stderr are attached to a terminal.
|
||||
* Query the terminal size.
|
||||
* Switch between bold and normal mode on the terminal.
|
||||
* Change foreground color on the terminal.
|
||||
* Move terminal cursor up, down, left, right, start of line.
|
||||
* Clear to end of line.
|
||||
|
||||
### File systems
|
||||
|
||||
* Get and set UNIX file mode.
|
||||
* Create and read symbolic links.
|
||||
* List the available file systems on the machine
|
||||
* Query file system mount point.
|
||||
* Query file system type.
|
||||
* Query file system device name.
|
||||
* Query whether a file system is local or remote.
|
||||
|
||||
## Supported platforms
|
||||
|
||||
Currently ported to OS X, Linux and Windows. Support for Solaris and FreeBSD is a work in progress. Tested on:
|
||||
|
||||
* OS X 10.7.4, 10.8 (x86_64), 10.6.7 (i386)
|
||||
* Ubunutu 12.04 (amd64), 8.04.4 (i386, amd64)
|
||||
* Solaris 11 (x86)
|
||||
* Windows 7 (x64), XP (x86)
|
||||
|
||||
## Using
|
||||
|
||||
Include `native-platform.jar` and `native-platform-${os}-${arch}.jar` in your classpath. From Gradle, you can do
|
||||
this:
|
||||
|
||||
repositories {
|
||||
maven { url "http://repo.gradle.org/gradle/libs-releases-local" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "net.rubygrapefruit:native-platform:0.3"
|
||||
}
|
||||
|
||||
You can also download [here](http://repo.gradle.org/gradle/libs-releases-local/net/rubygrapefruit/)
|
||||
|
||||
Some sample code to use the terminal:
|
||||
|
||||
import net.rubygrapefruit.platform.Native;
|
||||
import net.rubygrapefruit.platform.Terminals;
|
||||
import net.rubygrapefruit.platform.Terminal;
|
||||
import static net.rubygrapefruit.platform.Terminals.Output.*;
|
||||
|
||||
Terminals terminals = Native.get(Terminals.class);
|
||||
|
||||
// check if terminal
|
||||
terminals.isTerminal(Stdout);
|
||||
|
||||
// use terminal
|
||||
Terminal stdout = terminals.getTerminal(Stdout);
|
||||
stdout.bold();
|
||||
System.out.println("bold text");
|
||||
|
||||
## Changes
|
||||
|
||||
### 0.3
|
||||
|
||||
* Fixes to work with 64-bit OpenJDK 7 on Mac OS X. Thanks to Rene Gr<47>schke.
|
||||
|
||||
### 0.2
|
||||
|
||||
* Fixes to make native library extraction multi-process safe.
|
||||
* Fixes to windows terminal detection and reset.
|
||||
|
||||
### 0.1
|
||||
|
||||
* Initial release.
|
||||
|
||||
# Development
|
||||
|
||||
## Building
|
||||
|
||||
You will need to use the Gradle wrapper. Just run `gradlew` in the root directory.
|
||||
|
||||
### Ubuntu
|
||||
|
||||
The g++ compiler is required to build the native library. You will need the `g++` package for this. Usually this is already installed.
|
||||
|
||||
You need to install the `libncurses5-dev` package to pick up the ncurses header files. Also worth installing the `ncurses-doc` package too.
|
||||
|
||||
#### 64-bit machines with multi-arch support
|
||||
|
||||
Where multi-arch support is available (e.g. recent Ubuntu releases), you can build the i386 and amd64 versions of the library on the
|
||||
same machine.
|
||||
|
||||
You need to install the `gcc-multilib` and `g++-multilib` packages to pick up i386 support.
|
||||
|
||||
You need to install the `lib32ncurses5-dev` package to pick up the ncurses i386 version.
|
||||
|
||||
To build, include `-Pmultiarch` on the command-line.
|
||||
|
||||
### Windows
|
||||
|
||||
You need to install Visual studio, and build from a Visual studio command prompt.
|
||||
|
||||
### OS X
|
||||
|
||||
The g++ compiler is required to build the native library. You will need to install the XCode tools for this.
|
||||
|
||||
### Solaris
|
||||
|
||||
For Solaris 11, you need to install the `development/gcc-45` and `system/header` packages.
|
||||
|
||||
## Running
|
||||
|
||||
Run `gradle installApp` to install the test application into `test-app/build/install/native-platform-test`. Or
|
||||
`gradle distZip` to create an application distribtion in `test-app/build/distributions/native-platform-test-$version.zip`.
|
||||
|
||||
You can run `$INSTALL_DIR/bin/native-platform-test` to run the test application.
|
||||
|
||||
# Releasing
|
||||
|
||||
1. Create a tag and push.
|
||||
2. Build each variant:
|
||||
1. Checkout tag.
|
||||
2. `./gradlew clean :test :uploadJni -Prelease -PartifactoryUserName=<> -PartifactoryPassword=<>`
|
||||
* OS X universal
|
||||
* Linux i386, using Ubunutu 8.04
|
||||
* Linux amd64, using Ubunutu 8.04
|
||||
* Windows x86, using VC++ 2010
|
||||
* Windows x64
|
||||
3. Build Java library and test app:
|
||||
1. Checkout tag.
|
||||
2. `./gradlew clean :test :uploadArchives testApp:uploadArchives -Prelease -PartifactoryUserName=<> -PartifactoryPassword=<>`
|
||||
4. Checkout master
|
||||
5. Increment version number in `build.gradle` and this readme.
|
||||
|
||||
## Testing
|
||||
|
||||
* Test on IBM JVM.
|
||||
* Test on Java 5, 6, 7.
|
||||
* Test on Windows 7, Windows XP
|
||||
|
||||
## TODO
|
||||
|
||||
### Fixes
|
||||
|
||||
* Posix: allow terminal to be detected when ncurses cannot be loaded
|
||||
* Windows: fix detection of shared drive under VMWare fusion and Windows XP
|
||||
* Linux: detect remote filesystems.
|
||||
* Solaris: fix unicode file name handling.
|
||||
* Solaris: fail for unsupported architecture.
|
||||
* Solaris: build 32 bit and 64 bit libraries.
|
||||
* Freebsd: finish port.
|
||||
* Freebsd: fail for unsupported architecture.
|
||||
* Freebsd: build 32 bit and 64 bit libraries.
|
||||
|
||||
### Improvements
|
||||
|
||||
* Use wchar_to_java() for windows system and file system info.
|
||||
* Test network file systems on Mac, Linux, Windows
|
||||
* Test mount points on Windows
|
||||
* Cache class, method and field lookups (in particular for String conversions).
|
||||
* Determine C charset once at startup
|
||||
* Change readLink() implementation so that it does not need to NULL terminate the encoded content
|
||||
* Don't use NewStringUTF() anywhere
|
||||
* Don't use NewString() anywhere
|
||||
* Use iconv() to convert from C char string to UTF-16 when converting from C char string to Java String.
|
||||
* Support for cygwin terminal
|
||||
* Use TERM=xtermc instead of TERM=xterm on Solaris.
|
||||
* Add diagnostics for terminal.
|
||||
* Split out separate native library for terminal handling.
|
||||
* Version each native interface separately.
|
||||
* String names for errno values.
|
||||
* Split into multiple projects.
|
||||
* Convert to c.
|
||||
* Use fully decomposed form for unicode file names on hfs+ filesystems.
|
||||
* Extend FileSystem to deal with removable media.
|
||||
* Add a method to Terminal that returns a PrintStream that can be used to write to the terminal, regardless of what
|
||||
System.out/System.err point to.
|
||||
* Add a Terminal implementation that uses ANSI control codes. Use this on UNIX platforms when TERM != 'dumb' and
|
||||
libncurses cannot be loaded.
|
||||
* Add a method to Terminal that indicates whether the cursor wraps to the next line when a character is written to the
|
||||
rightmost character position.
|
||||
|
||||
### Ideas
|
||||
|
||||
* Expose platform-specific HTTP proxy configuration. Query registry on windows to determine IE settings.
|
||||
* Expose native named semaphores, mutexes and condition variables (CreateMutex, CreateSemaphore, CreateEvent, semget, sem_open, etc).
|
||||
* Expose infromation about network interfaces.
|
||||
* Fire events when filesystems or network interfaces change in some way.
|
||||
* Fire events when terminal size changes.
|
||||
* Fire events when files change.
|
||||
* Expose system keystores and authentication services.
|
||||
* Expose a mechanism for generating a temporary directory.
|
||||
|
||||
# Native-platform: Java bindings for various native APIs
|
||||
|
||||
A collection of cross-platform Java APIs for various native APIs. Supports OS X, Linux, Solaris and Windows.
|
||||
|
||||
These APIs support Java 5 and later. Some of these APIs overlap with APIs available in later Java versions.
|
||||
|
||||
## Available bindings
|
||||
|
||||
### System information
|
||||
|
||||
* Get kernel name and version.
|
||||
* Get machine architecture.
|
||||
|
||||
### Processes
|
||||
|
||||
* Get the PID of the current process.
|
||||
* Get and set the process working directory.
|
||||
|
||||
### Terminal and console
|
||||
|
||||
These bindings work for both the UNIX terminal and the Windows console:
|
||||
|
||||
* Determine if stdout/stderr are attached to a terminal.
|
||||
* Query the terminal size.
|
||||
* Switch between bold and normal mode on the terminal.
|
||||
* Change foreground color on the terminal.
|
||||
* Move terminal cursor up, down, left, right, start of line.
|
||||
* Clear to end of line.
|
||||
|
||||
### File systems
|
||||
|
||||
* Get and set UNIX file mode.
|
||||
* Create and read symbolic links.
|
||||
* List the available file systems on the machine
|
||||
* Query file system mount point.
|
||||
* Query file system type.
|
||||
* Query file system device name.
|
||||
* Query whether a file system is local or remote.
|
||||
|
||||
## Supported platforms
|
||||
|
||||
Currently ported to OS X, Linux and Windows. Support for Solaris and FreeBSD is a work in progress. Tested on:
|
||||
|
||||
* OS X 10.7.4, 10.8 (x86_64), 10.6.7 (i386)
|
||||
* Ubunutu 12.04 (amd64), 8.04.4 (i386, amd64)
|
||||
* Solaris 11 (x86)
|
||||
* Windows 7 (x64), XP (x86)
|
||||
|
||||
## Using
|
||||
|
||||
Include `native-platform.jar` and `native-platform-${os}-${arch}.jar` in your classpath. From Gradle, you can do
|
||||
this:
|
||||
|
||||
repositories {
|
||||
maven { url "http://repo.gradle.org/gradle/libs-releases-local" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "net.rubygrapefruit:native-platform:0.3"
|
||||
}
|
||||
|
||||
You can also download [here](http://repo.gradle.org/gradle/libs-releases-local/net/rubygrapefruit/)
|
||||
|
||||
Some sample code to use the terminal:
|
||||
|
||||
import net.rubygrapefruit.platform.Native;
|
||||
import net.rubygrapefruit.platform.Terminals;
|
||||
import net.rubygrapefruit.platform.Terminal;
|
||||
import static net.rubygrapefruit.platform.Terminals.Output.*;
|
||||
|
||||
Terminals terminals = Native.get(Terminals.class);
|
||||
|
||||
// check if terminal
|
||||
terminals.isTerminal(Stdout);
|
||||
|
||||
// use terminal
|
||||
Terminal stdout = terminals.getTerminal(Stdout);
|
||||
stdout.bold();
|
||||
System.out.println("bold text");
|
||||
|
||||
## Changes
|
||||
|
||||
### 0.3
|
||||
|
||||
* Fixes to work with 64-bit OpenJDK 7 on Mac OS X. Thanks to Rene Gr<47>schke.
|
||||
|
||||
### 0.2
|
||||
|
||||
* Fixes to make native library extraction multi-process safe.
|
||||
* Fixes to windows terminal detection and reset.
|
||||
|
||||
### 0.1
|
||||
|
||||
* Initial release.
|
||||
|
||||
# Development
|
||||
|
||||
## Building
|
||||
|
||||
You will need to use the Gradle wrapper. Just run `gradlew` in the root directory.
|
||||
|
||||
### Ubuntu
|
||||
|
||||
The g++ compiler is required to build the native library. You will need the `g++` package for this. Usually this is already installed.
|
||||
|
||||
You need to install the `libncurses5-dev` package to pick up the ncurses header files. Also worth installing the `ncurses-doc` package too.
|
||||
|
||||
#### 64-bit machines with multi-arch support
|
||||
|
||||
Where multi-arch support is available (e.g. recent Ubuntu releases), you can build the i386 and amd64 versions of the library on the
|
||||
same machine.
|
||||
|
||||
You need to install the `gcc-multilib` and `g++-multilib` packages to pick up i386 support.
|
||||
|
||||
You need to install the `lib32ncurses5-dev` package to pick up the ncurses i386 version.
|
||||
|
||||
To build, include `-Pmultiarch` on the command-line.
|
||||
|
||||
### Windows
|
||||
|
||||
You need to install Visual studio, and build from a Visual studio command prompt.
|
||||
|
||||
### OS X
|
||||
|
||||
The g++ compiler is required to build the native library. You will need to install the XCode tools for this.
|
||||
|
||||
### Solaris
|
||||
|
||||
For Solaris 11, you need to install the `development/gcc-45` and `system/header` packages.
|
||||
|
||||
## Running
|
||||
|
||||
Run `gradle installApp` to install the test application into `test-app/build/install/native-platform-test`. Or
|
||||
`gradle distZip` to create an application distribtion in `test-app/build/distributions/native-platform-test-$version.zip`.
|
||||
|
||||
You can run `$INSTALL_DIR/bin/native-platform-test` to run the test application.
|
||||
|
||||
# Releasing
|
||||
|
||||
1. Create a tag and push.
|
||||
2. Build each variant:
|
||||
1. Checkout tag.
|
||||
2. `./gradlew clean :test :uploadJni -Prelease -PartifactoryUserName=<> -PartifactoryPassword=<>`
|
||||
* OS X universal
|
||||
* Linux i386, using Ubunutu 8.04
|
||||
* Linux amd64, using Ubunutu 8.04
|
||||
* Windows x86, using VC++ 2010
|
||||
* Windows x64
|
||||
3. Build Java library and test app:
|
||||
1. Checkout tag.
|
||||
2. `./gradlew clean :test :uploadArchives testApp:uploadArchives -Prelease -PartifactoryUserName=<> -PartifactoryPassword=<>`
|
||||
4. Checkout master
|
||||
5. Increment version number in `build.gradle` and this readme.
|
||||
|
||||
## Testing
|
||||
|
||||
* Test on IBM JVM.
|
||||
* Test on Java 5, 6, 7.
|
||||
* Test on Windows 7, Windows XP
|
||||
|
||||
## TODO
|
||||
|
||||
### Fixes
|
||||
|
||||
* Posix: allow terminal to be detected when ncurses cannot be loaded
|
||||
* Windows: fix detection of shared drive under VMWare fusion and Windows XP
|
||||
* Linux: detect remote filesystems.
|
||||
* Solaris: fix unicode file name handling.
|
||||
* Solaris: fail for unsupported architecture.
|
||||
* Solaris: build 32 bit and 64 bit libraries.
|
||||
* Freebsd: finish port.
|
||||
* Freebsd: fail for unsupported architecture.
|
||||
* Freebsd: build 32 bit and 64 bit libraries.
|
||||
|
||||
### Improvements
|
||||
|
||||
* Use wchar_to_java() for windows system and file system info.
|
||||
* Test network file systems on Mac, Linux, Windows
|
||||
* Test mount points on Windows
|
||||
* Cache class, method and field lookups (in particular for String conversions).
|
||||
* Determine C charset once at startup
|
||||
* Change readLink() implementation so that it does not need to NULL terminate the encoded content
|
||||
* Don't use NewStringUTF() anywhere
|
||||
* Use iconv() to convert from C char string to UTF-16 when converting from C char string to Java String.
|
||||
* Support for cygwin terminal
|
||||
* Use TERM=xtermc instead of TERM=xterm on Solaris.
|
||||
* Add diagnostics for terminal.
|
||||
* Split out separate native library for terminal handling.
|
||||
* Version each native interface separately.
|
||||
* String names for errno values.
|
||||
* Split into multiple projects.
|
||||
* Convert to c.
|
||||
* Use fully decomposed form for unicode file names on hfs+ filesystems.
|
||||
* Extend FileSystem to deal with removable media.
|
||||
* Add a method to Terminal that returns a PrintStream that can be used to write to the terminal, regardless of what
|
||||
System.out/System.err point to.
|
||||
* Add a Terminal implementation that uses ANSI control codes. Use this on UNIX platforms when TERM != 'dumb' and
|
||||
libncurses cannot be loaded.
|
||||
* Add a method to Terminal that indicates whether the cursor wraps to the next line when a character is written to the
|
||||
rightmost character position.
|
||||
|
||||
### Ideas
|
||||
|
||||
* Expose platform-specific HTTP proxy configuration. Query registry on windows to determine IE settings.
|
||||
* Expose native named semaphores, mutexes and condition variables (CreateMutex, CreateSemaphore, CreateEvent, semget, sem_open, etc).
|
||||
* Expose infromation about network interfaces.
|
||||
* Fire events when filesystems or network interfaces change in some way.
|
||||
* Fire events when terminal size changes.
|
||||
* Fire events when files change.
|
||||
* Expose system keystores and authentication services.
|
||||
* Expose a mechanism for generating a temporary directory.
|
||||
|
||||
@@ -1,357 +1,411 @@
|
||||
/*
|
||||
* Copyright 2012 Adam Murdoch
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
#include "native.h"
|
||||
#include "generic.h"
|
||||
#include <windows.h>
|
||||
#include <wchar.h>
|
||||
|
||||
/*
|
||||
* Marks the given result as failed, using the current value of GetLastError()
|
||||
*/
|
||||
void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result) {
|
||||
mark_failed_with_code(env, message, GetLastError(), result);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_NativeLibraryFunctions_getSystemInfo(JNIEnv *env, jclass target, jobject info, jobject result) {
|
||||
jclass infoClass = env->GetObjectClass(info);
|
||||
|
||||
OSVERSIONINFOEX versionInfo;
|
||||
versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
|
||||
if (GetVersionEx((OSVERSIONINFO*)&versionInfo) == 0) {
|
||||
mark_failed_with_errno(env, "could not get version info", result);
|
||||
return;
|
||||
}
|
||||
|
||||
SYSTEM_INFO systemInfo;
|
||||
GetNativeSystemInfo(&systemInfo);
|
||||
jstring arch = NULL;
|
||||
if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) {
|
||||
arch = env->NewStringUTF("amd64");
|
||||
} else if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) {
|
||||
arch = env->NewStringUTF("x86");
|
||||
} else if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64) {
|
||||
arch = env->NewStringUTF("ia64");
|
||||
} else {
|
||||
arch = env->NewStringUTF("unknown");
|
||||
}
|
||||
|
||||
jmethodID method = env->GetMethodID(infoClass, "windows", "(IIIZLjava/lang/String;)V");
|
||||
env->CallVoidMethod(info, method, versionInfo.dwMajorVersion, versionInfo.dwMinorVersion,
|
||||
versionInfo.dwBuildNumber, versionInfo.wProductType == VER_NT_WORKSTATION,
|
||||
arch);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process functions
|
||||
*/
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_PosixProcessFunctions_getPid(JNIEnv *env, jclass target) {
|
||||
return GetCurrentProcessId();
|
||||
}
|
||||
|
||||
/*
|
||||
* File system functions
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_PosixFileSystemFunctions_listFileSystems(JNIEnv *env, jclass target, jobject info, jobject result) {
|
||||
wchar_t* volumeName = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1));
|
||||
|
||||
jclass info_class = env->GetObjectClass(info);
|
||||
jmethodID method = env->GetMethodID(info_class, "add", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V");
|
||||
|
||||
HANDLE handle = FindFirstVolumeW(volumeName, MAX_PATH+1);
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
free(volumeName);
|
||||
mark_failed_with_errno(env, "could not find first volume", result);
|
||||
return;
|
||||
}
|
||||
|
||||
wchar_t* deviceName = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1));
|
||||
wchar_t* pathNames = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1));
|
||||
wchar_t* fsName = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1));
|
||||
|
||||
while(true) {
|
||||
// Chop off the trailing '\'
|
||||
size_t len = wcslen(volumeName);
|
||||
if (len < 5) {
|
||||
mark_failed_with_message(env, "volume name is too short", result);
|
||||
break;
|
||||
}
|
||||
volumeName[len-1] = L'\0';
|
||||
|
||||
if (QueryDosDeviceW(&volumeName[4], deviceName, MAX_PATH+1) == 0) {
|
||||
mark_failed_with_errno(env, "could not query dos device", result);
|
||||
break;
|
||||
}
|
||||
volumeName[len-1] = L'\\';
|
||||
|
||||
DWORD used;
|
||||
if (GetVolumePathNamesForVolumeNameW(volumeName, pathNames, MAX_PATH+1, &used) == 0) {
|
||||
// TODO - try again if the buffer is too small
|
||||
mark_failed_with_errno(env, "could not query volume paths", result);
|
||||
break;
|
||||
}
|
||||
|
||||
wchar_t* cur = pathNames;
|
||||
if (cur[0] != L'\0') {
|
||||
// TODO - use GetDriveTypeW() to determine if removable, remote, etc
|
||||
if(GetVolumeInformationW(cur, NULL, 0, NULL, NULL, NULL, fsName, MAX_PATH+1) == 0) {
|
||||
if (GetLastError() != ERROR_NOT_READY) {
|
||||
mark_failed_with_errno(env, "could not query volume information", result);
|
||||
break;
|
||||
}
|
||||
wcscpy(fsName, L"unknown");
|
||||
}
|
||||
for (;cur[0] != L'\0'; cur += wcslen(cur) + 1) {
|
||||
env->CallVoidMethod(info, method, env->NewString((jchar*)deviceName, wcslen(deviceName)),
|
||||
env->NewString((jchar*)fsName, wcslen(fsName)), env->NewString((jchar*)cur, wcslen(cur)), JNI_FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
if (FindNextVolumeW(handle, volumeName, MAX_PATH) == 0) {
|
||||
if (GetLastError() != ERROR_NO_MORE_FILES) {
|
||||
mark_failed_with_errno(env, "could find next volume", result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(volumeName);
|
||||
free(deviceName);
|
||||
free(pathNames);
|
||||
free(fsName);
|
||||
FindVolumeClose(handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Console functions
|
||||
*/
|
||||
|
||||
HANDLE getHandle(JNIEnv *env, int output, jobject result) {
|
||||
HANDLE handle = output == 0 ? GetStdHandle(STD_OUTPUT_HANDLE) : GetStdHandle(STD_ERROR_HANDLE);
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
mark_failed_with_errno(env, "could not get console handle", result);
|
||||
return NULL;
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_isConsole(JNIEnv *env, jclass target, jint output, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
HANDLE handle = getHandle(env, output, result);
|
||||
if (handle == NULL) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
if (!GetConsoleScreenBufferInfo(handle, &console_info)) {
|
||||
if (GetLastError() == ERROR_INVALID_HANDLE) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_getConsoleSize(JNIEnv *env, jclass target, jint output, jobject dimension, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
HANDLE handle = getHandle(env, output, result);
|
||||
if (handle == NULL) {
|
||||
mark_failed_with_message(env, "not a console", result);
|
||||
return;
|
||||
}
|
||||
if (!GetConsoleScreenBufferInfo(handle, &console_info)) {
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
return;
|
||||
}
|
||||
|
||||
jclass dimensionClass = env->GetObjectClass(dimension);
|
||||
jfieldID widthField = env->GetFieldID(dimensionClass, "cols", "I");
|
||||
env->SetIntField(dimension, widthField, console_info.srWindow.Right - console_info.srWindow.Left + 1);
|
||||
jfieldID heightField = env->GetFieldID(dimensionClass, "rows", "I");
|
||||
env->SetIntField(dimension, heightField, console_info.srWindow.Bottom - console_info.srWindow.Top + 1);
|
||||
}
|
||||
|
||||
HANDLE current_console = NULL;
|
||||
WORD original_attributes = 0;
|
||||
WORD current_attributes = 0;
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_initConsole(JNIEnv *env, jclass target, jint output, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
HANDLE handle = getHandle(env, output, result);
|
||||
if (handle == NULL) {
|
||||
mark_failed_with_message(env, "not a terminal", result);
|
||||
return;
|
||||
}
|
||||
if (!GetConsoleScreenBufferInfo(handle, &console_info)) {
|
||||
if (GetLastError() == ERROR_INVALID_HANDLE) {
|
||||
mark_failed_with_message(env, "not a console", result);
|
||||
} else {
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
current_console = handle;
|
||||
original_attributes = console_info.wAttributes;
|
||||
current_attributes = original_attributes;
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_normal(env, target, result);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_bold(JNIEnv *env, jclass target, jobject result) {
|
||||
current_attributes |= FOREGROUND_INTENSITY;
|
||||
if (!SetConsoleTextAttribute(current_console, current_attributes)) {
|
||||
mark_failed_with_errno(env, "could not set text attributes", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_normal(JNIEnv *env, jclass target, jobject result) {
|
||||
current_attributes &= ~FOREGROUND_INTENSITY;
|
||||
SetConsoleTextAttribute(current_console, current_attributes);
|
||||
if (!SetConsoleTextAttribute(current_console, current_attributes)) {
|
||||
mark_failed_with_errno(env, "could not set text attributes", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_reset(JNIEnv *env, jclass target, jobject result) {
|
||||
current_attributes = original_attributes;
|
||||
if (!SetConsoleTextAttribute(current_console, current_attributes)) {
|
||||
mark_failed_with_errno(env, "could not set text attributes", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_foreground(JNIEnv *env, jclass target, jint color, jobject result) {
|
||||
current_attributes &= ~ (FOREGROUND_BLUE|FOREGROUND_RED|FOREGROUND_GREEN);
|
||||
switch (color) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
current_attributes |= FOREGROUND_RED;
|
||||
break;
|
||||
case 2:
|
||||
current_attributes |= FOREGROUND_GREEN;
|
||||
break;
|
||||
case 3:
|
||||
current_attributes |= FOREGROUND_RED|FOREGROUND_GREEN;
|
||||
break;
|
||||
case 4:
|
||||
current_attributes |= FOREGROUND_BLUE;
|
||||
break;
|
||||
case 5:
|
||||
current_attributes |= FOREGROUND_RED|FOREGROUND_BLUE;
|
||||
break;
|
||||
case 6:
|
||||
current_attributes |= FOREGROUND_GREEN|FOREGROUND_BLUE;
|
||||
break;
|
||||
default:
|
||||
current_attributes |= FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE;
|
||||
break;
|
||||
}
|
||||
|
||||
SetConsoleTextAttribute(current_console, current_attributes);
|
||||
if (!SetConsoleTextAttribute(current_console, current_attributes)) {
|
||||
mark_failed_with_errno(env, "could not set text attributes", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_left(JNIEnv *env, jclass target, jint count, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) {
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
return;
|
||||
}
|
||||
console_info.dwCursorPosition.X -= count;
|
||||
if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) {
|
||||
mark_failed_with_errno(env, "could not set cursor position", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_right(JNIEnv *env, jclass target, jint count, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) {
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
return;
|
||||
}
|
||||
console_info.dwCursorPosition.X += count;
|
||||
if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) {
|
||||
mark_failed_with_errno(env, "could not set cursor position", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_up(JNIEnv *env, jclass target, jint count, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) {
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
return;
|
||||
}
|
||||
console_info.dwCursorPosition.Y -= count;
|
||||
if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) {
|
||||
mark_failed_with_errno(env, "could not set cursor position", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_down(JNIEnv *env, jclass target, jint count, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) {
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
return;
|
||||
}
|
||||
console_info.dwCursorPosition.Y += count;
|
||||
if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) {
|
||||
mark_failed_with_errno(env, "could not set cursor position", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_startLine(JNIEnv *env, jclass target, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) {
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
return;
|
||||
}
|
||||
console_info.dwCursorPosition.X = 0;
|
||||
if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) {
|
||||
mark_failed_with_errno(env, "could not set cursor position", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_clearToEndOfLine(JNIEnv *env, jclass target, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) {
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
return;
|
||||
}
|
||||
DWORD count;
|
||||
if (!FillConsoleOutputCharacterW(current_console, L' ', console_info.dwSize.X - console_info.dwCursorPosition.X, console_info.dwCursorPosition, &count)) {
|
||||
mark_failed_with_errno(env, "could not clear console", result);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
/*
|
||||
* Copyright 2012 Adam Murdoch
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
#include "native.h"
|
||||
#include "generic.h"
|
||||
#include <windows.h>
|
||||
#include <wchar.h>
|
||||
|
||||
/*
|
||||
* Marks the given result as failed, using the current value of GetLastError()
|
||||
*/
|
||||
void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result) {
|
||||
mark_failed_with_code(env, message, GetLastError(), result);
|
||||
}
|
||||
|
||||
jstring wchar_to_java(JNIEnv* env, const wchar_t* chars, size_t len, jobject result) {
|
||||
if (sizeof(wchar_t) != 2) {
|
||||
mark_failed_with_message(env, "unexpected size of wchar_t", result);
|
||||
return NULL;
|
||||
}
|
||||
return env->NewString((jchar*)chars, len);
|
||||
}
|
||||
|
||||
wchar_t* java_to_wchar(JNIEnv *env, jstring string, jobject result) {
|
||||
jsize len = env->GetStringLength(string);
|
||||
wchar_t* str = (wchar_t*)malloc(sizeof(wchar_t) * (len+1));
|
||||
env->GetStringRegion(string, 0, len, (jchar*)str);
|
||||
str[len] = L'\0';
|
||||
return str;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_NativeLibraryFunctions_getSystemInfo(JNIEnv *env, jclass target, jobject info, jobject result) {
|
||||
jclass infoClass = env->GetObjectClass(info);
|
||||
|
||||
OSVERSIONINFOEX versionInfo;
|
||||
versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
|
||||
if (GetVersionEx((OSVERSIONINFO*)&versionInfo) == 0) {
|
||||
mark_failed_with_errno(env, "could not get version info", result);
|
||||
return;
|
||||
}
|
||||
|
||||
SYSTEM_INFO systemInfo;
|
||||
GetNativeSystemInfo(&systemInfo);
|
||||
jstring arch = NULL;
|
||||
if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) {
|
||||
arch = env->NewStringUTF("amd64");
|
||||
} else if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) {
|
||||
arch = env->NewStringUTF("x86");
|
||||
} else if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64) {
|
||||
arch = env->NewStringUTF("ia64");
|
||||
} else {
|
||||
arch = env->NewStringUTF("unknown");
|
||||
}
|
||||
|
||||
jmethodID method = env->GetMethodID(infoClass, "windows", "(IIIZLjava/lang/String;)V");
|
||||
env->CallVoidMethod(info, method, versionInfo.dwMajorVersion, versionInfo.dwMinorVersion,
|
||||
versionInfo.dwBuildNumber, versionInfo.wProductType == VER_NT_WORKSTATION,
|
||||
arch);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process functions
|
||||
*/
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_PosixProcessFunctions_getPid(JNIEnv *env, jclass target) {
|
||||
return GetCurrentProcessId();
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_PosixProcessFunctions_getWorkingDirectory(JNIEnv *env, jclass target, jobject result) {
|
||||
DWORD size = GetCurrentDirectoryW(0, NULL);
|
||||
if (size == 0) {
|
||||
mark_failed_with_errno(env, "could not determine length of working directory path", result);
|
||||
return NULL;
|
||||
}
|
||||
size = size+1; // Needs to include null character
|
||||
wchar_t* path = (wchar_t*)malloc(sizeof(wchar_t) * size);
|
||||
DWORD copied = GetCurrentDirectoryW(size, path);
|
||||
if (copied == 0) {
|
||||
free(path);
|
||||
mark_failed_with_errno(env, "could get working directory path", result);
|
||||
return NULL;
|
||||
}
|
||||
jstring dirName = wchar_to_java(env, path, copied, result);
|
||||
free(path);
|
||||
return dirName;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_PosixProcessFunctions_setWorkingDirectory(JNIEnv *env, jclass target, jstring dir, jobject result) {
|
||||
wchar_t* dirPath = java_to_wchar(env, dir, result);
|
||||
if (dirPath == NULL) {
|
||||
return;
|
||||
}
|
||||
if (!SetCurrentDirectoryW(dirPath)) {
|
||||
mark_failed_with_errno(env, "could not set current directory", result);
|
||||
free(dirPath);
|
||||
return;
|
||||
}
|
||||
|
||||
free(dirPath);
|
||||
}
|
||||
|
||||
/*
|
||||
* File system functions
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_PosixFileSystemFunctions_listFileSystems(JNIEnv *env, jclass target, jobject info, jobject result) {
|
||||
wchar_t* volumeName = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1));
|
||||
|
||||
jclass info_class = env->GetObjectClass(info);
|
||||
jmethodID method = env->GetMethodID(info_class, "add", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V");
|
||||
|
||||
HANDLE handle = FindFirstVolumeW(volumeName, MAX_PATH+1);
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
free(volumeName);
|
||||
mark_failed_with_errno(env, "could not find first volume", result);
|
||||
return;
|
||||
}
|
||||
|
||||
wchar_t* deviceName = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1));
|
||||
wchar_t* pathNames = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1));
|
||||
wchar_t* fsName = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1));
|
||||
|
||||
while(true) {
|
||||
// Chop off the trailing '\'
|
||||
size_t len = wcslen(volumeName);
|
||||
if (len < 5) {
|
||||
mark_failed_with_message(env, "volume name is too short", result);
|
||||
break;
|
||||
}
|
||||
volumeName[len-1] = L'\0';
|
||||
|
||||
if (QueryDosDeviceW(&volumeName[4], deviceName, MAX_PATH+1) == 0) {
|
||||
mark_failed_with_errno(env, "could not query dos device", result);
|
||||
break;
|
||||
}
|
||||
volumeName[len-1] = L'\\';
|
||||
|
||||
DWORD used;
|
||||
if (GetVolumePathNamesForVolumeNameW(volumeName, pathNames, MAX_PATH+1, &used) == 0) {
|
||||
// TODO - try again if the buffer is too small
|
||||
mark_failed_with_errno(env, "could not query volume paths", result);
|
||||
break;
|
||||
}
|
||||
|
||||
wchar_t* cur = pathNames;
|
||||
if (cur[0] != L'\0') {
|
||||
// TODO - use GetDriveTypeW() to determine if removable, remote, etc
|
||||
if(GetVolumeInformationW(cur, NULL, 0, NULL, NULL, NULL, fsName, MAX_PATH+1) == 0) {
|
||||
if (GetLastError() != ERROR_NOT_READY) {
|
||||
mark_failed_with_errno(env, "could not query volume information", result);
|
||||
break;
|
||||
}
|
||||
wcscpy(fsName, L"unknown");
|
||||
}
|
||||
for (;cur[0] != L'\0'; cur += wcslen(cur) + 1) {
|
||||
env->CallVoidMethod(info, method,
|
||||
wchar_to_java(env, cur, wcslen(cur), result),
|
||||
wchar_to_java(env, fsName, wcslen(fsName), result),
|
||||
wchar_to_java(env, deviceName, wcslen(deviceName), result),
|
||||
JNI_FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
if (FindNextVolumeW(handle, volumeName, MAX_PATH) == 0) {
|
||||
if (GetLastError() != ERROR_NO_MORE_FILES) {
|
||||
mark_failed_with_errno(env, "could find next volume", result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(volumeName);
|
||||
free(deviceName);
|
||||
free(pathNames);
|
||||
free(fsName);
|
||||
FindVolumeClose(handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Console functions
|
||||
*/
|
||||
|
||||
HANDLE getHandle(JNIEnv *env, int output, jobject result) {
|
||||
HANDLE handle = output == 0 ? GetStdHandle(STD_OUTPUT_HANDLE) : GetStdHandle(STD_ERROR_HANDLE);
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
mark_failed_with_errno(env, "could not get console handle", result);
|
||||
return NULL;
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_isConsole(JNIEnv *env, jclass target, jint output, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
HANDLE handle = getHandle(env, output, result);
|
||||
if (handle == NULL) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
if (!GetConsoleScreenBufferInfo(handle, &console_info)) {
|
||||
if (GetLastError() == ERROR_INVALID_HANDLE) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_getConsoleSize(JNIEnv *env, jclass target, jint output, jobject dimension, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
HANDLE handle = getHandle(env, output, result);
|
||||
if (handle == NULL) {
|
||||
mark_failed_with_message(env, "not a console", result);
|
||||
return;
|
||||
}
|
||||
if (!GetConsoleScreenBufferInfo(handle, &console_info)) {
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
return;
|
||||
}
|
||||
|
||||
jclass dimensionClass = env->GetObjectClass(dimension);
|
||||
jfieldID widthField = env->GetFieldID(dimensionClass, "cols", "I");
|
||||
env->SetIntField(dimension, widthField, console_info.srWindow.Right - console_info.srWindow.Left + 1);
|
||||
jfieldID heightField = env->GetFieldID(dimensionClass, "rows", "I");
|
||||
env->SetIntField(dimension, heightField, console_info.srWindow.Bottom - console_info.srWindow.Top + 1);
|
||||
}
|
||||
|
||||
HANDLE current_console = NULL;
|
||||
WORD original_attributes = 0;
|
||||
WORD current_attributes = 0;
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_initConsole(JNIEnv *env, jclass target, jint output, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
HANDLE handle = getHandle(env, output, result);
|
||||
if (handle == NULL) {
|
||||
mark_failed_with_message(env, "not a terminal", result);
|
||||
return;
|
||||
}
|
||||
if (!GetConsoleScreenBufferInfo(handle, &console_info)) {
|
||||
if (GetLastError() == ERROR_INVALID_HANDLE) {
|
||||
mark_failed_with_message(env, "not a console", result);
|
||||
} else {
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
current_console = handle;
|
||||
original_attributes = console_info.wAttributes;
|
||||
current_attributes = original_attributes;
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_normal(env, target, result);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_bold(JNIEnv *env, jclass target, jobject result) {
|
||||
current_attributes |= FOREGROUND_INTENSITY;
|
||||
if (!SetConsoleTextAttribute(current_console, current_attributes)) {
|
||||
mark_failed_with_errno(env, "could not set text attributes", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_normal(JNIEnv *env, jclass target, jobject result) {
|
||||
current_attributes &= ~FOREGROUND_INTENSITY;
|
||||
SetConsoleTextAttribute(current_console, current_attributes);
|
||||
if (!SetConsoleTextAttribute(current_console, current_attributes)) {
|
||||
mark_failed_with_errno(env, "could not set text attributes", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_reset(JNIEnv *env, jclass target, jobject result) {
|
||||
current_attributes = original_attributes;
|
||||
if (!SetConsoleTextAttribute(current_console, current_attributes)) {
|
||||
mark_failed_with_errno(env, "could not set text attributes", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_foreground(JNIEnv *env, jclass target, jint color, jobject result) {
|
||||
current_attributes &= ~ (FOREGROUND_BLUE|FOREGROUND_RED|FOREGROUND_GREEN);
|
||||
switch (color) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
current_attributes |= FOREGROUND_RED;
|
||||
break;
|
||||
case 2:
|
||||
current_attributes |= FOREGROUND_GREEN;
|
||||
break;
|
||||
case 3:
|
||||
current_attributes |= FOREGROUND_RED|FOREGROUND_GREEN;
|
||||
break;
|
||||
case 4:
|
||||
current_attributes |= FOREGROUND_BLUE;
|
||||
break;
|
||||
case 5:
|
||||
current_attributes |= FOREGROUND_RED|FOREGROUND_BLUE;
|
||||
break;
|
||||
case 6:
|
||||
current_attributes |= FOREGROUND_GREEN|FOREGROUND_BLUE;
|
||||
break;
|
||||
default:
|
||||
current_attributes |= FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE;
|
||||
break;
|
||||
}
|
||||
|
||||
SetConsoleTextAttribute(current_console, current_attributes);
|
||||
if (!SetConsoleTextAttribute(current_console, current_attributes)) {
|
||||
mark_failed_with_errno(env, "could not set text attributes", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_left(JNIEnv *env, jclass target, jint count, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) {
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
return;
|
||||
}
|
||||
console_info.dwCursorPosition.X -= count;
|
||||
if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) {
|
||||
mark_failed_with_errno(env, "could not set cursor position", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_right(JNIEnv *env, jclass target, jint count, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) {
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
return;
|
||||
}
|
||||
console_info.dwCursorPosition.X += count;
|
||||
if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) {
|
||||
mark_failed_with_errno(env, "could not set cursor position", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_up(JNIEnv *env, jclass target, jint count, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) {
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
return;
|
||||
}
|
||||
console_info.dwCursorPosition.Y -= count;
|
||||
if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) {
|
||||
mark_failed_with_errno(env, "could not set cursor position", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_down(JNIEnv *env, jclass target, jint count, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) {
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
return;
|
||||
}
|
||||
console_info.dwCursorPosition.Y += count;
|
||||
if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) {
|
||||
mark_failed_with_errno(env, "could not set cursor position", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_startLine(JNIEnv *env, jclass target, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) {
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
return;
|
||||
}
|
||||
console_info.dwCursorPosition.X = 0;
|
||||
if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) {
|
||||
mark_failed_with_errno(env, "could not set cursor position", result);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_clearToEndOfLine(JNIEnv *env, jclass target, jobject result) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO console_info;
|
||||
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) {
|
||||
mark_failed_with_errno(env, "could not get console buffer", result);
|
||||
return;
|
||||
}
|
||||
DWORD count;
|
||||
if (!FillConsoleOutputCharacterW(current_console, L' ', console_info.dwSize.X - console_info.dwCursorPosition.X, console_info.dwCursorPosition, &count)) {
|
||||
mark_failed_with_errno(env, "could not clear console", result);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,90 +1,76 @@
|
||||
/*
|
||||
* Copyright 2012 Adam Murdoch
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __INCLUDE_GENERIC_H__
|
||||
#define __INCLUDE_GENERIC_H__
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define NATIVE_VERSION 12
|
||||
|
||||
/*
|
||||
* Marks the given result as failed, using the given error message
|
||||
*/
|
||||
extern void mark_failed_with_message(JNIEnv *env, const char* message, jobject result);
|
||||
|
||||
/*
|
||||
* Marks the given result as failed, using the given error message and the current value of errno/GetLastError()
|
||||
*/
|
||||
extern void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result);
|
||||
|
||||
/*
|
||||
* Marks the given result as failed, using the given error message and error code
|
||||
*/
|
||||
extern void mark_failed_with_code(JNIEnv *env, const char* message, int error_code, jobject result);
|
||||
|
||||
typedef struct wchar_struct {
|
||||
// Not NULL terminated
|
||||
wchar_t* chars;
|
||||
// number of characters in the string
|
||||
size_t len;
|
||||
jstring source;
|
||||
JNIEnv *env;
|
||||
} wchar_str;
|
||||
|
||||
/*
|
||||
* Converts the given Java string to a wchar_str. Should call wchar_str_free() when finished.
|
||||
*
|
||||
* Returns NULL on failure.
|
||||
*/
|
||||
extern wchar_str*
|
||||
java_to_wchar_str(JNIEnv *env, jstring string, jobject result);
|
||||
|
||||
/*
|
||||
* Releases resources used by the given string.
|
||||
*/
|
||||
extern void wchar_str_free(wchar_str* str);
|
||||
|
||||
/*
|
||||
* Converts the given wchar_t string to a Java string.
|
||||
*
|
||||
* Returns NULL on failure.
|
||||
*/
|
||||
extern jstring wchar_to_java(JNIEnv* env, const wchar_t* chars, size_t len, jobject result);
|
||||
|
||||
/*
|
||||
* Converts the given Java string to a char_str. Should call free() when finished.
|
||||
*
|
||||
* Returns NULL on failure.
|
||||
*/
|
||||
extern char* java_to_char(JNIEnv *env, jstring string, jobject result);
|
||||
|
||||
/*
|
||||
* Converts the given NULL terminated char string to a Java string.
|
||||
*
|
||||
* Returns NULL on failure.
|
||||
*/
|
||||
extern jstring char_to_java(JNIEnv* env, const char* chars, jobject result);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
/*
|
||||
* Copyright 2012 Adam Murdoch
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __INCLUDE_GENERIC_H__
|
||||
#define __INCLUDE_GENERIC_H__
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define NATIVE_VERSION 12
|
||||
|
||||
/*
|
||||
* Marks the given result as failed, using the given error message
|
||||
*/
|
||||
extern void mark_failed_with_message(JNIEnv *env, const char* message, jobject result);
|
||||
|
||||
/*
|
||||
* Marks the given result as failed, using the given error message and the current value of errno/GetLastError()
|
||||
*/
|
||||
extern void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result);
|
||||
|
||||
/*
|
||||
* Marks the given result as failed, using the given error message and error code
|
||||
*/
|
||||
extern void mark_failed_with_code(JNIEnv *env, const char* message, int error_code, jobject result);
|
||||
|
||||
/*
|
||||
* Converts the given Java string to a NULL terminated wchar_str. Should call free() when finished.
|
||||
*
|
||||
* Returns NULL on failure.
|
||||
*/
|
||||
extern wchar_t*
|
||||
java_to_wchar(JNIEnv *env, jstring string, jobject result);
|
||||
|
||||
/*
|
||||
* Converts the given wchar_t string to a Java string.
|
||||
*
|
||||
* Returns NULL on failure.
|
||||
*/
|
||||
extern jstring wchar_to_java(JNIEnv* env, const wchar_t* chars, size_t len, jobject result);
|
||||
|
||||
/*
|
||||
* Converts the given Java string to a NULL terminated char string. Should call free() when finished.
|
||||
*
|
||||
* Returns NULL on failure.
|
||||
*/
|
||||
extern char* java_to_char(JNIEnv *env, jstring string, jobject result);
|
||||
|
||||
/*
|
||||
* Converts the given NULL terminated char string to a Java string.
|
||||
*
|
||||
* Returns NULL on failure.
|
||||
*/
|
||||
extern jstring char_to_java(JNIEnv* env, const char* chars, jobject result);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -33,23 +33,16 @@ class PosixFileTest extends Specification {
|
||||
}
|
||||
|
||||
def "can set mode on a file"() {
|
||||
def testFile = tmpDir.newFile("test.txt")
|
||||
def testFile = tmpDir.newFile(fileName)
|
||||
|
||||
when:
|
||||
file.setMode(testFile, 0740)
|
||||
|
||||
then:
|
||||
file.getMode(testFile) == 0740
|
||||
}
|
||||
|
||||
def "can set mode on a file with unicode in its name"() {
|
||||
def testFile = tmpDir.newFile("test\u03b1.txt")
|
||||
|
||||
when:
|
||||
file.setMode(testFile, 0740)
|
||||
|
||||
then:
|
||||
file.getMode(testFile) == 0740
|
||||
where:
|
||||
fileName << ["test.txt", "test\u03b1.txt"]
|
||||
}
|
||||
|
||||
def "cannot set mode on file that does not exist"() {
|
||||
|
||||
@@ -1,58 +1,61 @@
|
||||
/*
|
||||
* Copyright 2012 Adam Murdoch
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.rubygrapefruit.platform
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import spock.lang.Specification
|
||||
|
||||
class ProcessTest extends Specification {
|
||||
@Rule TemporaryFolder tmpDir
|
||||
final Process process = Native.get(Process.class)
|
||||
|
||||
def "caches process instance"() {
|
||||
expect:
|
||||
Native.get(Process.class) == process
|
||||
}
|
||||
|
||||
def "can get PID"() {
|
||||
expect:
|
||||
process.getProcessId() != 0
|
||||
}
|
||||
|
||||
def "can change working directory"() {
|
||||
def newDir = tmpDir.newFolder("dir").canonicalFile
|
||||
|
||||
when:
|
||||
def original = process.workingDirectory
|
||||
|
||||
then:
|
||||
original == new File(".").canonicalFile
|
||||
original == new File(System.getProperty("user.dir"))
|
||||
|
||||
when:
|
||||
process.workingDirectory = newDir
|
||||
|
||||
then:
|
||||
process.workingDirectory == newDir
|
||||
new File(".").canonicalFile == newDir
|
||||
new File(System.getProperty("user.dir")) == newDir
|
||||
|
||||
cleanup:
|
||||
process.workingDirectory = original
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright 2012 Adam Murdoch
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.rubygrapefruit.platform
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import spock.lang.Specification
|
||||
|
||||
class ProcessTest extends Specification {
|
||||
@Rule TemporaryFolder tmpDir
|
||||
final Process process = Native.get(Process.class)
|
||||
|
||||
def "caches process instance"() {
|
||||
expect:
|
||||
Native.get(Process.class) == process
|
||||
}
|
||||
|
||||
def "can get PID"() {
|
||||
expect:
|
||||
process.getProcessId() != 0
|
||||
}
|
||||
|
||||
def "can get and change working directory"() {
|
||||
def newDir = tmpDir.newFolder(dir).canonicalFile
|
||||
|
||||
when:
|
||||
def original = process.workingDirectory
|
||||
|
||||
then:
|
||||
original == new File(".").canonicalFile
|
||||
original == new File(System.getProperty("user.dir"))
|
||||
|
||||
when:
|
||||
process.workingDirectory = newDir
|
||||
|
||||
then:
|
||||
process.workingDirectory == newDir
|
||||
new File(".").canonicalFile == newDir
|
||||
new File(System.getProperty("user.dir")) == newDir
|
||||
|
||||
cleanup:
|
||||
process.workingDirectory = original
|
||||
|
||||
where:
|
||||
dir << ['dir', 'dir\u03b1']
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user