Merge changes
@ -8,16 +8,12 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="18" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" />
|
||||
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="26"/>
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="ClassiCube">
|
||||
<application android:icon="@mipmap/ccicon" android:label="ClassiCube">
|
||||
|
||||
<activity android:name="com.classicube.MainActivity"
|
||||
android:label="ClassiCube"
|
||||
<activity android:name="com.classicube.MainActivity" android:label="ClassiCube"
|
||||
android:configChanges="orientation|screenSize|keyboard|keyboardHidden">
|
||||
<!-- Tell NativeActivity the name of our .so -->
|
||||
<meta-data android:name="android.app.lib_name" android:value="classicube" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
@ -130,7 +130,15 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 {
|
||||
|
||||
void startGameAsync() {
|
||||
Log.i("CC_WIN", "handing off to native..");
|
||||
try {
|
||||
System.loadLibrary("classicube");
|
||||
} catch (UnsatisfiedLinkError ex) {
|
||||
ex.printStackTrace();
|
||||
showAlert("Failed to start", ex.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
gameRunning = true;
|
||||
runGameAsync();
|
||||
}
|
||||
|
||||
@ -157,7 +165,6 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 {
|
||||
}
|
||||
|
||||
if (!gameRunning) startGameAsync();
|
||||
gameRunning = true;
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@ -392,7 +399,7 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 {
|
||||
|
||||
setContentView(curView);
|
||||
curView.requestFocus();
|
||||
if (fullscreen) goFullscreen();
|
||||
if (fullscreen) setUIVisibility(FULLSCREEN_FLAGS);
|
||||
}
|
||||
|
||||
class LauncherView extends SurfaceView {
|
||||
@ -516,7 +523,10 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 {
|
||||
//ApplicationInfo info = getPackageManager().getApplicationInfo(name, 0);
|
||||
ApplicationInfo info = getApplicationInfo();
|
||||
File apkFile = new File(info.sourceDir);
|
||||
return apkFile.lastModified();
|
||||
|
||||
// https://developer.android.com/reference/java/io/File#lastModified()
|
||||
// lastModified is returned in milliseconds
|
||||
return apkFile.lastModified() / 1000;
|
||||
} catch (Exception ex) {
|
||||
return 0;
|
||||
}
|
||||
@ -606,7 +616,7 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 {
|
||||
//final Activity activity = this;
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
AlertDialog.Builder dlg = new AlertDialog.Builder(MainActivity.this, AlertDialog.THEME_HOLO_DARK);
|
||||
AlertDialog.Builder dlg = new AlertDialog.Builder(MainActivity.this);
|
||||
dlg.setTitle(title);
|
||||
dlg.setMessage(message);
|
||||
dlg.setPositiveButton("Close", new DialogInterface.OnClickListener() {
|
||||
@ -623,29 +633,30 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 {
|
||||
// TODO: this fails because multiple dialog boxes show
|
||||
}
|
||||
|
||||
public int getWindowState() {
|
||||
return fullscreen ? 1 : 0;
|
||||
}
|
||||
public int getWindowState() { return fullscreen ? 1 : 0; }
|
||||
final static int FULLSCREEN_FLAGS = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
|
||||
void goFullscreen() {
|
||||
curView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
|
||||
void setUIVisibility(int flags) {
|
||||
if (curView == null) return;
|
||||
try {
|
||||
curView.setSystemUiVisibility(flags);
|
||||
} catch (NoSuchMethodError ex) {
|
||||
// Not available on API < 11 (Android 3.0)
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void enterFullscreen() {
|
||||
fullscreen = true;
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (curView != null) goFullscreen();
|
||||
}
|
||||
public void run() { setUIVisibility(FULLSCREEN_FLAGS); }
|
||||
});
|
||||
}
|
||||
|
||||
public void exitFullscreen() {
|
||||
fullscreen = false;
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (curView != null) curView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
|
||||
}
|
||||
public void run() { setUIVisibility(View.SYSTEM_UI_FLAG_VISIBLE); }
|
||||
});
|
||||
}
|
||||
|
||||
@ -703,16 +714,19 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 {
|
||||
int len;
|
||||
try {
|
||||
conn.connect();
|
||||
Map<String, List<String>> all = conn.getHeaderFields();
|
||||
// Some implementations also provide this as getHeaderField(0), but some don't
|
||||
httpParseHeader("HTTP/1.1 " + conn.getResponseCode() + " MSG");
|
||||
|
||||
// Legitimate webservers aren't going to reply with over 200 headers
|
||||
for (int i = 0; i < 200; i++) {
|
||||
String key = conn.getHeaderFieldKey(i);
|
||||
String val = conn.getHeaderField(i);
|
||||
if (key == null && val == null) break;
|
||||
|
||||
for (Map.Entry<String, List<String>> h : all.entrySet()) {
|
||||
String key = h.getKey();
|
||||
for (String value : h.getValue()) {
|
||||
if (key == null) {
|
||||
httpParseHeader(value);
|
||||
httpParseHeader(val);
|
||||
} else {
|
||||
httpParseHeader(key + ":" + value);
|
||||
}
|
||||
httpParseHeader(key + ":" + val);
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
64
misc/build_server.py
Normal file
@ -0,0 +1,64 @@
|
||||
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
build_files = {
|
||||
'/ClassiCube.exe' : 'cc-w32-d3d.exe', '/ClassiCube.opengl.exe' : 'cc-w32-ogl.exe',
|
||||
'/ClassiCube.64.exe' : 'cc-w64-d3d.exe', '/ClassiCube.64-opengl.exe' : 'cc-w64-ogl.exe',
|
||||
'/ClassiCube.32' : 'cc-nix32', '/ClassiCube' : 'cc-nix64',
|
||||
'/ClassiCube.osx' : 'cc-osx32', '/ClassiCube.64.osx' : 'cc-osx64',
|
||||
'/ClassiCube.js' : 'cc.js', '/ClassiCube.apk' : 'cc.apk',
|
||||
'/ClassiCube.rpi' : 'cc-rpi',
|
||||
}
|
||||
|
||||
release_files = {
|
||||
'/win32' : 'win32/ClassiCube.exe', '/win64' : 'win64/ClassiCube.exe',
|
||||
'/osx32' : 'osx32/ClassiCube.tar.gz', '/osx64' : 'osx64/ClassiCube.tar.gz',
|
||||
'/mac32' : 'mac32/ClassiCube.tar.gz', '/mac64' : 'mac64/ClassiCube.tar.gz',
|
||||
'/nix32' : 'nix32/ClassiCube.tar.gz', '/nix64' : 'nix64/ClassiCube.tar.gz',
|
||||
'/rpi32' : 'rpi32/ClassiCube.tar.gz',
|
||||
}
|
||||
|
||||
def run_script(file):
|
||||
args = ["sh", file]
|
||||
su = subprocess.Popen(args)
|
||||
return su.wait()
|
||||
|
||||
class Handler(SimpleHTTPRequestHandler):
|
||||
|
||||
def serve_script(self, file, msg):
|
||||
ret = run_script(file)
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(msg % ret)
|
||||
|
||||
def do_GET(self):
|
||||
if self.path in build_files:
|
||||
self.serve_exe('client/src/' + build_files[self.path])
|
||||
elif self.path in release_files:
|
||||
self.serve_exe('client/release/' + release_files[self.path])
|
||||
elif self.path == '/rebuild':
|
||||
self.serve_script('build.sh', 'Rebuild client (%s)')
|
||||
elif self.path == '/rebuild_android':
|
||||
self.serve_script('build_android.sh', 'Rebuild android (%s)')
|
||||
elif self.path == '/release':
|
||||
self.serve_script('build_release.sh', 'Package release (%s)')
|
||||
else:
|
||||
self.send_error(404, "Unknown action")
|
||||
return
|
||||
|
||||
def serve_exe(self, path):
|
||||
try:
|
||||
f = open(path, 'rb')
|
||||
fs = os.fstat(f.fileno())
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "application/octet-stream")
|
||||
self.send_header("Content-Length", str(fs[6]))
|
||||
self.end_headers()
|
||||
self.copyfile(f, self.wfile)
|
||||
f.close()
|
||||
except IOError:
|
||||
self.send_error(404, "File not found")
|
||||
|
||||
httpd = HTTPServer(('', 80), Handler)
|
||||
httpd.serve_forever()
|
54
misc/buildbot_android.sh
Normal file
@ -0,0 +1,54 @@
|
||||
FLAGS="-fPIC -shared -s -O1 -fvisibility=hidden -rdynamic"
|
||||
LIBS="-lGLESv2 -lEGL -lm -landroid -llog"
|
||||
NDK_ROOT="/home/buildbot/android/android-ndk-r22/toolchains/llvm/prebuilt/linux-x86_64/bin"
|
||||
TOOLS_ROOT="/home/buildbot/android/sdk/build-tools/26.0.0"
|
||||
SDK_ROOT="/home/buildbot/android/sdk/platforms/android-26"
|
||||
|
||||
cd /home/buildbot/client/src
|
||||
$NDK_ROOT/armv7a-linux-androideabi16-clang *.c $FLAGS -march=armv5 $LIBS -o cc-droid-arm_16
|
||||
$NDK_ROOT/armv7a-linux-androideabi16-clang *.c $FLAGS $LIBS -o cc-droid-arm_32
|
||||
$NDK_ROOT/aarch64-linux-android21-clang *.c $FLAGS $LIBS -o cc-droid-arm_64
|
||||
$NDK_ROOT/i686-linux-android16-clang *.c $FLAGS $LIBS -o cc-droid-x86_32
|
||||
$NDK_ROOT/x86_64-linux-android21-clang *.c $FLAGS $LIBS -o cc-droid-x86_64
|
||||
|
||||
cd ../android/app/src/main
|
||||
# remove old java temp files
|
||||
rm -rf obj
|
||||
mkdir obj
|
||||
rm classes.dex
|
||||
|
||||
# copy required native libraries
|
||||
rm -rf lib
|
||||
mkdir lib
|
||||
mkdir lib/armeabi
|
||||
mkdir lib/armeabi-v7a
|
||||
mkdir lib/arm64-v8a
|
||||
mkdir lib/x86
|
||||
mkdir lib/x86_64
|
||||
cp ~/client/src/cc-droid-arm_16 lib/armeabi/libclassicube.so
|
||||
cp ~/client/src/cc-droid-arm_32 lib/armeabi-v7a/libclassicube.so
|
||||
cp ~/client/src/cc-droid-arm_64 lib/arm64-v8a/libclassicube.so
|
||||
cp ~/client/src/cc-droid-x86_32 lib/x86/libclassicube.so
|
||||
cp ~/client/src/cc-droid-x86_64 lib/x86_64/libclassicube.so
|
||||
|
||||
# The following commands are for manually building an .apk, see
|
||||
# https://spin.atomicobject.com/2011/08/22/building-android-application-bundles-apks-by-hand/
|
||||
# https://github.com/cnlohr/rawdrawandroid/blob/master/Makefile
|
||||
# https://stackoverflow.com/questions/41132753/how-can-i-build-an-android-apk-without-gradle-on-the-command-line
|
||||
# https://github.com/skanti/Android-Manual-Build-Command-Line/blob/master/hello-jni/Makefile
|
||||
# https://github.com/skanti/Android-Manual-Build-Command-Line/blob/master/hello-jni/CMakeLists.txt
|
||||
|
||||
# compile interop java file into its multiple .class files
|
||||
javac java/com/classicube/MainActivity.java -d ./obj -classpath $SDK_ROOT/android.jar
|
||||
# compile the multiple .class files into one .dex file
|
||||
$TOOLS_ROOT/dx --dex --output=obj/classes.dex ./obj
|
||||
# create initial .apk with packaged version of resources
|
||||
$TOOLS_ROOT/aapt package -f -M AndroidManifest.xml -S res -F obj/cc-unsigned.apk -I $SDK_ROOT/android.jar
|
||||
# and add all the required files
|
||||
cp obj/classes.dex classes.dex
|
||||
$TOOLS_ROOT/aapt add -f obj/cc-unsigned.apk classes.dex lib/armeabi/libclassicube.so lib/armeabi-v7a/libclassicube.so lib/arm64-v8a/libclassicube.so lib/x86/libclassicube.so lib/x86_64/libclassicube.so
|
||||
# sign the apk with debug key (https://stackoverflow.com/questions/16711233/)
|
||||
cp obj/cc-unsigned.apk obj/cc-signed.apk
|
||||
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore debug.keystore -storepass android -keypass android obj/cc-signed.apk androiddebugkey
|
||||
# create aligned .apk file
|
||||
$TOOLS_ROOT/zipalign -f -v 4 obj/cc-signed.apk ~/client/src/cc.apk
|
@ -17,22 +17,23 @@ def notify_webhook(body):
|
||||
"content" : body
|
||||
}
|
||||
r = requests.post(WEBHOOK_URL, json=webhook_data)
|
||||
print("BuildNotify response: " + r.text)
|
||||
print("Webhook response: " + r.text)
|
||||
except Exception as e:
|
||||
print("BuildNotify failed: %s" % (e))
|
||||
print("Webhook failed: %s" % (e))
|
||||
|
||||
def build_exists(file):
|
||||
return os.path.exists('client/src/' + file)
|
||||
|
||||
builds = {
|
||||
'Win32' : build_exists('cc-w32-d3d.exe'),
|
||||
'Win64' : build_exists('cc-w64-d3d.exe'),
|
||||
'Mac32' : build_exists('cc-osx32'),
|
||||
'Mac64' : build_exists('cc-osx64'),
|
||||
'Nix32' : build_exists('cc-nix32'),
|
||||
'Nix64' : build_exists('cc-nix64'),
|
||||
'Rpi' : build_exists('cc-rpi'),
|
||||
'Windows32' : build_exists('cc-w32-d3d.exe'),
|
||||
'Windows64' : build_exists('cc-w64-d3d.exe'),
|
||||
'macOS32' : build_exists('cc-osx32'),
|
||||
'macOS64' : build_exists('cc-osx64'),
|
||||
'Linux32' : build_exists('cc-nix32'),
|
||||
'Linux64' : build_exists('cc-nix64'),
|
||||
'RPi' : build_exists('cc-rpi'),
|
||||
'Web' : build_exists('cc.js'),
|
||||
'Android' : build_exists('cc.apk'),
|
||||
'Win-ogl32' : build_exists('cc-w32-ogl.exe'),
|
||||
'Win-ogl64' : build_exists('cc-w64-ogl.exe'),
|
||||
}
|
||||
@ -48,17 +49,19 @@ def check_build(key):
|
||||
failed.append(key)
|
||||
else:
|
||||
if not builds[key_32] and not builds[key_64]:
|
||||
failed.append(key + '32/64')
|
||||
failed.append(key + ' 32/64')
|
||||
elif not builds[key_32]:
|
||||
failed.append(key_32)
|
||||
failed.append(key + ' 32')
|
||||
elif not builds[key_64]:
|
||||
failed.append(key_64)
|
||||
failed.append(key + ' 64')
|
||||
|
||||
check_build('Win')
|
||||
check_build('Mac')
|
||||
check_build('Nix')
|
||||
check_build('Rpi')
|
||||
check_build('Windows')
|
||||
check_build('macOS')
|
||||
check_build('Linux')
|
||||
check_build('RPi')
|
||||
check_build('Web')
|
||||
check_build('Android')
|
||||
check_build('Win-ogl')
|
||||
|
||||
if len(failed):
|
||||
notify_webhook('<@%s>, failed to compile for: %s' % (TARGET_USER, ', '.join(failed)))
|
@ -1,4 +1,4 @@
|
||||
ClassiCube is a custom Minecraft Classic and ClassiCube client written in C that works on Windows, OSX, Linux, BSD, Solaris, Haiku, and in a browser.
|
||||
ClassiCube is a custom Minecraft Classic and ClassiCube client written in C that works on Windows, macOS, Linux, Android, BSD, Solaris, Haiku, and in a browser.
|
||||
**It is not affiliated with (or supported by) Mojang AB, Minecraft, or Microsoft in any way.**
|
||||
|
||||

|
||||
@ -14,9 +14,10 @@ You can grab the latest stable binaries [from here](https://www.classicube.net/d
|
||||
It **does not** work with 'modern/premium' Minecraft servers.
|
||||
|
||||
#### Requirements
|
||||
* Windows: 2000 or later. (Windows 98 with KernelEx also *technically* works)
|
||||
* macOS: macOS 10.5 or later. (Can be compiled to work with 10.3/10.4 though)
|
||||
* Windows: 98 or later.
|
||||
* macOS: 10.5 or later. (Can be compiled to work with 10.3/10.4 though)
|
||||
* Linux: libcurl and libopenal.
|
||||
* Android: 2.3 or later.
|
||||
|
||||
**Note:** When running from within VirtualBox, disable Mouse Integration, otherwise the camera will not work properly.
|
||||
|
||||
|
67
src/Audio.c
@ -17,6 +17,8 @@
|
||||
#include "Window.h"
|
||||
#endif
|
||||
|
||||
#define QUOTE(x) #x
|
||||
#define DefineDynFunc(sym) { QUOTE(sym), (void**)&_ ## sym }
|
||||
int Audio_SoundsVolume, Audio_MusicVolume;
|
||||
|
||||
#if defined CC_BUILD_NOAUDIO
|
||||
@ -129,20 +131,18 @@ static const cc_string alLib = String_FromConst("libopenal.so");
|
||||
static const cc_string alLib = String_FromConst("libopenal.so.1");
|
||||
#endif
|
||||
|
||||
#define QUOTE(x) #x
|
||||
#define DefineALFunc(sym) { QUOTE(sym), (void**)&_ ## sym }
|
||||
static cc_bool LoadALFuncs(void) {
|
||||
static const struct DynamicLibSym funcs[18] = {
|
||||
DefineALFunc(alcCreateContext), DefineALFunc(alcMakeContextCurrent),
|
||||
DefineALFunc(alcDestroyContext), DefineALFunc(alcOpenDevice),
|
||||
DefineALFunc(alcCloseDevice), DefineALFunc(alcGetError),
|
||||
DefineDynFunc(alcCreateContext), DefineDynFunc(alcMakeContextCurrent),
|
||||
DefineDynFunc(alcDestroyContext), DefineDynFunc(alcOpenDevice),
|
||||
DefineDynFunc(alcCloseDevice), DefineDynFunc(alcGetError),
|
||||
|
||||
DefineALFunc(alGetError), DefineALFunc(alGenSources),
|
||||
DefineALFunc(alDeleteSources), DefineALFunc(alGetSourcei),
|
||||
DefineALFunc(alSourcePlay), DefineALFunc(alSourceStop),
|
||||
DefineALFunc(alSourceQueueBuffers), DefineALFunc(alSourceUnqueueBuffers),
|
||||
DefineALFunc(alGenBuffers), DefineALFunc(alDeleteBuffers),
|
||||
DefineALFunc(alBufferData), DefineALFunc(alDistanceModel)
|
||||
DefineDynFunc(alGetError), DefineDynFunc(alGenSources),
|
||||
DefineDynFunc(alDeleteSources), DefineDynFunc(alGetSourcei),
|
||||
DefineDynFunc(alSourcePlay), DefineDynFunc(alSourceStop),
|
||||
DefineDynFunc(alSourceQueueBuffers), DefineDynFunc(alSourceUnqueueBuffers),
|
||||
DefineDynFunc(alGenBuffers), DefineDynFunc(alDeleteBuffers),
|
||||
DefineDynFunc(alBufferData), DefineDynFunc(alDistanceModel)
|
||||
};
|
||||
|
||||
void* lib = DynamicLib_Load2(&alLib);
|
||||
@ -390,22 +390,51 @@ struct AudioContext {
|
||||
SLBufferQueueItf bqPlayerQueue;
|
||||
};
|
||||
|
||||
static SLresult (SLAPIENTRY *_slCreateEngine)(
|
||||
SLObjectItf *pEngine,
|
||||
SLuint32 numOptions,
|
||||
const SLEngineOption *pEngineOptions,
|
||||
SLuint32 numInterfaces,
|
||||
const SLInterfaceID *pInterfaceIds,
|
||||
const SLboolean *pInterfaceRequired
|
||||
);
|
||||
static SLInterfaceID* _SL_IID_NULL;
|
||||
static SLInterfaceID* _SL_IID_PLAY;
|
||||
static SLInterfaceID* _SL_IID_ENGINE;
|
||||
static SLInterfaceID* _SL_IID_BUFFERQUEUE;
|
||||
static const cc_string slLib = String_FromConst("libOpenSLES.so");
|
||||
|
||||
static cc_bool LoadSLFuncs(void) {
|
||||
static const struct DynamicLibSym funcs[5] = {
|
||||
DefineDynFunc(slCreateEngine), DefineDynFunc(SL_IID_NULL),
|
||||
DefineDynFunc(SL_IID_PLAY), DefineDynFunc(SL_IID_ENGINE),
|
||||
DefineDynFunc(SL_IID_BUFFERQUEUE)
|
||||
};
|
||||
|
||||
void* lib = DynamicLib_Load2(&slLib);
|
||||
if (!lib) { Logger_DynamicLibWarn("loading", &slLib); return false; }
|
||||
return DynamicLib_GetAll(lib, funcs, Array_Elems(funcs));
|
||||
}
|
||||
|
||||
static cc_bool Backend_Init(void) {
|
||||
static const cc_string msg = String_FromConst("Failed to init OpenSLES. No audio will play.");
|
||||
SLInterfaceID ids[1];
|
||||
SLboolean req[1];
|
||||
SLresult res;
|
||||
|
||||
if (slEngineObject) return true;
|
||||
/* mixer doesn't use any effects */
|
||||
ids[0] = SL_IID_NULL; req[0] = SL_BOOLEAN_FALSE;
|
||||
if (!LoadSLFuncs()) { Logger_WarnFunc(&msg); return false; }
|
||||
|
||||
res = slCreateEngine(&slEngineObject, 0, NULL, 0, NULL, NULL);
|
||||
/* mixer doesn't use any effects */
|
||||
ids[0] = *_SL_IID_NULL; req[0] = SL_BOOLEAN_FALSE;
|
||||
|
||||
res = _slCreateEngine(&slEngineObject, 0, NULL, 0, NULL, NULL);
|
||||
if (res) { Logger_SimpleWarn(res, "creating OpenSL ES engine"); return false; }
|
||||
|
||||
res = (*slEngineObject)->Realize(slEngineObject, SL_BOOLEAN_FALSE);
|
||||
if (res) { Logger_SimpleWarn(res, "realising OpenSL ES engine"); return false; }
|
||||
|
||||
res = (*slEngineObject)->GetInterface(slEngineObject, SL_IID_ENGINE, &slEngineEngine);
|
||||
res = (*slEngineObject)->GetInterface(slEngineObject, *_SL_IID_ENGINE, &slEngineEngine);
|
||||
if (res) { Logger_SimpleWarn(res, "initing OpenSL ES engine"); return false; }
|
||||
|
||||
res = (*slEngineEngine)->CreateOutputMix(slEngineEngine, &slOutputObject, 1, ids, req);
|
||||
@ -489,16 +518,16 @@ static cc_result Backend_SetFormat(struct AudioContext* ctx, struct AudioFormat*
|
||||
dst.pLocator = &output;
|
||||
dst.pFormat = NULL;
|
||||
|
||||
ids[0] = SL_IID_BUFFERQUEUE; req[0] = SL_BOOLEAN_TRUE;
|
||||
ids[1] = SL_IID_PLAY; req[1] = SL_BOOLEAN_TRUE;
|
||||
ids[0] = *_SL_IID_BUFFERQUEUE; req[0] = SL_BOOLEAN_TRUE;
|
||||
ids[1] = *_SL_IID_PLAY; req[1] = SL_BOOLEAN_TRUE;
|
||||
|
||||
res = (*slEngineEngine)->CreateAudioPlayer(slEngineEngine, &bqPlayerObject, &src, &dst, 2, ids, req);
|
||||
ctx->bqPlayerObject = bqPlayerObject;
|
||||
if (res) return res;
|
||||
|
||||
if ((res = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE))) return res;
|
||||
if ((res = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &ctx->bqPlayerPlayer))) return res;
|
||||
if ((res = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &ctx->bqPlayerQueue))) return res;
|
||||
if ((res = (*bqPlayerObject)->GetInterface(bqPlayerObject, *_SL_IID_PLAY, &ctx->bqPlayerPlayer))) return res;
|
||||
if ((res = (*bqPlayerObject)->GetInterface(bqPlayerObject, *_SL_IID_BUFFERQUEUE, &ctx->bqPlayerQueue))) return res;
|
||||
|
||||
return (*ctx->bqPlayerQueue)->RegisterCallback(ctx->bqPlayerQueue, OnBufferFinished, ctx);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
#define GAME_MAX_CMDARGS 5
|
||||
#define GAME_APP_VER "1.2.3"
|
||||
#define GAME_APP_VER "1.2.4"
|
||||
#define GAME_API_VER 1
|
||||
|
||||
#if defined CC_BUILD_WEB
|
||||
@ -13,14 +13,14 @@
|
||||
#define GAME_APP_NAME "ClassiCube web"
|
||||
#define GAME_APP_TITLE "ClassiCube"
|
||||
#elif defined CC_BUILD_COCOA
|
||||
#define GAME_APP_NAME "ClassiCube 1.2.3 alpha"
|
||||
#define GAME_APP_TITLE "ClassiCube 1.2.3 alpha"
|
||||
#define GAME_APP_NAME "ClassiCube 1.2.4 alpha"
|
||||
#define GAME_APP_TITLE "ClassiCube 1.2.4 alpha"
|
||||
#elif defined CC_BUILD_ANDROID
|
||||
#define GAME_APP_NAME "ClassiCube 1.2.3 android alpha"
|
||||
#define GAME_APP_TITLE "ClassiCube 1.2.3 android alpha"
|
||||
#define GAME_APP_NAME "ClassiCube 1.2.4 android alpha"
|
||||
#define GAME_APP_TITLE "ClassiCube 1.2.4 android alpha"
|
||||
#else
|
||||
#define GAME_APP_NAME "ClassiCube 1.2.3"
|
||||
#define GAME_APP_TITLE "ClassiCube 1.2.3"
|
||||
#define GAME_APP_NAME "ClassiCube 1.2.4"
|
||||
#define GAME_APP_TITLE "ClassiCube 1.2.4"
|
||||
#endif
|
||||
|
||||
/* Max number of characters strings can have. */
|
||||
|
@ -36,7 +36,7 @@ void DrawTextArgs_MakeEmpty(struct DrawTextArgs* args, struct FontDesc* font, cc
|
||||
*-----------------------------------------------------Font functions------------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
static char defaultBuffer[STRING_SIZE];
|
||||
static cc_string font_candidates[11] = {
|
||||
static cc_string font_candidates[12] = {
|
||||
String_FromArray(defaultBuffer), /* Filled in with user's default font */
|
||||
String_FromConst("Arial"), /* preferred font on all platforms */
|
||||
String_FromConst("Liberation Sans"), /* nice looking fallbacks for linux */
|
||||
@ -45,9 +45,10 @@ static cc_string font_candidates[11] = {
|
||||
String_FromConst("Cantarell"),
|
||||
String_FromConst("DejaVu Sans Book"),
|
||||
String_FromConst("Century Schoolbook L Roman"), /* commonly available on linux */
|
||||
String_FromConst("Slate For OnePlus"), /* android 10, some devices */
|
||||
String_FromConst("Roboto"), /* android (broken on some android 10 devices) */
|
||||
String_FromConst("Geneva") /* for ancient macOS versions */
|
||||
String_FromConst("Slate For OnePlus"), /* Android 10, some devices */
|
||||
String_FromConst("Roboto"), /* Android (broken on some Android 10 devices) */
|
||||
String_FromConst("Geneva"), /* for ancient macOS versions */
|
||||
String_FromConst("Droid Sans") /* for old Android versions */
|
||||
};
|
||||
|
||||
void Drawer2D_SetDefaultFont(const cc_string* fontName) {
|
||||
|
@ -973,13 +973,18 @@ void Gfx_CalcOrthoMatrix(float width, float height, struct Matrix* matrix) {
|
||||
matrix->row4.Z = ORTHO_NEAR / (ORTHO_NEAR - ORTHO_FAR);
|
||||
}
|
||||
|
||||
void Gfx_CalcPerspectiveMatrix(float fov, float aspect, float zFar, struct Matrix* matrix) {
|
||||
static float CalcZNear(float fov) {
|
||||
/* With reversed z depth, near Z plane can be much closer (with sufficient depth buffer precision) */
|
||||
/* This reduces clipping with high FOV without sacrificing depth precision for faraway objects */
|
||||
/* However for low FOV, don't reduce near Z in order to gain a bit more depth precision */
|
||||
float zNear = (depthBits < 24 || fov <= 70 * MATH_DEG2RAD) ? 0.05f : 0.001953125f;
|
||||
Matrix_PerspectiveFieldOfView(matrix, fov, aspect, zNear, zFar);
|
||||
if (depthBits < 24 || fov <= 70 * MATH_DEG2RAD) return 0.05f;
|
||||
if (fov <= 100 * MATH_DEG2RAD) return 0.025f;
|
||||
if (fov <= 150 * MATH_DEG2RAD) return 0.0125f;
|
||||
return 0.00390625f;
|
||||
}
|
||||
|
||||
void Gfx_CalcPerspectiveMatrix(float fov, float aspect, float zFar, struct Matrix* matrix) {
|
||||
Matrix_PerspectiveFieldOfView(matrix, fov, aspect, CalcZNear(fov), zFar);
|
||||
/* Adjust the projection matrix to produce reversed Z values */
|
||||
matrix->row3.Z = -matrix->row3.Z - 1.0f;
|
||||
matrix->row4.Z = -matrix->row4.Z;
|
||||
|
23
src/Input.c
@ -68,6 +68,18 @@ static cc_bool AnyBlockTouches(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ClearTouches(void) {
|
||||
int i;
|
||||
for (i = 0; i < INPUT_MAX_POINTERS; i++) touches[i].type = 0;
|
||||
Pointers_Count = 0;
|
||||
}
|
||||
|
||||
void Input_SetTouchMode(cc_bool enabled) {
|
||||
ClearTouches();
|
||||
Input_TouchMode = enabled;
|
||||
Pointers_Count = enabled ? 0 : 1;
|
||||
}
|
||||
|
||||
void Input_AddTouch(long id, int x, int y) {
|
||||
int i;
|
||||
for (i = 0; i < INPUT_MAX_POINTERS; i++) {
|
||||
@ -215,6 +227,9 @@ void Input_SetPressed(int key) {
|
||||
Input_Pressed[key] = true;
|
||||
Event_RaiseInput(&InputEvents.Down, key, wasPressed);
|
||||
|
||||
if (key == 'C' && Key_IsActionPressed()) Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_COPY, 0);
|
||||
if (key == 'V' && Key_IsActionPressed()) Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_PASTE, 0);
|
||||
|
||||
/* don't allow multiple left mouse down events */
|
||||
if (key != KEY_LMOUSE || wasPressed) return;
|
||||
Pointer_SetPressed(0, true);
|
||||
@ -456,7 +471,7 @@ int Hotkeys_FindPartial(int key) {
|
||||
struct HotkeyData hk;
|
||||
int i, modifiers = 0;
|
||||
|
||||
if (Key_IsControlPressed()) modifiers |= HOTKEY_MOD_CTRL;
|
||||
if (Key_IsCtrlPressed()) modifiers |= HOTKEY_MOD_CTRL;
|
||||
if (Key_IsShiftPressed()) modifiers |= HOTKEY_MOD_SHIFT;
|
||||
if (Key_IsAltPressed()) modifiers |= HOTKEY_MOD_ALT;
|
||||
|
||||
@ -806,7 +821,7 @@ cc_bool Input_HandleMouseWheel(float delta) {
|
||||
struct HacksComp* h;
|
||||
cc_bool hotbar;
|
||||
|
||||
hotbar = Key_IsAltPressed() || Key_IsControlPressed() || Key_IsShiftPressed();
|
||||
hotbar = Key_IsAltPressed() || Key_IsCtrlPressed() || Key_IsShiftPressed();
|
||||
if (!hotbar && Camera.Active->Zoom(delta)) return true;
|
||||
if (!KeyBind_IsPressed(KEYBIND_ZOOM_SCROLL)) return false;
|
||||
|
||||
@ -1089,9 +1104,7 @@ static void OnInit(void) {
|
||||
|
||||
static void OnFree(void) {
|
||||
#ifdef CC_BUILD_TOUCH
|
||||
int i;
|
||||
for (i = 0; i < INPUT_MAX_POINTERS; i++) touches[i].type = 0;
|
||||
Pointers_Count = 0;
|
||||
ClearTouches();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
10
src/Input.h
@ -44,7 +44,10 @@ enum InputButtons {
|
||||
|
||||
/* NOTE: RMOUSE must be before MMOUSE for PlayerClick compatibility */
|
||||
KEY_XBUTTON1, KEY_XBUTTON2, KEY_LMOUSE, KEY_RMOUSE, KEY_MMOUSE,
|
||||
INPUT_COUNT
|
||||
INPUT_COUNT,
|
||||
|
||||
INPUT_CLIPBOARD_COPY = 1001,
|
||||
INPUT_CLIPBOARD_PASTE = 1002
|
||||
};
|
||||
|
||||
/* Simple names for each input button. */
|
||||
@ -52,14 +55,14 @@ extern const char* const Input_Names[INPUT_COUNT];
|
||||
|
||||
#define Key_IsWinPressed() (Input_Pressed[KEY_LWIN] || Input_Pressed[KEY_RWIN])
|
||||
#define Key_IsAltPressed() (Input_Pressed[KEY_LALT] || Input_Pressed[KEY_RALT])
|
||||
#define Key_IsControlPressed() (Input_Pressed[KEY_LCTRL] || Input_Pressed[KEY_RCTRL])
|
||||
#define Key_IsCtrlPressed() (Input_Pressed[KEY_LCTRL] || Input_Pressed[KEY_RCTRL])
|
||||
#define Key_IsShiftPressed() (Input_Pressed[KEY_LSHIFT] || Input_Pressed[KEY_RSHIFT])
|
||||
|
||||
#ifdef CC_BUILD_DARWIN
|
||||
/* macOS uses CMD instead of CTRL for clipboard and stuff */
|
||||
#define Key_IsActionPressed() Key_IsWinPressed()
|
||||
#else
|
||||
#define Key_IsActionPressed() Key_IsControlPressed()
|
||||
#define Key_IsActionPressed() Key_IsCtrlPressed()
|
||||
#endif
|
||||
|
||||
/* Pressed state of each input button. Use Input_Set to change. */
|
||||
@ -83,6 +86,7 @@ extern int Pointers_Count;
|
||||
extern cc_bool Input_TapPlace, Input_HoldPlace;
|
||||
/* Whether touch input is being used. */
|
||||
extern cc_bool Input_TouchMode;
|
||||
void Input_SetTouchMode(cc_bool enabled);
|
||||
|
||||
void Input_AddTouch(long id, int x, int y);
|
||||
void Input_UpdateTouch(long id, int x, int y);
|
||||
|
@ -1005,10 +1005,8 @@ static struct CheckResourcesScreen {
|
||||
|
||||
static void CheckResourcesScreen_Yes(void* w, int idx) { FetchResourcesScreen_SetActive(); }
|
||||
static void CheckResourcesScreen_Next(void* w, int idx) {
|
||||
static const cc_string optionsTxt = String_FromConst("options.txt");
|
||||
Http_ClearPending();
|
||||
|
||||
if (File_Exists(&optionsTxt)) {
|
||||
if (Options_LoadResult != ReturnCode_FileNotFound) {
|
||||
MainScreen_SetActive();
|
||||
} else {
|
||||
ChooseModeScreen_SetActive(true);
|
||||
|
32
src/LWeb.c
@ -109,43 +109,47 @@ static void Json_ConsumeObject(struct JsonContext* ctx) {
|
||||
char keyBuffer[STRING_SIZE];
|
||||
cc_string value, oldKey = ctx->curKey;
|
||||
int token;
|
||||
ctx->depth++;
|
||||
ctx->OnNewObject(ctx);
|
||||
|
||||
while (true) {
|
||||
token = Json_ConsumeToken(ctx);
|
||||
if (token == ',') continue;
|
||||
if (token == '}') return;
|
||||
if (token == '}') break;
|
||||
|
||||
if (token != '"') { ctx->failed = true; return; }
|
||||
if (token != '"') { ctx->failed = true; break; }
|
||||
String_InitArray(ctx->curKey, keyBuffer);
|
||||
Json_ConsumeString(ctx, &ctx->curKey);
|
||||
|
||||
token = Json_ConsumeToken(ctx);
|
||||
if (token != ':') { ctx->failed = true; return; }
|
||||
if (token != ':') { ctx->failed = true; break; }
|
||||
|
||||
token = Json_ConsumeToken(ctx);
|
||||
if (token == TOKEN_NONE) { ctx->failed = true; return; }
|
||||
if (token == TOKEN_NONE) { ctx->failed = true; break; }
|
||||
|
||||
value = Json_ConsumeValue(token, ctx);
|
||||
ctx->OnValue(ctx, &value);
|
||||
ctx->curKey = oldKey;
|
||||
}
|
||||
ctx->depth--;
|
||||
}
|
||||
|
||||
static void Json_ConsumeArray(struct JsonContext* ctx) {
|
||||
cc_string value;
|
||||
int token;
|
||||
ctx->depth++;
|
||||
ctx->OnNewArray(ctx);
|
||||
|
||||
while (true) {
|
||||
token = Json_ConsumeToken(ctx);
|
||||
if (token == ',') continue;
|
||||
if (token == ']') return;
|
||||
if (token == ']') break;
|
||||
|
||||
if (token == TOKEN_NONE) { ctx->failed = true; return; }
|
||||
if (token == TOKEN_NONE) { ctx->failed = true; break; }
|
||||
value = Json_ConsumeValue(token, ctx);
|
||||
ctx->OnValue(ctx, &value);
|
||||
}
|
||||
ctx->depth--;
|
||||
}
|
||||
|
||||
static cc_string Json_ConsumeValue(int token, struct JsonContext* ctx) {
|
||||
@ -169,6 +173,7 @@ void Json_Init(struct JsonContext* ctx, STRING_REF char* str, int len) {
|
||||
ctx->left = len;
|
||||
ctx->failed = false;
|
||||
ctx->curKey = String_Empty;
|
||||
ctx->depth = 0;
|
||||
|
||||
ctx->OnNewArray = Json_NullOnNew;
|
||||
ctx->OnNewObject = Json_NullOnNew;
|
||||
@ -421,12 +426,19 @@ void FetchServerTask_Run(const cc_string* hash) {
|
||||
*#########################################################################################################################*/
|
||||
struct FetchServersData FetchServersTask;
|
||||
static void FetchServersTask_Count(struct JsonContext* ctx) {
|
||||
/* JSON is expected in this format: */
|
||||
/* { "servers" : (depth = 1) */
|
||||
/* [ (depth = 2) */
|
||||
/* { server1 }, (depth = 3) */
|
||||
/* { server2 }, (depth = 3) */
|
||||
/* ... */
|
||||
if (ctx->depth != 3) return;
|
||||
FetchServersTask.numServers++;
|
||||
}
|
||||
|
||||
static void FetchServersTask_Next(struct JsonContext* ctx) {
|
||||
if (ctx->depth != 3) return;
|
||||
curServer++;
|
||||
if (curServer < FetchServersTask.servers) return;
|
||||
ServerInfo_Init(curServer);
|
||||
}
|
||||
|
||||
@ -440,8 +452,7 @@ static void FetchServersTask_Handle(cc_uint8* data, cc_uint32 len) {
|
||||
FetchServersTask.servers = NULL;
|
||||
FetchServersTask.orders = NULL;
|
||||
|
||||
/* -1 because servers is surrounded by a { */
|
||||
FetchServersTask.numServers = -1;
|
||||
FetchServersTask.numServers = 0;
|
||||
Json_Handle(data, len, NULL, NULL, FetchServersTask_Count);
|
||||
count = FetchServersTask.numServers;
|
||||
|
||||
@ -449,8 +460,7 @@ static void FetchServersTask_Handle(cc_uint8* data, cc_uint32 len) {
|
||||
FetchServersTask.servers = (struct ServerInfo*)Mem_Alloc(count, sizeof(struct ServerInfo), "servers list");
|
||||
FetchServersTask.orders = (cc_uint16*)Mem_Alloc(count, 2, "servers order");
|
||||
|
||||
/* -2 because servers is surrounded by a { */
|
||||
curServer = FetchServersTask.servers - 2;
|
||||
curServer = FetchServersTask.servers - 1;
|
||||
Json_Handle(data, len, ServerInfo_Parse, NULL, FetchServersTask_Next);
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ struct JsonContext {
|
||||
int left; /* Number of characters left to be inspected. */
|
||||
cc_bool failed; /* Whether there was an error parsing the JSON. */
|
||||
cc_string curKey; /* Key/Name of current member */
|
||||
int depth; /* Object/Array depth (e.g. { { { is depth 3 */
|
||||
|
||||
JsonOnNew OnNewArray; /* Invoked when start of an array is read. */
|
||||
JsonOnNew OnNewObject; /* Invoked when start of an object is read. */
|
||||
|
@ -438,13 +438,16 @@ static void LInput_Unselect(void* widget, int idx) {
|
||||
Window_CloseKeyboard();
|
||||
}
|
||||
|
||||
static void LInput_CopyFromClipboard(cc_string* text, void* widget) {
|
||||
struct LInput* w = (struct LInput*)widget;
|
||||
String_UNSAFE_TrimStart(text);
|
||||
String_UNSAFE_TrimEnd(text);
|
||||
static void LInput_CopyFromClipboard(struct LInput* w) {
|
||||
cc_string text; char textBuffer[2048];
|
||||
String_InitArray(text, textBuffer);
|
||||
|
||||
if (w->ClipboardFilter) w->ClipboardFilter(text);
|
||||
LInput_AppendString(w, text);
|
||||
Clipboard_GetText(&text);
|
||||
String_UNSAFE_TrimStart(&text);
|
||||
String_UNSAFE_TrimEnd(&text);
|
||||
|
||||
if (w->ClipboardFilter) w->ClipboardFilter(&text);
|
||||
LInput_AppendString(w, &text);
|
||||
}
|
||||
|
||||
static void LInput_KeyDown(void* widget, int key, cc_bool was) {
|
||||
@ -453,10 +456,10 @@ static void LInput_KeyDown(void* widget, int key, cc_bool was) {
|
||||
LInput_Backspace(w);
|
||||
} else if (key == KEY_DELETE) {
|
||||
LInput_Delete(w);
|
||||
} else if (key == 'C' && Key_IsActionPressed()) {
|
||||
} else if (key == INPUT_CLIPBOARD_COPY) {
|
||||
if (w->text.length) Clipboard_SetText(&w->text);
|
||||
} else if (key == 'V' && Key_IsActionPressed()) {
|
||||
Clipboard_RequestText(LInput_CopyFromClipboard, w);
|
||||
} else if (key == INPUT_CLIPBOARD_PASTE) {
|
||||
LInput_CopyFromClipboard(w);
|
||||
} else if (key == KEY_ESCAPE) {
|
||||
LInput_Clear(w);
|
||||
} else if (key == KEY_LEFT) {
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
struct StringsBuffer Options;
|
||||
static struct StringsBuffer changedOpts;
|
||||
cc_result Options_LoadResult;
|
||||
|
||||
void Options_Free(void) {
|
||||
StringsBuffer_Clear(&Options);
|
||||
@ -35,8 +36,8 @@ static cc_bool Options_LoadFilter(const cc_string* entry) {
|
||||
void Options_Load(void) {
|
||||
/* Increase from max 512 to 2048 per entry */
|
||||
StringsBuffer_SetLengthBits(&Options, 11);
|
||||
EntryList_Load(&Options, "options-default.txt", '=', NULL);
|
||||
EntryList_Load(&Options, "options.txt", '=', NULL);
|
||||
Options_LoadResult = EntryList_Load(&Options, "options-default.txt", '=', NULL);
|
||||
Options_LoadResult = EntryList_Load(&Options, "options.txt", '=', NULL);
|
||||
}
|
||||
|
||||
void Options_Reload(void) {
|
||||
@ -52,7 +53,7 @@ void Options_Reload(void) {
|
||||
StringsBuffer_Remove(&Options, i);
|
||||
}
|
||||
/* Load only options which have not changed */
|
||||
EntryList_Load(&Options, "options.txt", '=', Options_LoadFilter);
|
||||
Options_LoadResult = EntryList_Load(&Options, "options.txt", '=', Options_LoadFilter);
|
||||
}
|
||||
|
||||
static void SaveOptions(void) {
|
||||
|
@ -72,6 +72,7 @@
|
||||
#define OPT_TOUCH_BUTTONS "gui-touchbuttons"
|
||||
#define OPT_TOUCH_SCALE "gui-touchscale"
|
||||
#define OPT_HTTP_ONLY "http-no-https"
|
||||
#define OPT_RAW_INPUT "win-raw-input"
|
||||
|
||||
#define LOPT_SESSION "launcher-session"
|
||||
#define LOPT_USERNAME "launcher-cc-username"
|
||||
@ -87,6 +88,7 @@
|
||||
|
||||
struct StringsBuffer;
|
||||
extern struct StringsBuffer Options;
|
||||
extern cc_result Options_LoadResult;
|
||||
/* Frees any memory allocated in storing options. */
|
||||
void Options_Free(void);
|
||||
|
||||
|
129
src/Platform.c
@ -357,48 +357,68 @@ int File_Exists(const cc_string* path) {
|
||||
return attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY);
|
||||
}
|
||||
|
||||
static cc_result Directory_EnumCore(const cc_string* dirPath, const cc_string* file, DWORD attribs,
|
||||
void* obj, Directory_EnumCallback callback) {
|
||||
cc_string path; char pathBuffer[MAX_PATH + 10];
|
||||
/* ignore . and .. entry */
|
||||
if (file->length == 1 && file->buffer[0] == '.') return 0;
|
||||
if (file->length == 2 && file->buffer[0] == '.' && file->buffer[1] == '.') return 0;
|
||||
|
||||
String_InitArray(path, pathBuffer);
|
||||
String_Format2(&path, "%s/%s", dirPath, file);
|
||||
|
||||
if (attribs & FILE_ATTRIBUTE_DIRECTORY) return Directory_Enum(&path, obj, callback);
|
||||
callback(&path, obj);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cc_result Directory_Enum(const cc_string* dirPath, void* obj, Directory_EnumCallback callback) {
|
||||
cc_string path; char pathBuffer[MAX_PATH + 10];
|
||||
WCHAR str[NATIVE_STR_LEN];
|
||||
WCHAR* src;
|
||||
WIN32_FIND_DATAW entry;
|
||||
WIN32_FIND_DATAW eW;
|
||||
WIN32_FIND_DATAA eA;
|
||||
int i, ansi = false;
|
||||
HANDLE find;
|
||||
cc_result res;
|
||||
int i;
|
||||
|
||||
/* Need to append \* to search for files in directory */
|
||||
String_InitArray(path, pathBuffer);
|
||||
String_Format1(&path, "%s\\*", dirPath);
|
||||
Platform_EncodeUtf16(str, &path);
|
||||
|
||||
find = FindFirstFileW(str, &entry);
|
||||
if (find == INVALID_HANDLE_VALUE) return GetLastError();
|
||||
find = FindFirstFileW(str, &eW);
|
||||
if (!find || find == INVALID_HANDLE_VALUE) {
|
||||
if ((res = GetLastError()) != ERROR_CALL_NOT_IMPLEMENTED) return res;
|
||||
ansi = true;
|
||||
|
||||
/* Windows 9x does not support W API functions */
|
||||
Platform_Utf16ToAnsi(str);
|
||||
find = FindFirstFileA((LPCSTR)str, &eA);
|
||||
if (find == INVALID_HANDLE_VALUE) return GetLastError();
|
||||
}
|
||||
|
||||
if (ansi) {
|
||||
do {
|
||||
path.length = 0;
|
||||
String_Format1(&path, "%s/", dirPath);
|
||||
|
||||
/* ignore . and .. entry */
|
||||
src = entry.cFileName;
|
||||
if (src[0] == '.' && src[1] == '\0') continue;
|
||||
if (src[0] == '.' && src[1] == '.' && src[2] == '\0') continue;
|
||||
|
||||
for (i = 0; i < MAX_PATH && src[i]; i++) {
|
||||
/* TODO: UTF16 to codepoint conversion */
|
||||
String_Append(&path, Convert_CodepointToCP437(src[i]));
|
||||
for (i = 0; i < MAX_PATH && eA.cFileName[i]; i++) {
|
||||
String_Append(&path, Convert_CodepointToCP437(eA.cFileName[i]));
|
||||
}
|
||||
|
||||
if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
res = Directory_Enum(&path, obj, callback);
|
||||
if (res) { FindClose(find); return res; }
|
||||
if ((res = Directory_EnumCore(dirPath, &path, eA.dwFileAttributes, obj, callback))) return res;
|
||||
} while (FindNextFileA(find, &eA));
|
||||
} else {
|
||||
callback(&path, obj);
|
||||
do {
|
||||
path.length = 0;
|
||||
for (i = 0; i < MAX_PATH && eW.cFileName[i]; i++) {
|
||||
/* TODO: UTF16 to codepoint conversion */
|
||||
String_Append(&path, Convert_CodepointToCP437(eW.cFileName[i]));
|
||||
}
|
||||
if ((res = Directory_EnumCore(dirPath, &path, eW.dwFileAttributes, obj, callback))) return res;
|
||||
} while (FindNextFileW(find, &eW));
|
||||
}
|
||||
} while (FindNextFileW(find, &entry));
|
||||
|
||||
res = GetLastError(); /* return code from FindNextFile */
|
||||
FindClose(find);
|
||||
return res == ERROR_NO_MORE_FILES ? 0 : GetLastError();
|
||||
return res == ERROR_NO_MORE_FILES ? 0 : res;
|
||||
}
|
||||
|
||||
static cc_result DoFile(cc_file* file, const cc_string* path, DWORD access, DWORD createMode) {
|
||||
@ -1021,19 +1041,40 @@ cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) {
|
||||
*-----------------------------------------------------Process/Module------------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
#if defined CC_BUILD_WIN
|
||||
static cc_result Process_RawStart(WCHAR* path, WCHAR* args) {
|
||||
static cc_result Process_RawGetExePath(WCHAR* path, int* len) {
|
||||
*len = GetModuleFileNameW(NULL, path, NATIVE_STR_LEN);
|
||||
return *len ? 0 : GetLastError();
|
||||
}
|
||||
|
||||
cc_result Process_StartGame(const cc_string* args) {
|
||||
WCHAR path[NATIVE_STR_LEN + 1], raw[NATIVE_STR_LEN];
|
||||
cc_string argv; char argvBuffer[NATIVE_STR_LEN];
|
||||
STARTUPINFOW si = { 0 };
|
||||
PROCESS_INFORMATION pi = { 0 };
|
||||
cc_result res;
|
||||
int len;
|
||||
|
||||
Process_RawGetExePath(path, &len);
|
||||
path[len] = '\0';
|
||||
si.cb = sizeof(STARTUPINFOW);
|
||||
|
||||
if (CreateProcessW(path, args, NULL, NULL,
|
||||
false, 0, NULL, NULL, &si, &pi)) goto success;
|
||||
//if ((res = GetLastError()) != ERROR_CALL_NOT_IMPLEMENTED) return res;
|
||||
String_InitArray(argv, argvBuffer);
|
||||
/* Game doesn't actually care about argv[0] */
|
||||
String_Format1(&argv, "cc %s", args);
|
||||
String_UNSAFE_TrimEnd(&argv);
|
||||
Platform_EncodeUtf16(raw, &argv);
|
||||
|
||||
//Platform_Utf16ToAnsi(path);
|
||||
//if (CreateProcessA((LPCSTR)path, args, NULL, NULL,
|
||||
// false, 0, NULL, NULL, &si, &pi)) goto success;
|
||||
if (CreateProcessW(path, raw, NULL, NULL,
|
||||
false, 0, NULL, NULL, &si, &pi)) goto success;
|
||||
if ((res = GetLastError()) != ERROR_CALL_NOT_IMPLEMENTED) return res;
|
||||
|
||||
/* Windows 9x does not support W API functions */
|
||||
len = GetModuleFileNameA(NULL, (LPSTR)path, NATIVE_STR_LEN);
|
||||
((char*)path)[len] = '\0';
|
||||
Platform_Utf16ToAnsi(raw);
|
||||
|
||||
if (CreateProcessA((LPCSTR)path, (LPSTR)raw, NULL, NULL,
|
||||
false, 0, NULL, NULL, &si, &pi)) goto success;
|
||||
return GetLastError();
|
||||
|
||||
success:
|
||||
@ -1043,27 +1084,7 @@ success:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static cc_result Process_RawGetExePath(WCHAR* path, int* len) {
|
||||
*len = GetModuleFileNameW(NULL, path, NATIVE_STR_LEN);
|
||||
return *len ? 0 : GetLastError();
|
||||
}
|
||||
|
||||
cc_result Process_StartGame(const cc_string* args) {
|
||||
cc_string argv; char argvBuffer[NATIVE_STR_LEN];
|
||||
WCHAR raw[NATIVE_STR_LEN], path[NATIVE_STR_LEN + 1];
|
||||
int len;
|
||||
|
||||
cc_result res = Process_RawGetExePath(path, &len);
|
||||
if (res) return res;
|
||||
path[len] = '\0';
|
||||
|
||||
String_InitArray(argv, argvBuffer);
|
||||
String_Format1(&argv, "ClassiCube.exe %s", args);
|
||||
Platform_EncodeUtf16(raw, &argv);
|
||||
return Process_RawStart(path, raw);
|
||||
}
|
||||
void Process_Exit(cc_result code) { ExitProcess(code); }
|
||||
|
||||
void Process_StartOpen(const cc_string* args) {
|
||||
WCHAR str[NATIVE_STR_LEN];
|
||||
Platform_EncodeUtf16(str, args);
|
||||
@ -1272,7 +1293,6 @@ cc_bool Updater_Clean(void) {
|
||||
|
||||
cc_result Updater_Start(const char** action) {
|
||||
WCHAR path[NATIVE_STR_LEN + 1];
|
||||
WCHAR args[2] = { 'a', '\0' }; /* don't actually care about arguments */
|
||||
cc_result res;
|
||||
int len = 0;
|
||||
|
||||
@ -1286,7 +1306,7 @@ cc_result Updater_Start(const char** action) {
|
||||
if (!MoveFileExW(UPDATE_SRC, path, MOVEFILE_REPLACE_EXISTING)) return GetLastError();
|
||||
|
||||
*action = "Restarting game";
|
||||
return Process_RawStart(path, args);
|
||||
return Process_StartGame(&String_Empty);
|
||||
}
|
||||
|
||||
cc_result Updater_GetBuildTime(cc_uint64* timestamp) {
|
||||
@ -1343,10 +1363,7 @@ cc_result Updater_GetBuildTime(cc_uint64* t) { return ERR_NOT_SUPPORTED; }
|
||||
cc_result Updater_GetBuildTime(cc_uint64* t) {
|
||||
JNIEnv* env;
|
||||
JavaGetCurrentEnv(env);
|
||||
|
||||
/* https://developer.android.com/reference/java/io/File#lastModified() */
|
||||
/* lastModified is returned in milliseconds */
|
||||
*t = JavaCallLong(env, "getApkUpdateTime", "()J", NULL) / 1000;
|
||||
*t = JavaCallLong(env, "getApkUpdateTime", "()J", NULL);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@ -1565,7 +1582,7 @@ void Platform_Utf16ToAnsi(void* data) {
|
||||
WCHAR* src = (WCHAR*)data;
|
||||
char* dst = (char*)data;
|
||||
|
||||
while (*src) { *dst++ = *src++; }
|
||||
while (*src) { *dst++ = (char)(*src++); }
|
||||
*dst = '\0';
|
||||
}
|
||||
|
||||
|
@ -737,9 +737,12 @@ static void Classic_Reset(void) {
|
||||
}
|
||||
|
||||
static void Classic_Tick(void) {
|
||||
struct Entity* p = &LocalPlayer_Instance.Base;
|
||||
struct LocalPlayer* p = &LocalPlayer_Instance;
|
||||
struct Entity* e = &LocalPlayer_Instance.Base;
|
||||
if (!classic_receivedFirstPos) return;
|
||||
Classic_WritePosition(p->Position, p->Yaw, p->Pitch);
|
||||
/* Report end position of each physics tick, rather than current position */
|
||||
/* (otherwise can miss landing on a block then jumping off of it again) */
|
||||
Classic_WritePosition(p->Interp.Next.Pos, e->Yaw, e->Pitch);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1448,7 +1448,7 @@ static int InventoryScreen_PointerDown(void* screen, int id, int x, int y) {
|
||||
handled = Elem_HandlesPointerDown(table, id, x, y);
|
||||
|
||||
if (!handled || table->pendingClose) {
|
||||
hotbar = Key_IsControlPressed() || Key_IsShiftPressed();
|
||||
hotbar = Key_IsCtrlPressed() || Key_IsShiftPressed();
|
||||
if (!hotbar) Gui_Remove((struct Screen*)s);
|
||||
}
|
||||
return TOUCH_TYPE_GUI;
|
||||
@ -1467,7 +1467,7 @@ static int InventoryScreen_PointerMove(void* screen, int id, int x, int y) {
|
||||
static int InventoryScreen_MouseScroll(void* screen, float delta) {
|
||||
struct InventoryScreen* s = (struct InventoryScreen*)screen;
|
||||
|
||||
cc_bool hotbar = Key_IsAltPressed() || Key_IsControlPressed() || Key_IsShiftPressed();
|
||||
cc_bool hotbar = Key_IsAltPressed() || Key_IsCtrlPressed() || Key_IsShiftPressed();
|
||||
if (hotbar) return false;
|
||||
return Elem_HandlesMouseScroll(&s->table, delta);
|
||||
}
|
||||
|
11
src/Utils.c
@ -222,7 +222,7 @@ int Convert_FromBase64(const char* src, int len, cc_uint8* dst) {
|
||||
/*########################################################################################################################*
|
||||
*--------------------------------------------------------EntryList--------------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
void EntryList_Load(struct StringsBuffer* list, const char* file, char separator, EntryList_Filter filter) {
|
||||
cc_result EntryList_Load(struct StringsBuffer* list, const char* file, char separator, EntryList_Filter filter) {
|
||||
cc_string entry; char entryBuffer[1024];
|
||||
cc_string path;
|
||||
cc_string key, value;
|
||||
@ -236,8 +236,8 @@ void EntryList_Load(struct StringsBuffer* list, const char* file, char separator
|
||||
maxLen = list->_lenMask ? list->_lenMask : STRINGSBUFFER_DEF_LEN_MASK;
|
||||
|
||||
res = Stream_OpenFile(&stream, &path);
|
||||
if (res == ReturnCode_FileNotFound) return;
|
||||
if (res) { Logger_SysWarn2(res, "opening", &path); return; }
|
||||
if (res == ReturnCode_FileNotFound) return res;
|
||||
if (res) { Logger_SysWarn2(res, "opening", &path); return res; }
|
||||
|
||||
/* ReadLine reads single byte at a time */
|
||||
Stream_ReadonlyBuffered(&buffered, &stream, buffer, sizeof(buffer));
|
||||
@ -274,10 +274,11 @@ void EntryList_Load(struct StringsBuffer* list, const char* file, char separator
|
||||
|
||||
res = stream.Close(&stream);
|
||||
if (res) { Logger_SysWarn2(res, "closing", &path); }
|
||||
return res;
|
||||
}
|
||||
|
||||
void EntryList_UNSAFE_Load(struct StringsBuffer* list, const char* file) {
|
||||
EntryList_Load(list, file, '\0', NULL);
|
||||
cc_result EntryList_UNSAFE_Load(struct StringsBuffer* list, const char* file) {
|
||||
return EntryList_Load(list, file, '\0', NULL);
|
||||
}
|
||||
|
||||
void EntryList_Save(struct StringsBuffer* list, const char* file) {
|
||||
|
@ -59,10 +59,10 @@ typedef cc_bool (*EntryList_Filter)(const cc_string* entry);
|
||||
/* Loads the entries from disc. */
|
||||
/* NOTE: If separator is \0, does NOT check for duplicate keys when loading. */
|
||||
/* filter can be used to optionally skip loading some entries from the file. */
|
||||
CC_NOINLINE void EntryList_Load(struct StringsBuffer* list, const char* file, char separator, EntryList_Filter filter);
|
||||
CC_NOINLINE cc_result EntryList_Load(struct StringsBuffer* list, const char* file, char separator, EntryList_Filter filter);
|
||||
/* Shortcut for EntryList_Load with separator of \0 and filter of NULL */
|
||||
/* NOTE: Does NOT check for duplicate keys */
|
||||
CC_NOINLINE void EntryList_UNSAFE_Load(struct StringsBuffer* list, const char* file);
|
||||
CC_NOINLINE cc_result EntryList_UNSAFE_Load(struct StringsBuffer* list, const char* file);
|
||||
/* Saves the entries in the given list to disc. */
|
||||
CC_NOINLINE void EntryList_Save(struct StringsBuffer* list, const char* file);
|
||||
/* Removes the entry whose key caselessly equals the given key. */
|
||||
|
@ -1216,18 +1216,20 @@ static void InputWidget_EndKey(struct InputWidget* w) {
|
||||
InputWidget_UpdateCaret(w);
|
||||
}
|
||||
|
||||
static void InputWidget_CopyFromClipboard(cc_string* text, void* w) {
|
||||
InputWidget_AppendText((struct InputWidget*)w, text);
|
||||
static void InputWidget_CopyFromClipboard(struct InputWidget* w) {
|
||||
cc_string text; char textBuffer[2048];
|
||||
String_InitArray(text, textBuffer);
|
||||
|
||||
Clipboard_GetText(&text);
|
||||
InputWidget_AppendText(w, &text);
|
||||
}
|
||||
|
||||
static cc_bool InputWidget_OtherKey(struct InputWidget* w, int key) {
|
||||
int maxChars = w->GetMaxLines() * INPUTWIDGET_LEN;
|
||||
if (!Key_IsActionPressed()) return false;
|
||||
|
||||
if (key == 'V' && w->text.length < maxChars) {
|
||||
Clipboard_RequestText(InputWidget_CopyFromClipboard, w);
|
||||
if (key == INPUT_CLIPBOARD_PASTE && w->text.length < maxChars) {
|
||||
InputWidget_CopyFromClipboard(w);
|
||||
return true;
|
||||
} else if (key == 'C') {
|
||||
} else if (key == INPUT_CLIPBOARD_COPY) {
|
||||
if (!w->text.length) return true;
|
||||
Clipboard_SetText(&w->text);
|
||||
return true;
|
||||
|
157
src/Window.c
@ -15,16 +15,8 @@ struct _WinData WindowInfo;
|
||||
|
||||
int Display_ScaleX(int x) { return (int)(x * DisplayInfo.ScaleX); }
|
||||
int Display_ScaleY(int y) { return (int)(y * DisplayInfo.ScaleY); }
|
||||
|
||||
#ifndef CC_BUILD_WEB
|
||||
void Clipboard_RequestText(RequestClipboardCallback callback, void* obj) {
|
||||
cc_string text; char textBuffer[2048];
|
||||
String_InitArray(text, textBuffer);
|
||||
|
||||
Clipboard_GetText(&text);
|
||||
callback(&text, obj);
|
||||
}
|
||||
#endif
|
||||
#define Display_CentreX(width) (DisplayInfo.X + (DisplayInfo.Width - width) / 2)
|
||||
#define Display_CentreY(height) (DisplayInfo.Y + (DisplayInfo.Height - height) / 2)
|
||||
|
||||
#if defined CC_BUILD_IOS
|
||||
/* iOS implements these functions in external interop_ios.m file */
|
||||
@ -982,7 +974,9 @@ static void InitRawMouse(void) {
|
||||
_getRawInputData = (FUNC_GetRawInputData) DynamicLib_Get2(lib, "GetRawInputData");
|
||||
rawMouseSupported = _registerRawInput && _getRawInputData;
|
||||
}
|
||||
if (!rawMouseSupported) { Platform_LogConst("Raw input unsupported!"); return; }
|
||||
|
||||
rawMouseSupported &= Options_GetBool(OPT_RAW_INPUT, true);
|
||||
if (!rawMouseSupported) { Platform_LogConst("## Raw input unsupported!"); return; }
|
||||
|
||||
rid.usUsagePage = 1; /* HID_USAGE_PAGE_GENERIC; */
|
||||
rid.usUsage = 2; /* HID_USAGE_GENERIC_MOUSE; */
|
||||
@ -2920,7 +2914,7 @@ static const char* OnBeforeUnload(int type, const void* ev, void *data) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int MapNativeKey(int k) {
|
||||
static int MapNativeKey(int k, int l) {
|
||||
if (k >= '0' && k <= '9') return k;
|
||||
if (k >= 'A' && k <= 'Z') return k;
|
||||
if (k >= DOM_VK_F1 && k <= DOM_VK_F24) { return KEY_F1 + (k - DOM_VK_F1); }
|
||||
@ -2929,10 +2923,10 @@ static int MapNativeKey(int k) {
|
||||
switch (k) {
|
||||
case DOM_VK_BACK_SPACE: return KEY_BACKSPACE;
|
||||
case DOM_VK_TAB: return KEY_TAB;
|
||||
case DOM_VK_RETURN: return KEY_ENTER;
|
||||
case DOM_VK_SHIFT: return KEY_LSHIFT;
|
||||
case DOM_VK_CONTROL: return KEY_LCTRL;
|
||||
case DOM_VK_ALT: return KEY_LALT;
|
||||
case DOM_VK_RETURN: return l == DOM_KEY_LOCATION_NUMPAD ? KEY_KP_ENTER : KEY_ENTER;
|
||||
case DOM_VK_SHIFT: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RSHIFT : KEY_LSHIFT;
|
||||
case DOM_VK_CONTROL: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RCTRL : KEY_LCTRL;
|
||||
case DOM_VK_ALT: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RALT : KEY_LALT;
|
||||
case DOM_VK_PAUSE: return KEY_PAUSE;
|
||||
case DOM_VK_CAPS_LOCK: return KEY_CAPSLOCK;
|
||||
case DOM_VK_ESCAPE: return KEY_ESCAPE;
|
||||
@ -2952,7 +2946,7 @@ static int MapNativeKey(int k) {
|
||||
|
||||
case DOM_VK_SEMICOLON: return KEY_SEMICOLON;
|
||||
case DOM_VK_EQUALS: return KEY_EQUALS;
|
||||
case DOM_VK_WIN: return KEY_LWIN;
|
||||
case DOM_VK_WIN: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RWIN : KEY_LWIN;
|
||||
case DOM_VK_MULTIPLY: return KEY_KP_MULTIPLY;
|
||||
case DOM_VK_ADD: return KEY_KP_PLUS;
|
||||
case DOM_VK_SUBTRACT: return KEY_KP_MINUS;
|
||||
@ -2979,34 +2973,21 @@ static int MapNativeKey(int k) {
|
||||
return KEY_NONE;
|
||||
}
|
||||
|
||||
static EM_BOOL OnKey(int type, const EmscriptenKeyboardEvent* ev, void* data) {
|
||||
int key = MapNativeKey(ev->keyCode);
|
||||
static EM_BOOL OnKeyDown(int type, const EmscriptenKeyboardEvent* ev, void* data) {
|
||||
int key = MapNativeKey(ev->keyCode, ev->location);
|
||||
/* iOS safari still sends backspace key events, don't intercept those */
|
||||
if (key == KEY_BACKSPACE && Input_TouchMode && keyboardOpen) return false;
|
||||
|
||||
if (ev->location == DOM_KEY_LOCATION_RIGHT) {
|
||||
switch (key) {
|
||||
case KEY_LALT: key = KEY_RALT; break;
|
||||
case KEY_LCTRL: key = KEY_RCTRL; break;
|
||||
case KEY_LSHIFT: key = KEY_RSHIFT; break;
|
||||
case KEY_LWIN: key = KEY_RWIN; break;
|
||||
}
|
||||
}
|
||||
else if (ev->location == DOM_KEY_LOCATION_NUMPAD) {
|
||||
switch (key) {
|
||||
case KEY_ENTER: key = KEY_KP_ENTER; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (key) Input_Set(key, type == EMSCRIPTEN_EVENT_KEYDOWN);
|
||||
if (key) Input_SetPressed(key);
|
||||
DeferredEnableRawMouse();
|
||||
|
||||
if (!key) return false;
|
||||
/* KeyUp always intercepted */
|
||||
if (type != EMSCRIPTEN_EVENT_KEYDOWN) return true;
|
||||
|
||||
/* If holding down Ctrl or Alt, keys aren't going to generate a KeyPress event anyways. */
|
||||
/* This intercepts Ctrl+S etc. Ctrl+C and Ctrl+V are not intercepted for clipboard. */
|
||||
if (Key_IsAltPressed() || Key_IsWinPressed()) return true;
|
||||
if (Key_IsControlPressed() && key != 'C' && key != 'V') return true;
|
||||
/* NOTE: macOS uses Win (Command) key instead of Ctrl, have to account for that too */
|
||||
if (Key_IsAltPressed()) return true;
|
||||
if (Key_IsWinPressed()) return key != 'C' && key != 'V';
|
||||
if (Key_IsCtrlPressed()) return key != 'C' && key != 'V';
|
||||
|
||||
/* Space needs special handling, as intercepting this prevents the ' ' key press event */
|
||||
/* But on Safari, space scrolls the page - so need to intercept when keyboard is NOT open */
|
||||
@ -3019,6 +3000,13 @@ static EM_BOOL OnKey(int type, const EmscriptenKeyboardEvent* ev, void* data) {
|
||||
(key >= KEY_INSERT && key <= KEY_MENU) || (key >= KEY_ENTER && key <= KEY_NUMLOCK);
|
||||
}
|
||||
|
||||
static EM_BOOL OnKeyUp(int type, const EmscriptenKeyboardEvent* ev, void* data) {
|
||||
int key = MapNativeKey(ev->keyCode, ev->location);
|
||||
if (key) Input_SetReleased(key);
|
||||
DeferredEnableRawMouse();
|
||||
return key != KEY_NONE;
|
||||
}
|
||||
|
||||
static EM_BOOL OnKeyPress(int type, const EmscriptenKeyboardEvent* ev, void* data) {
|
||||
char keyChar;
|
||||
DeferredEnableRawMouse();
|
||||
@ -3032,6 +3020,10 @@ static EM_BOOL OnKeyPress(int type, const EmscriptenKeyboardEvent* ev, void* dat
|
||||
/* have these intercepted key presses in its text buffer) */
|
||||
if (Input_TouchMode && keyboardOpen) return false;
|
||||
|
||||
/* Safari on macOS still sends a keypress event, which must not be cancelled */
|
||||
/* (otherwise copy/paste doesn't work, as it uses Win+C / Win+V) */
|
||||
if (ev->metaKey) return false;
|
||||
|
||||
if (Convert_TryCodepointToCP437(ev->charCode, &keyChar)) {
|
||||
Event_RaiseInt(&InputEvents.Press, keyChar);
|
||||
}
|
||||
@ -3056,8 +3048,8 @@ static void HookEvents(void) {
|
||||
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnResize);
|
||||
emscripten_set_beforeunload_callback( NULL, OnBeforeUnload);
|
||||
|
||||
emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKey);
|
||||
emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKey);
|
||||
emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyDown);
|
||||
emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyUp);
|
||||
emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyPress);
|
||||
|
||||
emscripten_set_touchstart_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnTouchStart);
|
||||
@ -3101,19 +3093,24 @@ void Window_Init(void) {
|
||||
EM_ASM(window.addEventListener('copy',
|
||||
function(e) {
|
||||
if (window.getSelection && window.getSelection().toString()) return;
|
||||
if (window.cc_copyText) {
|
||||
if (e.clipboardData) { e.clipboardData.setData('text/plain', window.cc_copyText); }
|
||||
ccall('Window_RequestClipboardText', 'void');
|
||||
if (!window.cc_copyText) return;
|
||||
|
||||
if (e.clipboardData) {
|
||||
e.clipboardData.setData('text/plain', window.cc_copyText);
|
||||
e.preventDefault();
|
||||
window.cc_copyText = null;
|
||||
}
|
||||
window.cc_copyText = null;
|
||||
});
|
||||
);
|
||||
|
||||
/* Paste text (window.clipboardData is handled in Clipboard_RequestText instead) */
|
||||
/* Paste text (window.clipboardData is handled in Clipboard_GetText instead) */
|
||||
EM_ASM(window.addEventListener('paste',
|
||||
function(e) {
|
||||
var contents = e.clipboardData ? e.clipboardData.getData('text/plain') : "";
|
||||
if (e.clipboardData) {
|
||||
var contents = e.clipboardData.getData('text/plain');
|
||||
ccall('Window_GotClipboardText', 'void', ['string'], [contents]);
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
@ -3124,8 +3121,7 @@ void Window_Init(void) {
|
||||
return /iPhone|iPad|iPod/i.test(navigator.userAgent) ||
|
||||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints && navigator.maxTouchPoints > 2);
|
||||
});
|
||||
Input_TouchMode = is_ios || droid;
|
||||
Pointers_Count = Input_TouchMode ? 0 : 1;
|
||||
Input_SetTouchMode(is_ios || droid);
|
||||
|
||||
/* iOS shifts the whole webpage up when opening chat, which causes problems */
|
||||
/* as the chat/send butons are positioned at the top of the canvas - they */
|
||||
@ -3169,20 +3165,33 @@ void Window_SetTitle(const cc_string* title) {
|
||||
EM_ASM_({ document.title = UTF8ToString($0); }, str);
|
||||
}
|
||||
|
||||
static RequestClipboardCallback clipboard_func;
|
||||
static void* clipboard_obj;
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE void Window_GotClipboardText(char* src) {
|
||||
cc_string str; char strBuffer[512];
|
||||
if (!clipboard_func) return;
|
||||
|
||||
String_InitArray(str, strBuffer);
|
||||
String_AppendUtf8(&str, src, String_CalcLen(src, 2048));
|
||||
clipboard_func(&str, clipboard_obj);
|
||||
clipboard_func = NULL;
|
||||
static char pasteBuffer[512];
|
||||
static cc_string pasteStr;
|
||||
EMSCRIPTEN_KEEPALIVE void Window_RequestClipboardText(void) {
|
||||
Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_COPY, 0);
|
||||
}
|
||||
|
||||
void Clipboard_GetText(cc_string* value) { }
|
||||
EMSCRIPTEN_KEEPALIVE void Window_StoreClipboardText(char* src) {
|
||||
String_InitArray(pasteStr, pasteBuffer);
|
||||
String_AppendUtf8(&pasteStr, src, String_CalcLen(src, 2048));
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE void Window_GotClipboardText(char* src) {
|
||||
Window_StoreClipboardText(src);
|
||||
Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_PASTE, 0);
|
||||
}
|
||||
|
||||
void Clipboard_GetText(cc_string* value) {
|
||||
/* For IE11, use window.clipboardData to get the clipboard */
|
||||
EM_ASM_({
|
||||
if (window.clipboardData) {
|
||||
var contents = window.clipboardData.getData('Text');
|
||||
ccall('Window_StoreClipboardText', 'void', ['string'], [contents]);
|
||||
}
|
||||
});
|
||||
String_Copy(value, &pasteStr);
|
||||
pasteStr.length = 0;
|
||||
}
|
||||
void Clipboard_SetText(const cc_string* value) {
|
||||
char str[NATIVE_STR_LEN];
|
||||
Platform_EncodeUtf8(str, value);
|
||||
@ -3199,18 +3208,6 @@ void Clipboard_SetText(const cc_string* value) {
|
||||
}, str);
|
||||
}
|
||||
|
||||
void Clipboard_RequestText(RequestClipboardCallback callback, void* obj) {
|
||||
clipboard_func = callback;
|
||||
clipboard_obj = obj;
|
||||
|
||||
/* For IE11, use window.clipboardData to get the clipboard */
|
||||
EM_ASM_({
|
||||
if (!window.clipboardData) return;
|
||||
var contents = window.clipboardData.getData('Text');
|
||||
ccall('Window_GotClipboardText', 'void', ['string'], [contents]);
|
||||
});
|
||||
}
|
||||
|
||||
void Window_Show(void) { }
|
||||
|
||||
int Window_GetWindowState(void) {
|
||||
@ -3672,7 +3669,8 @@ void Window_Init(void) {
|
||||
JavaRegisterNatives(env, methods);
|
||||
|
||||
WindowInfo.SoftKeyboard = SOFT_KEYBOARD_RESIZE;
|
||||
Input_TouchMode = true;
|
||||
Input_SetTouchMode(true);
|
||||
|
||||
DisplayInfo.Depth = 32;
|
||||
DisplayInfo.ScaleX = JavaCallFloat(env, "getDpiX", "()F", NULL);
|
||||
DisplayInfo.ScaleY = JavaCallFloat(env, "getDpiY", "()F", NULL);
|
||||
@ -3820,13 +3818,13 @@ void Window_FreeFramebuffer(struct Bitmap* bmp) {
|
||||
Mem_Free(bmp->scan0);
|
||||
}
|
||||
|
||||
void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) {
|
||||
void Window_OpenKeyboard(const struct OpenKeyboardArgs* kArgs) {
|
||||
JNIEnv* env;
|
||||
jvalue args[2];
|
||||
JavaGetCurrentEnv(env);
|
||||
|
||||
args[0].l = JavaMakeString(env, args->text);
|
||||
args[1].i = args->type;
|
||||
args[0].l = JavaMakeString(env, kArgs->text);
|
||||
args[1].i = kArgs->type;
|
||||
JavaCallVoid(env, "openKeyboard", "(Ljava/lang/String;I)V", args);
|
||||
(*env)->DeleteLocalRef(env, args[0].l);
|
||||
}
|
||||
@ -4214,12 +4212,11 @@ static XVisualInfo GLContext_SelectVisual(void) {
|
||||
|
||||
InitGraphicsMode(&mode);
|
||||
GetAttribs(&mode, attribs, GLCONTEXT_DEFAULT_DEPTH);
|
||||
if (!glXQueryVersion(win_display, &major, &minor)) {
|
||||
Logger_Abort("glXQueryVersion failed");
|
||||
}
|
||||
screen = DefaultScreen(win_display);
|
||||
|
||||
if (major >= 1 && minor >= 3) {
|
||||
if (!glXQueryVersion(win_display, &major, &minor)) {
|
||||
Platform_LogConst("glXQueryVersion failed");
|
||||
} else if (major >= 1 && minor >= 3) {
|
||||
/* ChooseFBConfig returns an array of GLXFBConfig opaque structures */
|
||||
fbconfigs = glXChooseFBConfig(win_display, screen, attribs, &fbcount);
|
||||
if (fbconfigs && fbcount) {
|
||||
|
@ -84,15 +84,14 @@ void Window_Create(int width, int height);
|
||||
/* Sets the text of the titlebar above the window. */
|
||||
CC_API void Window_SetTitle(const cc_string* title);
|
||||
|
||||
typedef void (*RequestClipboardCallback)(cc_string* value, void* obj);
|
||||
/* Gets the text currently on the clipboard. */
|
||||
/* NOTE: On most platforms this function can be called at any time. */
|
||||
/* In web backend, can only be called during INPUT_CLIPBOARD_PASTE event. */
|
||||
CC_API void Clipboard_GetText(cc_string* value);
|
||||
/* Sets the text currently on the clipboard. */
|
||||
/* NOTE: On most platforms this function can be called at any time. */
|
||||
/* In web backend, can only be called during INPUT_CLIPBOARD_COPY event. */
|
||||
CC_API void Clipboard_SetText(const cc_string* value);
|
||||
/* Calls a callback function when text is retrieved from the clipboard. */
|
||||
/* NOTE: On most platforms this just calls Clipboard_GetText. */
|
||||
/* With emscripten however, the callback is instead called when a 'paste' event arrives. */
|
||||
void Clipboard_RequestText(RequestClipboardCallback callback, void* obj);
|
||||
|
||||
/* Makes the window visible and focussed on screen. */
|
||||
void Window_Show(void);
|
||||
|