chore(v2): vendor dependencies for offline/China builds
go mod vendor pins onnxruntime_go v1.12.1, Gio and the rest into v2/vendor so go run/build work without hitting proxy.golang.org (blocked/slow in China). Verified: CGO_ENABLED=1 go build -mod=vendor ./internal/spike and GOOS=windows go build -mod=vendor ./internal/ui both pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+68
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package org.gioui;
|
||||
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public final class Gio {
|
||||
private static final Object initLock = new Object();
|
||||
private static boolean jniLoaded;
|
||||
private static final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
/**
|
||||
* init loads and initializes the Go native library and runs
|
||||
* the Go main function.
|
||||
*
|
||||
* It is exported for use by Android apps that need to run Go code
|
||||
* outside the lifecycle of the Gio activity.
|
||||
*/
|
||||
public static synchronized void init(Context appCtx) {
|
||||
synchronized (initLock) {
|
||||
if (jniLoaded) {
|
||||
return;
|
||||
}
|
||||
String dataDir = appCtx.getFilesDir().getAbsolutePath();
|
||||
byte[] dataDirUTF8;
|
||||
try {
|
||||
dataDirUTF8 = dataDir.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
System.loadLibrary("gio");
|
||||
runGoMain(dataDirUTF8, appCtx);
|
||||
jniLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
static private native void runGoMain(byte[] dataDir, Context context);
|
||||
|
||||
static void writeClipboard(Context ctx, String s) {
|
||||
ClipboardManager m = (ClipboardManager)ctx.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
m.setPrimaryClip(ClipData.newPlainText(null, s));
|
||||
}
|
||||
|
||||
static String readClipboard(Context ctx) {
|
||||
ClipboardManager m = (ClipboardManager)ctx.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData c = m.getPrimaryClip();
|
||||
if (c == null || c.getItemCount() < 1) {
|
||||
return null;
|
||||
}
|
||||
return c.getItemAt(0).coerceToText(ctx).toString();
|
||||
}
|
||||
|
||||
static void wakeupMainThread() {
|
||||
handler.post(new Runnable() {
|
||||
@Override public void run() {
|
||||
scheduleMainFuncs();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static private native void scheduleMainFuncs();
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package org.gioui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public final class GioActivity extends Activity {
|
||||
private GioView view;
|
||||
public FrameLayout layer;
|
||||
|
||||
@Override public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
layer = new FrameLayout(this);
|
||||
view = new GioView(this);
|
||||
|
||||
view.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
));
|
||||
view.setFocusable(true);
|
||||
view.setFocusableInTouchMode(true);
|
||||
|
||||
layer.addView(view);
|
||||
setContentView(layer);
|
||||
onNewIntent(this.getIntent());
|
||||
}
|
||||
|
||||
@Override public void onDestroy() {
|
||||
view.destroy();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override public void onStart() {
|
||||
super.onStart();
|
||||
view.start();
|
||||
}
|
||||
|
||||
@Override public void onStop() {
|
||||
view.stop();
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override public void onPause() {
|
||||
super.onPause();
|
||||
view.pause();
|
||||
}
|
||||
|
||||
@Override public void onResume() {
|
||||
super.onResume();
|
||||
view.resume();
|
||||
}
|
||||
|
||||
@Override public void onConfigurationChanged(Configuration c) {
|
||||
super.onConfigurationChanged(c);
|
||||
view.configurationChanged();
|
||||
}
|
||||
|
||||
@Override public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
GioView.onLowMemory();
|
||||
}
|
||||
|
||||
@Override public void onBackPressed() {
|
||||
if (!view.backPressed())
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
view.onIntentEvent(intent);
|
||||
}
|
||||
}
|
||||
+858
@@ -0,0 +1,858 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package org.gioui;
|
||||
|
||||
import java.lang.Class;
|
||||
import java.lang.IllegalAccessException;
|
||||
import java.lang.InstantiationException;
|
||||
import java.lang.ExceptionInInitializerError;
|
||||
import java.lang.SecurityException;
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.text.TextUtils;
|
||||
import android.text.Selection;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Choreographer;
|
||||
import android.view.Display;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.PointerIcon;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsetsController;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.CorrectionInfo;
|
||||
import android.view.inputmethod.CompletionInfo;
|
||||
import android.view.inputmethod.CursorAnchorInfo;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.ExtractedText;
|
||||
import android.view.inputmethod.ExtractedTextRequest;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.InputContentInfo;
|
||||
import android.view.inputmethod.SurroundingText;
|
||||
import android.view.accessibility.AccessibilityNodeProvider;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public final class GioView extends SurfaceView implements Choreographer.FrameCallback {
|
||||
private static boolean jniLoaded;
|
||||
|
||||
private final SurfaceHolder.Callback surfCallbacks;
|
||||
private final InputMethodManager imm;
|
||||
private final float scrollXScale;
|
||||
private final float scrollYScale;
|
||||
private final AccessibilityManager accessManager;
|
||||
private int keyboardHint;
|
||||
|
||||
private long nhandle;
|
||||
|
||||
public GioView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public GioView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
}
|
||||
setLayoutParams(new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT));
|
||||
|
||||
// Late initialization of the Go runtime to wait for a valid context.
|
||||
Gio.init(context.getApplicationContext());
|
||||
|
||||
// Set background color to transparent to avoid a flickering
|
||||
// issue on ChromeOS.
|
||||
setBackgroundColor(Color.argb(0, 0, 0, 0));
|
||||
|
||||
ViewConfiguration conf = ViewConfiguration.get(context);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
scrollXScale = conf.getScaledHorizontalScrollFactor();
|
||||
scrollYScale = conf.getScaledVerticalScrollFactor();
|
||||
|
||||
// The platform focus highlight is not aware of Gio's widgets.
|
||||
setDefaultFocusHighlightEnabled(false);
|
||||
} else {
|
||||
float listItemHeight = 48; // dp
|
||||
float px = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
listItemHeight,
|
||||
getResources().getDisplayMetrics()
|
||||
);
|
||||
scrollXScale = px;
|
||||
scrollYScale = px;
|
||||
}
|
||||
|
||||
setHighRefreshRate();
|
||||
|
||||
accessManager = (AccessibilityManager)context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||
imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
nhandle = onCreateView(this);
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
surfCallbacks = new SurfaceHolder.Callback() {
|
||||
@Override public void surfaceCreated(SurfaceHolder holder) {
|
||||
// Ignore; surfaceChanged is guaranteed to be called immediately after this.
|
||||
}
|
||||
@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
onSurfaceChanged(nhandle, getHolder().getSurface());
|
||||
}
|
||||
@Override public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
onSurfaceDestroyed(nhandle);
|
||||
}
|
||||
};
|
||||
getHolder().addCallback(surfCallbacks);
|
||||
}
|
||||
|
||||
@Override public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (nhandle != 0) {
|
||||
onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), true, event.getEventTime());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (nhandle != 0) {
|
||||
onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), false, event.getEventTime());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
dispatchMotionEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean onTouchEvent(MotionEvent event) {
|
||||
// Ask for unbuffered events. Flutter and Chrome do it
|
||||
// so assume it's good for us as well.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
requestUnbufferedDispatch(event);
|
||||
}
|
||||
|
||||
dispatchMotionEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setCursor(int id) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
return;
|
||||
}
|
||||
PointerIcon pointerIcon = PointerIcon.getSystemIcon(getContext(), id);
|
||||
setPointerIcon(pointerIcon);
|
||||
}
|
||||
|
||||
private void setOrientation(int id, int fallback) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
id = fallback;
|
||||
}
|
||||
((Activity) this.getContext()).setRequestedOrientation(id);
|
||||
}
|
||||
|
||||
private void setFullscreen(boolean enabled) {
|
||||
int flags = this.getSystemUiVisibility();
|
||||
if (enabled) {
|
||||
flags |= SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
flags |= SYSTEM_UI_FLAG_HIDE_NAVIGATION;
|
||||
flags |= SYSTEM_UI_FLAG_FULLSCREEN;
|
||||
flags |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
|
||||
} else {
|
||||
flags &= ~SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
flags &= ~SYSTEM_UI_FLAG_HIDE_NAVIGATION;
|
||||
flags &= ~SYSTEM_UI_FLAG_FULLSCREEN;
|
||||
flags &= ~SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
|
||||
}
|
||||
this.setSystemUiVisibility(flags);
|
||||
}
|
||||
|
||||
private enum Bar {
|
||||
NAVIGATION,
|
||||
STATUS,
|
||||
}
|
||||
|
||||
private void setBarColor(Bar t, int color, int luminance) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return;
|
||||
}
|
||||
|
||||
Window window = ((Activity) this.getContext()).getWindow();
|
||||
|
||||
int insetsMask;
|
||||
int viewMask;
|
||||
|
||||
switch (t) {
|
||||
case STATUS:
|
||||
insetsMask = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
|
||||
viewMask = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
||||
window.setStatusBarColor(color);
|
||||
break;
|
||||
case NAVIGATION:
|
||||
insetsMask = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
|
||||
viewMask = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
||||
window.setNavigationBarColor(color);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("invalid bar type");
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
int flags = this.getSystemUiVisibility();
|
||||
if (luminance > 128) {
|
||||
flags |= viewMask;
|
||||
} else {
|
||||
flags &= ~viewMask;
|
||||
}
|
||||
this.setSystemUiVisibility(flags);
|
||||
return;
|
||||
}
|
||||
|
||||
WindowInsetsController insetsController = window.getInsetsController();
|
||||
if (insetsController == null) {
|
||||
return;
|
||||
}
|
||||
if (luminance > 128) {
|
||||
insetsController.setSystemBarsAppearance(insetsMask, insetsMask);
|
||||
} else {
|
||||
insetsController.setSystemBarsAppearance(0, insetsMask);
|
||||
}
|
||||
}
|
||||
|
||||
private void setStatusColor(int color, int luminance) {
|
||||
this.setBarColor(Bar.STATUS, color, luminance);
|
||||
}
|
||||
|
||||
private void setNavigationColor(int color, int luminance) {
|
||||
this.setBarColor(Bar.NAVIGATION, color, luminance);
|
||||
}
|
||||
|
||||
private void setHighRefreshRate() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = getContext();
|
||||
Display display = context.getDisplay();
|
||||
Display.Mode[] supportedModes = display.getSupportedModes();
|
||||
if (supportedModes.length <= 1) {
|
||||
// Nothing to set
|
||||
return;
|
||||
}
|
||||
|
||||
Display.Mode currentMode = display.getMode();
|
||||
int currentWidth = currentMode.getPhysicalWidth();
|
||||
int currentHeight = currentMode.getPhysicalHeight();
|
||||
|
||||
float minRefreshRate = -1;
|
||||
float maxRefreshRate = -1;
|
||||
float bestRefreshRate = -1;
|
||||
int bestModeId = -1;
|
||||
for (Display.Mode mode : supportedModes) {
|
||||
float refreshRate = mode.getRefreshRate();
|
||||
float width = mode.getPhysicalWidth();
|
||||
float height = mode.getPhysicalHeight();
|
||||
|
||||
if (minRefreshRate == -1 || refreshRate < minRefreshRate) {
|
||||
minRefreshRate = refreshRate;
|
||||
}
|
||||
if (maxRefreshRate == -1 || refreshRate > maxRefreshRate) {
|
||||
maxRefreshRate = refreshRate;
|
||||
}
|
||||
|
||||
boolean refreshRateIsBetter = bestRefreshRate == -1 || refreshRate > bestRefreshRate;
|
||||
if (width == currentWidth && height == currentHeight && refreshRateIsBetter) {
|
||||
int modeId = mode.getModeId();
|
||||
bestRefreshRate = refreshRate;
|
||||
bestModeId = modeId;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestModeId == -1) {
|
||||
// Not expecting this but just in case
|
||||
return;
|
||||
}
|
||||
|
||||
if (minRefreshRate == maxRefreshRate) {
|
||||
// Can't improve the refresh rate
|
||||
return;
|
||||
}
|
||||
|
||||
Window window = ((Activity) context).getWindow();
|
||||
WindowManager.LayoutParams layoutParams = window.getAttributes();
|
||||
layoutParams.preferredDisplayModeId = bestModeId;
|
||||
window.setAttributes(layoutParams);
|
||||
}
|
||||
|
||||
protected void onIntentEvent(Intent intent) {
|
||||
if (intent == null) {
|
||||
return;
|
||||
}
|
||||
if (intent.getData() != null) {
|
||||
this.onOpenURI(nhandle, intent.getData().toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected boolean dispatchHoverEvent(MotionEvent event) {
|
||||
if (!accessManager.isTouchExplorationEnabled()) {
|
||||
return super.dispatchHoverEvent(event);
|
||||
}
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_HOVER_ENTER:
|
||||
// Fall through.
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
onTouchExploration(nhandle, event.getX(), event.getY());
|
||||
break;
|
||||
case MotionEvent.ACTION_HOVER_EXIT:
|
||||
onExitTouchExploration(nhandle);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void sendA11yEvent(int eventType, int viewId) {
|
||||
if (!accessManager.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
AccessibilityEvent event = obtainA11yEvent(eventType, viewId);
|
||||
getParent().requestSendAccessibilityEvent(this, event);
|
||||
}
|
||||
|
||||
AccessibilityEvent obtainA11yEvent(int eventType, int viewId) {
|
||||
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
|
||||
event.setPackageName(getContext().getPackageName());
|
||||
event.setSource(this, viewId);
|
||||
return event;
|
||||
}
|
||||
|
||||
boolean isA11yActive() {
|
||||
return accessManager.isEnabled();
|
||||
}
|
||||
|
||||
void sendA11yChange(int viewId) {
|
||||
if (!accessManager.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
AccessibilityEvent event = obtainA11yEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED, viewId);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
|
||||
}
|
||||
getParent().requestSendAccessibilityEvent(this, event);
|
||||
}
|
||||
|
||||
private void dispatchMotionEvent(MotionEvent event) {
|
||||
if (nhandle == 0) {
|
||||
return;
|
||||
}
|
||||
for (int j = 0; j < event.getHistorySize(); j++) {
|
||||
long time = event.getHistoricalEventTime(j);
|
||||
for (int i = 0; i < event.getPointerCount(); i++) {
|
||||
onTouchEvent(
|
||||
nhandle,
|
||||
event.ACTION_MOVE,
|
||||
event.getPointerId(i),
|
||||
event.getToolType(i),
|
||||
event.getHistoricalX(i, j),
|
||||
event.getHistoricalY(i, j),
|
||||
scrollXScale*event.getHistoricalAxisValue(MotionEvent.AXIS_HSCROLL, i, j),
|
||||
scrollYScale*event.getHistoricalAxisValue(MotionEvent.AXIS_VSCROLL, i, j),
|
||||
event.getButtonState(),
|
||||
time);
|
||||
}
|
||||
}
|
||||
int act = event.getActionMasked();
|
||||
int idx = event.getActionIndex();
|
||||
for (int i = 0; i < event.getPointerCount(); i++) {
|
||||
int pact = event.ACTION_MOVE;
|
||||
if (i == idx) {
|
||||
pact = act;
|
||||
}
|
||||
onTouchEvent(
|
||||
nhandle,
|
||||
pact,
|
||||
event.getPointerId(i),
|
||||
event.getToolType(i),
|
||||
event.getX(i), event.getY(i),
|
||||
scrollXScale*event.getAxisValue(MotionEvent.AXIS_HSCROLL, i),
|
||||
scrollYScale*event.getAxisValue(MotionEvent.AXIS_VSCROLL, i),
|
||||
event.getButtonState(),
|
||||
event.getEventTime());
|
||||
}
|
||||
}
|
||||
|
||||
@Override public InputConnection onCreateInputConnection(EditorInfo editor) {
|
||||
Snippet snip = getSnippet();
|
||||
editor.inputType = this.keyboardHint;
|
||||
editor.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
|
||||
editor.initialSelStart = imeToUTF16(nhandle, imeSelectionStart(nhandle));
|
||||
editor.initialSelEnd = imeToUTF16(nhandle, imeSelectionEnd(nhandle));
|
||||
int selStart = editor.initialSelStart - snip.offset;
|
||||
editor.initialCapsMode = TextUtils.getCapsMode(snip.snippet, selStart, this.keyboardHint);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
editor.setInitialSurroundingSubText(snip.snippet, imeToUTF16(nhandle, snip.offset));
|
||||
}
|
||||
imeSetComposingRegion(nhandle, -1, -1);
|
||||
return new GioInputConnection();
|
||||
}
|
||||
|
||||
void setInputHint(int hint) {
|
||||
if (hint == this.keyboardHint) {
|
||||
return;
|
||||
}
|
||||
this.keyboardHint = hint;
|
||||
restartInput();
|
||||
}
|
||||
|
||||
void showTextInput() {
|
||||
GioView.this.requestFocus();
|
||||
imm.showSoftInput(GioView.this, 0);
|
||||
}
|
||||
|
||||
void hideTextInput() {
|
||||
imm.hideSoftInputFromWindow(getWindowToken(), 0);
|
||||
}
|
||||
|
||||
@Override protected boolean fitSystemWindows(Rect insets) {
|
||||
if (nhandle != 0) {
|
||||
onWindowInsets(nhandle, insets.top, insets.right, insets.bottom, insets.left);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void postFrameCallback() {
|
||||
Choreographer.getInstance().removeFrameCallback(this);
|
||||
Choreographer.getInstance().postFrameCallback(this);
|
||||
}
|
||||
|
||||
@Override public void doFrame(long nanos) {
|
||||
if (nhandle != 0) {
|
||||
onFrameCallback(nhandle);
|
||||
}
|
||||
}
|
||||
|
||||
int getDensity() {
|
||||
return getResources().getDisplayMetrics().densityDpi;
|
||||
}
|
||||
|
||||
float getFontScale() {
|
||||
return getResources().getConfiguration().fontScale;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (nhandle != 0) {
|
||||
onStartView(nhandle);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (nhandle != 0) {
|
||||
onStopView(nhandle);
|
||||
}
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
if (nhandle != 0) {
|
||||
onFocusChange(nhandle, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
if (nhandle != 0) {
|
||||
onFocusChange(nhandle, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (nhandle != 0) {
|
||||
onDestroyView(nhandle);
|
||||
}
|
||||
}
|
||||
|
||||
protected void unregister() {
|
||||
setOnFocusChangeListener(null);
|
||||
getHolder().removeCallback(surfCallbacks);
|
||||
nhandle = 0;
|
||||
}
|
||||
|
||||
public void configurationChanged() {
|
||||
if (nhandle != 0) {
|
||||
onConfigurationChanged(nhandle);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean backPressed() {
|
||||
if (nhandle == 0) {
|
||||
return false;
|
||||
}
|
||||
return onBack(nhandle);
|
||||
}
|
||||
|
||||
void restartInput() {
|
||||
imm.restartInput(this);
|
||||
}
|
||||
|
||||
void updateSelection() {
|
||||
int selStart = imeToUTF16(nhandle, imeSelectionStart(nhandle));
|
||||
int selEnd = imeToUTF16(nhandle, imeSelectionEnd(nhandle));
|
||||
int compStart = imeToUTF16(nhandle, imeComposingStart(nhandle));
|
||||
int compEnd = imeToUTF16(nhandle, imeComposingEnd(nhandle));
|
||||
imm.updateSelection(this, selStart, selEnd, compStart, compEnd);
|
||||
}
|
||||
|
||||
void updateCaret(float m00, float m01, float m02, float m10, float m11, float m12, float caretX, float caretTop, float caretBase, float caretBottom) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return;
|
||||
}
|
||||
Matrix m = new Matrix();
|
||||
m.setValues(new float[]{m00, m01, m02, m10, m11, m12, 0.0f, 0.0f, 1.0f});
|
||||
m.setConcat(getMatrix(), m);
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
int compStart = imeComposingStart(nhandle);
|
||||
int compEnd = imeComposingEnd(nhandle);
|
||||
Snippet snip = getSnippet();
|
||||
String composing = "";
|
||||
if (compStart != -1) {
|
||||
composing = snip.substringRunes(compStart, compEnd);
|
||||
}
|
||||
CursorAnchorInfo inf = new CursorAnchorInfo.Builder()
|
||||
.setMatrix(m)
|
||||
.setComposingText(imeToUTF16(nhandle, compStart), composing)
|
||||
.setSelectionRange(imeToUTF16(nhandle, selStart), imeToUTF16(nhandle, selEnd))
|
||||
.setInsertionMarkerLocation(caretX, caretTop, caretBase, caretBottom, 0)
|
||||
.build();
|
||||
imm.updateCursorAnchorInfo(this, inf);
|
||||
}
|
||||
|
||||
static private native long onCreateView(GioView view);
|
||||
static private native void onDestroyView(long handle);
|
||||
static private native void onStartView(long handle);
|
||||
static private native void onStopView(long handle);
|
||||
static private native void onSurfaceDestroyed(long handle);
|
||||
static private native void onSurfaceChanged(long handle, Surface surface);
|
||||
static private native void onConfigurationChanged(long handle);
|
||||
static private native void onWindowInsets(long handle, int top, int right, int bottom, int left);
|
||||
static public native void onLowMemory();
|
||||
static private native void onTouchEvent(long handle, int action, int pointerID, int tool, float x, float y, float scrollX, float scrollY, int buttons, long time);
|
||||
static private native void onKeyEvent(long handle, int code, int character, boolean pressed, long time);
|
||||
static private native void onFrameCallback(long handle);
|
||||
static private native boolean onBack(long handle);
|
||||
static private native void onFocusChange(long handle, boolean focus);
|
||||
static private native AccessibilityNodeInfo initializeAccessibilityNodeInfo(long handle, int viewId, int screenX, int screenY, AccessibilityNodeInfo info);
|
||||
static private native void onTouchExploration(long handle, float x, float y);
|
||||
static private native void onExitTouchExploration(long handle);
|
||||
static private native void onA11yFocus(long handle, int viewId);
|
||||
static private native void onClearA11yFocus(long handle, int viewId);
|
||||
static private native void onOpenURI(long handle, String uri);
|
||||
static private native void imeSetSnippet(long handle, int start, int end);
|
||||
static private native String imeSnippet(long handle);
|
||||
static private native int imeSnippetStart(long handle);
|
||||
static private native int imeSelectionStart(long handle);
|
||||
static private native int imeSelectionEnd(long handle);
|
||||
static private native int imeComposingStart(long handle);
|
||||
static private native int imeComposingEnd(long handle);
|
||||
static private native int imeReplace(long handle, int start, int end, String text);
|
||||
static private native int imeSetSelection(long handle, int start, int end);
|
||||
static private native int imeSetComposingRegion(long handle, int start, int end);
|
||||
// imeToRunes converts the Java character index into runes (Java code points).
|
||||
static private native int imeToRunes(long handle, int chars);
|
||||
// imeToUTF16 converts the rune index into Java characters.
|
||||
static private native int imeToUTF16(long handle, int runes);
|
||||
|
||||
private class GioInputConnection implements InputConnection {
|
||||
private int batchDepth;
|
||||
|
||||
@Override public boolean beginBatchEdit() {
|
||||
batchDepth++;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean endBatchEdit() {
|
||||
batchDepth--;
|
||||
return batchDepth > 0;
|
||||
}
|
||||
|
||||
@Override public boolean clearMetaKeyStates(int states) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean commitCompletion(CompletionInfo text) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean commitCorrection(CorrectionInfo info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean commitText(CharSequence text, int cursor) {
|
||||
setComposingText(text, cursor);
|
||||
return finishComposingText();
|
||||
}
|
||||
|
||||
@Override public boolean deleteSurroundingText(int beforeChars, int afterChars) {
|
||||
// translate before and after to runes.
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
int before = selStart - imeToRunes(nhandle, imeToUTF16(nhandle, selStart) - beforeChars);
|
||||
int after = selEnd - imeToRunes(nhandle, imeToUTF16(nhandle, selEnd) - afterChars);
|
||||
return deleteSurroundingTextInCodePoints(before, after);
|
||||
}
|
||||
|
||||
@Override public boolean finishComposingText() {
|
||||
imeSetComposingRegion(nhandle, -1, -1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public int getCursorCapsMode(int reqModes) {
|
||||
Snippet snip = getSnippet();
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int off = imeToUTF16(nhandle, selStart - snip.offset);
|
||||
if (off < 0 || off > snip.snippet.length()) {
|
||||
return 0;
|
||||
}
|
||||
return TextUtils.getCapsMode(snip.snippet, off, reqModes);
|
||||
}
|
||||
|
||||
@Override public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override public CharSequence getSelectedText(int flags) {
|
||||
Snippet snip = getSnippet();
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
String sub = snip.substringRunes(selStart, selEnd);
|
||||
return sub;
|
||||
}
|
||||
|
||||
@Override public CharSequence getTextAfterCursor(int n, int flags) {
|
||||
Snippet snip = getSnippet();
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
// n are in Java characters, but in worst case we'll just ask for more runes
|
||||
// than wanted.
|
||||
imeSetSnippet(nhandle, selStart - n, selEnd + n);
|
||||
int start = selEnd;
|
||||
int end = imeToRunes(nhandle, imeToUTF16(nhandle, selEnd) + n);
|
||||
String ret = snip.substringRunes(start, end);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override public CharSequence getTextBeforeCursor(int n, int flags) {
|
||||
Snippet snip = getSnippet();
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
// n are in Java characters, but in worst case we'll just ask for more runes
|
||||
// than wanted.
|
||||
imeSetSnippet(nhandle, selStart - n, selEnd + n);
|
||||
int start = imeToRunes(nhandle, imeToUTF16(nhandle, selStart) - n);
|
||||
int end = selStart;
|
||||
String ret = snip.substringRunes(start, end);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override public boolean performContextMenuAction(int id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean performEditorAction(int editorAction) {
|
||||
long eventTime = SystemClock.uptimeMillis();
|
||||
// Translate to enter key.
|
||||
onKeyEvent(nhandle, KeyEvent.KEYCODE_ENTER, '\n', true, eventTime);
|
||||
onKeyEvent(nhandle, KeyEvent.KEYCODE_ENTER, '\n', false, eventTime);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean performPrivateCommand(String action, Bundle data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean reportFullscreenMode(boolean enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean sendKeyEvent(KeyEvent event) {
|
||||
boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN;
|
||||
onKeyEvent(nhandle, event.getKeyCode(), event.getUnicodeChar(), pressed, event.getEventTime());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean setComposingRegion(int startChars, int endChars) {
|
||||
int compStart = imeToRunes(nhandle, startChars);
|
||||
int compEnd = imeToRunes(nhandle, endChars);
|
||||
imeSetComposingRegion(nhandle, compStart, compEnd);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean setComposingText(CharSequence text, int relCursor) {
|
||||
int start = imeComposingStart(nhandle);
|
||||
int end = imeComposingEnd(nhandle);
|
||||
if (start == -1 || end == -1) {
|
||||
start = imeSelectionStart(nhandle);
|
||||
end = imeSelectionEnd(nhandle);
|
||||
}
|
||||
String str = text.toString();
|
||||
imeReplace(nhandle, start, end, str);
|
||||
int cursor = start;
|
||||
int runes = str.codePointCount(0, str.length());
|
||||
if (relCursor > 0) {
|
||||
cursor += runes;
|
||||
relCursor--;
|
||||
}
|
||||
imeSetComposingRegion(nhandle, start, start + runes);
|
||||
|
||||
// Move cursor.
|
||||
Snippet snip = getSnippet();
|
||||
cursor = imeToRunes(nhandle, imeToUTF16(nhandle, cursor) + relCursor);
|
||||
imeSetSelection(nhandle, cursor, cursor);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean setSelection(int startChars, int endChars) {
|
||||
int start = imeToRunes(nhandle, startChars);
|
||||
int end = imeToRunes(nhandle, endChars);
|
||||
imeSetSelection(nhandle, start, end);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*@Override*/ public boolean requestCursorUpdates(int cursorUpdateMode) {
|
||||
// We always provide cursor updates.
|
||||
return true;
|
||||
}
|
||||
|
||||
/*@Override*/ public void closeConnection() {
|
||||
}
|
||||
|
||||
/*@Override*/ public Handler getHandler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*@Override*/ public boolean commitContent(InputContentInfo info, int flags, Bundle opts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*@Override*/ public boolean deleteSurroundingTextInCodePoints(int before, int after) {
|
||||
if (after > 0) {
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
imeReplace(nhandle, selEnd, selEnd + after, "");
|
||||
}
|
||||
if (before > 0) {
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
imeReplace(nhandle, selStart - before, selStart, "");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*@Override*/ public SurroundingText getSurroundingText(int beforeChars, int afterChars, int flags) {
|
||||
Snippet snip = getSnippet();
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
// Expanding in Java characters is ok.
|
||||
imeSetSnippet(nhandle, selStart - beforeChars, selEnd + afterChars);
|
||||
return new SurroundingText(snip.snippet, imeToUTF16(nhandle, selStart), imeToUTF16(nhandle, selEnd), imeToUTF16(nhandle, snip.offset));
|
||||
}
|
||||
}
|
||||
|
||||
private Snippet getSnippet() {
|
||||
Snippet snip = new Snippet();
|
||||
snip.snippet = imeSnippet(nhandle);
|
||||
snip.offset = imeSnippetStart(nhandle);
|
||||
return snip;
|
||||
}
|
||||
|
||||
// Snippet is like android.view.inputmethod.SurroundingText but available for Android < 31.
|
||||
private static class Snippet {
|
||||
String snippet;
|
||||
// offset of snippet into the entire editor content. It is in runes because we won't require
|
||||
// Gio editors to keep track of UTF-16 offsets. The distinction won't matter in practice because IMEs only
|
||||
// ever see snippets.
|
||||
int offset;
|
||||
|
||||
// substringRunes returns the substring from start to end in runes. The resuls is
|
||||
// truncated to the snippet.
|
||||
String substringRunes(int start, int end) {
|
||||
start -= this.offset;
|
||||
end -= this.offset;
|
||||
int runes = snippet.codePointCount(0, snippet.length());
|
||||
if (start < 0) {
|
||||
start = 0;
|
||||
}
|
||||
if (end < 0) {
|
||||
end = 0;
|
||||
}
|
||||
if (start > runes) {
|
||||
start = runes;
|
||||
}
|
||||
if (end > runes) {
|
||||
end = runes;
|
||||
}
|
||||
return snippet.substring(
|
||||
snippet.offsetByCodePoints(0, start),
|
||||
snippet.offsetByCodePoints(0, end)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public AccessibilityNodeProvider getAccessibilityNodeProvider() {
|
||||
return new AccessibilityNodeProvider() {
|
||||
private final int[] screenOff = new int[2];
|
||||
|
||||
@Override public AccessibilityNodeInfo createAccessibilityNodeInfo(int viewId) {
|
||||
AccessibilityNodeInfo info = null;
|
||||
if (viewId == View.NO_ID) {
|
||||
info = AccessibilityNodeInfo.obtain(GioView.this);
|
||||
GioView.this.onInitializeAccessibilityNodeInfo(info);
|
||||
} else {
|
||||
info = AccessibilityNodeInfo.obtain(GioView.this, viewId);
|
||||
info.setPackageName(getContext().getPackageName());
|
||||
info.setVisibleToUser(true);
|
||||
}
|
||||
GioView.this.getLocationOnScreen(screenOff);
|
||||
info = GioView.this.initializeAccessibilityNodeInfo(nhandle, viewId, screenOff[0], screenOff[1], info);
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override public boolean performAction(int viewId, int action, Bundle arguments) {
|
||||
if (viewId == View.NO_ID) {
|
||||
return GioView.this.performAccessibilityAction(action, arguments);
|
||||
}
|
||||
switch (action) {
|
||||
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
|
||||
GioView.this.onA11yFocus(nhandle, viewId);
|
||||
GioView.this.sendA11yEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, viewId);
|
||||
return true;
|
||||
case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
|
||||
GioView.this.onClearA11yFocus(nhandle, viewId);
|
||||
GioView.this.sendA11yEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, viewId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"gioui.org/io/event"
|
||||
"golang.org/x/net/idna"
|
||||
"image"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gioui.org/io/input"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
// extraArgs contains extra arguments to append to
|
||||
// os.Args. The arguments are separated with |.
|
||||
// Useful for running programs on mobiles where the
|
||||
// command line is not available.
|
||||
// Set with the go linker flag -X.
|
||||
var extraArgs string
|
||||
|
||||
// ID is the app id exposed to the platform.
|
||||
//
|
||||
// On Android ID is the package property of AndroidManifest.xml,
|
||||
// on iOS ID is the CFBundleIdentifier of the app Info.plist,
|
||||
// on Wayland it is the toplevel app_id,
|
||||
// on X11 it is the X11 XClassHint.
|
||||
//
|
||||
// ID is set by the [gioui.org/cmd/gogio] tool or manually with the -X linker flag. For example,
|
||||
//
|
||||
// go build -ldflags="-X 'gioui.org/app.ID=org.gioui.example.Kitchen'" .
|
||||
//
|
||||
// Note that ID is treated as a constant, and that changing it at runtime
|
||||
// is not supported. The default value of ID is filepath.Base(os.Args[0]).
|
||||
var ID = ""
|
||||
|
||||
// A FrameEvent requests a new frame in the form of a list of
|
||||
// operations that describes the window content.
|
||||
type FrameEvent struct {
|
||||
// Now is the current animation. Use Now instead of time.Now to
|
||||
// synchronize animation and to avoid the time.Now call overhead.
|
||||
Now time.Time
|
||||
// Metric converts device independent dp and sp to device pixels.
|
||||
Metric unit.Metric
|
||||
// Size is the dimensions of the window.
|
||||
Size image.Point
|
||||
// Insets represent the space occupied by system decorations and controls.
|
||||
Insets Insets
|
||||
// Frame completes the FrameEvent by drawing the graphical operations
|
||||
// from ops into the window.
|
||||
Frame func(frame *op.Ops)
|
||||
// Source is the interface between the window and widgets.
|
||||
Source input.Source
|
||||
}
|
||||
|
||||
// URLEvent is generated for external requests to open a URL. Unlike window specific events,
|
||||
// it is delivered through the [Events] iterator.
|
||||
//
|
||||
// In order to receive URLEvents the program must register one or more URL schemes. A scheme can
|
||||
// be registered using gogio, with the `-schemes` flag.
|
||||
type URLEvent struct {
|
||||
URL *url.URL
|
||||
}
|
||||
|
||||
// ViewEvent provides handles to the underlying window objects for the
|
||||
// current display protocol.
|
||||
type ViewEvent interface {
|
||||
implementsViewEvent()
|
||||
ImplementsEvent()
|
||||
// Valid will return true when the ViewEvent does contains valid handles.
|
||||
// If a window receives an invalid ViewEvent, it should deinitialize any
|
||||
// state referring to handles from a previous ViewEvent.
|
||||
Valid() bool
|
||||
}
|
||||
|
||||
// Insets is the space taken up by
|
||||
// system decoration such as translucent
|
||||
// system bars and software keyboards.
|
||||
type Insets struct {
|
||||
// Values are in pixels.
|
||||
Top, Bottom, Left, Right unit.Dp
|
||||
}
|
||||
|
||||
// NewContext is shorthand for
|
||||
//
|
||||
// layout.Context{
|
||||
// Ops: ops,
|
||||
// Now: e.Now,
|
||||
// Source: e.Source,
|
||||
// Metric: e.Metric,
|
||||
// Constraints: layout.Exact(e.Size),
|
||||
// }
|
||||
//
|
||||
// NewContext calls ops.Reset and adjusts ops for e.Insets.
|
||||
func NewContext(ops *op.Ops, e FrameEvent) layout.Context {
|
||||
ops.Reset()
|
||||
|
||||
size := e.Size
|
||||
|
||||
if e.Insets != (Insets{}) {
|
||||
left := e.Metric.Dp(e.Insets.Left)
|
||||
top := e.Metric.Dp(e.Insets.Top)
|
||||
op.Offset(image.Point{
|
||||
X: left,
|
||||
Y: top,
|
||||
}).Add(ops)
|
||||
|
||||
size.X -= left + e.Metric.Dp(e.Insets.Right)
|
||||
size.Y -= top + e.Metric.Dp(e.Insets.Bottom)
|
||||
}
|
||||
|
||||
return layout.Context{
|
||||
Ops: ops,
|
||||
Now: e.Now,
|
||||
Source: e.Source,
|
||||
Metric: e.Metric,
|
||||
Constraints: layout.Exact(size),
|
||||
}
|
||||
}
|
||||
|
||||
// DataDir returns a path to use for application-specific
|
||||
// configuration data.
|
||||
// On desktop systems, DataDir use os.UserConfigDir.
|
||||
// On iOS NSDocumentDirectory is queried.
|
||||
// For Android Context.getFilesDir is used.
|
||||
//
|
||||
// BUG: On Android, DataDir panics if called before main.
|
||||
func DataDir() (string, error) {
|
||||
return dataDir()
|
||||
}
|
||||
|
||||
// Main must be called last from the program main function.
|
||||
// On most platforms Main blocks forever, for Android and
|
||||
// iOS it returns immediately to give control of the main
|
||||
// thread back to the system.
|
||||
//
|
||||
// Calling Main is necessary because some operating systems
|
||||
// require control of the main thread of the program for
|
||||
// running windows.
|
||||
func Main() {
|
||||
osMain()
|
||||
}
|
||||
|
||||
// Events is an iterator that yields events that are not specific to any window,
|
||||
// such as [URLEvent]. It never returns.
|
||||
//
|
||||
// Events must be called by the main goroutine, and replaces the
|
||||
// call to [Main].
|
||||
func Events(yield func(event.Event) bool) {
|
||||
yieldGlobalEvent = yield
|
||||
osMain()
|
||||
}
|
||||
|
||||
var yieldGlobalEvent func(evt event.Event) bool
|
||||
|
||||
func processGlobalEvent(evt event.Event) {
|
||||
if yieldGlobalEvent == nil {
|
||||
return
|
||||
}
|
||||
if !yieldGlobalEvent(evt) {
|
||||
yieldGlobalEvent = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (FrameEvent) ImplementsEvent() {}
|
||||
func (URLEvent) ImplementsEvent() {}
|
||||
|
||||
func init() {
|
||||
if extraArgs != "" {
|
||||
args := strings.Split(extraArgs, "|")
|
||||
os.Args = append(os.Args, args...)
|
||||
}
|
||||
if ID == "" {
|
||||
ID = filepath.Base(os.Args[0])
|
||||
}
|
||||
}
|
||||
|
||||
// newURLEvent creates a URLEvent from a raw URL string, handling Punycode decoding.
|
||||
func newURLEvent(rawurl string) (URLEvent, error) {
|
||||
u, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
return URLEvent{}, err
|
||||
}
|
||||
u.Host, err = idna.Punycode.ToUnicode(u.Hostname())
|
||||
if err != nil {
|
||||
return URLEvent{}, err
|
||||
}
|
||||
u, err = url.Parse(u.String())
|
||||
if err != nil {
|
||||
return URLEvent{}, err
|
||||
}
|
||||
return URLEvent{URL: u}, nil
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/d3d11"
|
||||
)
|
||||
|
||||
type d3d11Context struct {
|
||||
win *window
|
||||
dev *d3d11.Device
|
||||
ctx *d3d11.DeviceContext
|
||||
|
||||
swchain *d3d11.IDXGISwapChain
|
||||
renderTarget *d3d11.RenderTargetView
|
||||
width, height int
|
||||
}
|
||||
|
||||
const debugDirectX = false
|
||||
|
||||
func init() {
|
||||
drivers = append(drivers, gpuAPI{
|
||||
priority: 1,
|
||||
initializer: func(w *window) (context, error) {
|
||||
hwnd, _, _ := w.HWND()
|
||||
var flags uint32
|
||||
if debugDirectX {
|
||||
flags |= d3d11.CREATE_DEVICE_DEBUG
|
||||
}
|
||||
dev, ctx, _, err := d3d11.CreateDevice(
|
||||
d3d11.DRIVER_TYPE_HARDWARE,
|
||||
flags,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewContext: %v", err)
|
||||
}
|
||||
swchain, err := d3d11.CreateSwapChain(dev, hwnd)
|
||||
if err != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(ctx), ctx.Vtbl.Release)
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(dev), dev.Vtbl.Release)
|
||||
return nil, err
|
||||
}
|
||||
return &d3d11Context{win: w, dev: dev, ctx: ctx, swchain: swchain}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (c *d3d11Context) API() gpu.API {
|
||||
return gpu.Direct3D11{Device: unsafe.Pointer(c.dev)}
|
||||
}
|
||||
|
||||
func (c *d3d11Context) RenderTarget() (gpu.RenderTarget, error) {
|
||||
return gpu.Direct3D11RenderTarget{
|
||||
RenderTarget: unsafe.Pointer(c.renderTarget),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *d3d11Context) Present() error {
|
||||
return wrapErr(c.swchain.Present(1, 0))
|
||||
}
|
||||
|
||||
func wrapErr(err error) error {
|
||||
if err, ok := err.(d3d11.ErrorCode); ok {
|
||||
switch err.Code {
|
||||
case d3d11.DXGI_STATUS_OCCLUDED:
|
||||
// Ignore
|
||||
return nil
|
||||
case d3d11.DXGI_ERROR_DEVICE_RESET, d3d11.DXGI_ERROR_DEVICE_REMOVED, d3d11.D3DDDIERR_DEVICEREMOVED:
|
||||
return gpu.ErrDeviceLost
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *d3d11Context) Refresh() error {
|
||||
var width, height int
|
||||
_, width, height = c.win.HWND()
|
||||
if c.renderTarget != nil && width == c.width && height == c.height {
|
||||
return nil
|
||||
}
|
||||
c.releaseFBO()
|
||||
if err := c.swchain.ResizeBuffers(0, 0, 0, d3d11.DXGI_FORMAT_UNKNOWN, 0); err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
c.width = width
|
||||
c.height = height
|
||||
|
||||
backBuffer, err := c.swchain.GetBuffer(0, &d3d11.IID_Texture2D)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
texture := (*d3d11.Resource)(unsafe.Pointer(backBuffer))
|
||||
renderTarget, err := c.dev.CreateRenderTargetView(texture)
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(backBuffer), backBuffer.Vtbl.Release)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.renderTarget = renderTarget
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *d3d11Context) Lock() error {
|
||||
c.ctx.OMSetRenderTargets(c.renderTarget, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *d3d11Context) Unlock() {}
|
||||
|
||||
func (c *d3d11Context) Release() {
|
||||
c.releaseFBO()
|
||||
if c.swchain != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(c.swchain), c.swchain.Vtbl.Release)
|
||||
}
|
||||
if c.ctx != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(c.ctx), c.ctx.Vtbl.Release)
|
||||
}
|
||||
if c.dev != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(c.dev), c.dev.Vtbl.Release)
|
||||
}
|
||||
*c = d3d11Context{}
|
||||
if debugDirectX {
|
||||
d3d11.ReportLiveObjects()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *d3d11Context) releaseFBO() {
|
||||
if c.renderTarget != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(c.renderTarget), c.renderTarget.Vtbl.Release)
|
||||
c.renderTarget = nil
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !android
|
||||
|
||||
package app
|
||||
|
||||
import "os"
|
||||
|
||||
func dataDir() (string, error) {
|
||||
return os.UserConfigDir()
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
/*
|
||||
Package app provides a platform-independent interface to operating system
|
||||
functionality for running graphical user interfaces.
|
||||
|
||||
See https://gioui.org for instructions to set up and run Gio programs.
|
||||
|
||||
# Windows
|
||||
|
||||
A Window is run by calling its Event method in a loop. The first time a
|
||||
method on Window is called, a new GUI window is created and shown. On mobile
|
||||
platforms or when Gio is embedded in another project, Window merely connects
|
||||
with a previously created GUI window.
|
||||
|
||||
The most important event is [FrameEvent] that prompts an update of the window
|
||||
contents.
|
||||
|
||||
For example:
|
||||
|
||||
w := new(app.Window)
|
||||
for {
|
||||
e := w.Event()
|
||||
if e, ok := e.(app.FrameEvent); ok {
|
||||
ops.Reset()
|
||||
// Add operations to ops.
|
||||
...
|
||||
// Completely replace the window contents and state.
|
||||
e.Frame(ops)
|
||||
}
|
||||
}
|
||||
|
||||
A program must keep receiving events from the event channel until
|
||||
[DestroyEvent] is received.
|
||||
|
||||
# Main
|
||||
|
||||
The Main function must be called from a program's main function, to hand over
|
||||
control of the main thread to operating systems that need it.
|
||||
|
||||
Because Main is also blocking on some platforms, the event loop of a Window must run in a goroutine.
|
||||
|
||||
For example, to display a blank but otherwise functional window:
|
||||
|
||||
package main
|
||||
|
||||
import "gioui.org/app"
|
||||
|
||||
func main() {
|
||||
go func() {
|
||||
w := new(app.Window)
|
||||
for {
|
||||
w.Event()
|
||||
}
|
||||
}()
|
||||
app.Main()
|
||||
}
|
||||
|
||||
# Events
|
||||
|
||||
The [Events] iterator yields app-specific events such as [URLEvent]. [Window.Event]
|
||||
yields events that target a particular window.
|
||||
|
||||
# Permissions
|
||||
|
||||
The packages under gioui.org/app/permission should be imported
|
||||
by a Gio program or by one of its dependencies to indicate that specific
|
||||
operating-system permissions are required. Please see documentation for
|
||||
package gioui.org/app/permission for more information.
|
||||
*/
|
||||
package app
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !noopengl
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#include <android/native_window_jni.h>
|
||||
#include <EGL/egl.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/internal/egl"
|
||||
)
|
||||
|
||||
type androidContext struct {
|
||||
win *window
|
||||
eglSurf egl.NativeWindowType
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func init() {
|
||||
newAndroidGLESContext = func(w *window) (context, error) {
|
||||
ctx, err := egl.NewContext(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &androidContext{win: w, Context: ctx}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *androidContext) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *androidContext) Refresh() error {
|
||||
c.Context.ReleaseSurface()
|
||||
if err := c.win.setVisual(c.Context.VisualID()); err != nil {
|
||||
return err
|
||||
}
|
||||
win, _, _ := c.win.nativeWindow()
|
||||
c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *androidContext) Lock() error {
|
||||
// The Android emulator creates a broken surface if it is not
|
||||
// created on the same thread as the context is made current.
|
||||
if c.eglSurf != nil {
|
||||
if err := c.Context.CreateSurface(c.eglSurf); err != nil {
|
||||
return err
|
||||
}
|
||||
c.eglSurf = nil
|
||||
}
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *androidContext) Unlock() {
|
||||
c.Context.ReleaseCurrent()
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build ((linux && !android) || freebsd) && !nowayland && !noopengl
|
||||
// +build linux,!android freebsd
|
||||
// +build !nowayland
|
||||
// +build !noopengl
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/internal/egl"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: egl wayland-egl
|
||||
#cgo freebsd openbsd LDFLAGS: -lwayland-egl
|
||||
#cgo CFLAGS: -DEGL_NO_X11
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <wayland-client.h>
|
||||
#include <wayland-egl.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type wlContext struct {
|
||||
win *window
|
||||
*egl.Context
|
||||
eglWin *C.struct_wl_egl_window
|
||||
}
|
||||
|
||||
func init() {
|
||||
newWaylandEGLContext = func(w *window) (context, error) {
|
||||
disp := egl.NativeDisplayType(unsafe.Pointer(w.display()))
|
||||
ctx, err := egl.NewContext(disp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
surf, width, height := w.surface()
|
||||
if surf == nil {
|
||||
return nil, errors.New("wayland: no surface")
|
||||
}
|
||||
eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
|
||||
if eglWin == nil {
|
||||
return nil, errors.New("wayland: wl_egl_window_create failed")
|
||||
}
|
||||
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
||||
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We're in charge of the frame callbacks, don't let eglSwapBuffers
|
||||
// wait for callbacks that may never arrive.
|
||||
ctx.EnableVSync(false)
|
||||
|
||||
return &wlContext{Context: ctx, win: w, eglWin: eglWin}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *wlContext) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
if c.eglWin != nil {
|
||||
C.wl_egl_window_destroy(c.eglWin)
|
||||
c.eglWin = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *wlContext) Refresh() error {
|
||||
surf, width, height := c.win.surface()
|
||||
if surf == nil {
|
||||
return errors.New("wayland: no surface")
|
||||
}
|
||||
C.wl_egl_window_resize(c.eglWin, C.int(width), C.int(height), 0, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *wlContext) Lock() error {
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *wlContext) Unlock() {
|
||||
c.Context.ReleaseCurrent()
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !noopengl
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"gioui.org/internal/egl"
|
||||
)
|
||||
|
||||
type glContext struct {
|
||||
win *window
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func init() {
|
||||
drivers = append(drivers, gpuAPI{
|
||||
priority: 2,
|
||||
initializer: func(w *window) (context, error) {
|
||||
disp := egl.NativeDisplayType(w.HDC())
|
||||
ctx, err := egl.NewContext(disp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
win, _, _ := w.HWND()
|
||||
eglSurf := egl.NativeWindowType(win)
|
||||
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
if err := ctx.MakeCurrent(); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
defer ctx.ReleaseCurrent()
|
||||
ctx.EnableVSync(true)
|
||||
return &glContext{win: w, Context: ctx}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (c *glContext) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *glContext) Refresh() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Lock() error {
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *glContext) Unlock() {
|
||||
c.Context.ReleaseCurrent()
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build ((linux && !android) || freebsd || openbsd) && !nox11 && !noopengl
|
||||
// +build linux,!android freebsd openbsd
|
||||
// +build !nox11
|
||||
// +build !noopengl
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/internal/egl"
|
||||
)
|
||||
|
||||
type x11Context struct {
|
||||
win *x11Window
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func init() {
|
||||
newX11EGLContext = func(w *x11Window) (context, error) {
|
||||
disp := egl.NativeDisplayType(unsafe.Pointer(w.display()))
|
||||
ctx, err := egl.NewContext(disp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
win, _, _ := w.window()
|
||||
eglSurf := egl.NativeWindowType(uintptr(win))
|
||||
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
if err := ctx.MakeCurrent(); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
defer ctx.ReleaseCurrent()
|
||||
ctx.EnableVSync(true)
|
||||
return &x11Context{win: w, Context: ctx}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *x11Context) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *x11Context) Refresh() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *x11Context) Lock() error {
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *x11Context) Unlock() {
|
||||
c.Context.ReleaseCurrent()
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
#include <UIKit/UIKit.h>
|
||||
|
||||
@interface GioViewController : UIViewController
|
||||
@end
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && ios && nometal
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
@import UIKit;
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <OpenGLES/ES2/gl.h>
|
||||
#include <OpenGLES/ES2/glext.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) int gio_renderbufferStorage(CFTypeRef ctx, CFTypeRef layer, GLenum buffer);
|
||||
__attribute__ ((visibility ("hidden"))) int gio_presentRenderbuffer(CFTypeRef ctx, GLenum buffer);
|
||||
__attribute__ ((visibility ("hidden"))) int gio_makeCurrent(CFTypeRef ctx);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createContext(void);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createGLLayer(void);
|
||||
|
||||
static CFTypeRef getViewLayer(CFTypeRef viewRef) {
|
||||
@autoreleasepool {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
return CFBridgingRetain(view.layer);
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/gl"
|
||||
)
|
||||
|
||||
type context struct {
|
||||
owner *window
|
||||
c *gl.Functions
|
||||
ctx C.CFTypeRef
|
||||
layer C.CFTypeRef
|
||||
init bool
|
||||
frameBuffer gl.Framebuffer
|
||||
colorBuffer gl.Renderbuffer
|
||||
}
|
||||
|
||||
func newContext(w *window) (*context, error) {
|
||||
ctx := C.gio_createContext()
|
||||
if ctx == 0 {
|
||||
return nil, fmt.Errorf("failed to create EAGLContext")
|
||||
}
|
||||
api := contextAPI()
|
||||
f, err := gl.NewFunctions(api.Context, api.ES)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &context{
|
||||
ctx: ctx,
|
||||
owner: w,
|
||||
layer: C.getViewLayer(w.contextView()),
|
||||
c: f,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func contextAPI() gpu.OpenGL {
|
||||
return gpu.OpenGL{}
|
||||
}
|
||||
|
||||
func (c *context) RenderTarget() gpu.RenderTarget {
|
||||
return gpu.OpenGLRenderTarget(c.frameBuffer)
|
||||
}
|
||||
|
||||
func (c *context) API() gpu.API {
|
||||
return contextAPI()
|
||||
}
|
||||
|
||||
func (c *context) Release() {
|
||||
if c.ctx == 0 {
|
||||
return
|
||||
}
|
||||
C.gio_renderbufferStorage(c.ctx, 0, C.GLenum(gl.RENDERBUFFER))
|
||||
c.c.DeleteFramebuffer(c.frameBuffer)
|
||||
c.c.DeleteRenderbuffer(c.colorBuffer)
|
||||
C.gio_makeCurrent(0)
|
||||
C.CFRelease(c.ctx)
|
||||
c.ctx = 0
|
||||
}
|
||||
|
||||
func (c *context) Present() error {
|
||||
if c.layer == 0 {
|
||||
panic("context is not active")
|
||||
}
|
||||
c.c.BindRenderbuffer(gl.RENDERBUFFER, c.colorBuffer)
|
||||
if C.gio_presentRenderbuffer(c.ctx, C.GLenum(gl.RENDERBUFFER)) == 0 {
|
||||
return errors.New("presentRenderBuffer failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Lock() error {
|
||||
// OpenGL contexts are implicit and thread-local. Lock the OS thread.
|
||||
runtime.LockOSThread()
|
||||
|
||||
if C.gio_makeCurrent(c.ctx) == 0 {
|
||||
return errors.New("[EAGLContext setCurrentContext] failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Unlock() {
|
||||
C.gio_makeCurrent(0)
|
||||
}
|
||||
|
||||
func (c *context) Refresh() error {
|
||||
if C.gio_makeCurrent(c.ctx) == 0 {
|
||||
return errors.New("[EAGLContext setCurrentContext] failed")
|
||||
}
|
||||
if !c.init {
|
||||
c.init = true
|
||||
c.frameBuffer = c.c.CreateFramebuffer()
|
||||
c.colorBuffer = c.c.CreateRenderbuffer()
|
||||
}
|
||||
if !c.owner.visible {
|
||||
// Make sure any in-flight GL commands are complete.
|
||||
c.c.Finish()
|
||||
return nil
|
||||
}
|
||||
currentRB := gl.Renderbuffer{uint(c.c.GetInteger(gl.RENDERBUFFER_BINDING))}
|
||||
c.c.BindRenderbuffer(gl.RENDERBUFFER, c.colorBuffer)
|
||||
if C.gio_renderbufferStorage(c.ctx, c.layer, C.GLenum(gl.RENDERBUFFER)) == 0 {
|
||||
return errors.New("renderbufferStorage failed")
|
||||
}
|
||||
c.c.BindRenderbuffer(gl.RENDERBUFFER, currentRB)
|
||||
c.c.BindFramebuffer(gl.FRAMEBUFFER, c.frameBuffer)
|
||||
c.c.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, c.colorBuffer)
|
||||
if st := c.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
|
||||
return fmt.Errorf("framebuffer incomplete, status: %#x\n", st)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
return newContext(w)
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,ios,nometal
|
||||
|
||||
@import UIKit;
|
||||
@import OpenGLES;
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
Class gio_layerClass(void) {
|
||||
return [CAEAGLLayer class];
|
||||
}
|
||||
|
||||
int gio_renderbufferStorage(CFTypeRef ctxRef, CFTypeRef layerRef, GLenum buffer) {
|
||||
EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
|
||||
CAEAGLLayer *layer = (__bridge CAEAGLLayer *)layerRef;
|
||||
return (int)[ctx renderbufferStorage:buffer fromDrawable:layer];
|
||||
}
|
||||
|
||||
int gio_presentRenderbuffer(CFTypeRef ctxRef, GLenum buffer) {
|
||||
EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
|
||||
return (int)[ctx presentRenderbuffer:buffer];
|
||||
}
|
||||
|
||||
int gio_makeCurrent(CFTypeRef ctxRef) {
|
||||
EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
|
||||
return (int)[EAGLContext setCurrentContext:ctx];
|
||||
}
|
||||
|
||||
CFTypeRef gio_createContext(void) {
|
||||
EAGLContext *ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
|
||||
if (ctx == nil) {
|
||||
return nil;
|
||||
}
|
||||
return CFBridgingRetain(ctx);
|
||||
}
|
||||
|
||||
CFTypeRef gio_createGLLayer(void) {
|
||||
CAEAGLLayer *layer = [[CAEAGLLayer layer] init];
|
||||
if (layer == nil) {
|
||||
return nil;
|
||||
}
|
||||
layer.drawableProperties = @{kEAGLDrawablePropertyColorFormat: kEAGLColorFormatSRGBA8};
|
||||
layer.opaque = YES;
|
||||
layer.anchorPoint = CGPointMake(0, 0);
|
||||
return CFBridgingRetain(layer);
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall/js"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/gl"
|
||||
)
|
||||
|
||||
type glContext struct {
|
||||
ctx js.Value
|
||||
cnv js.Value
|
||||
w *window
|
||||
}
|
||||
|
||||
func newContext(w *window) (*glContext, error) {
|
||||
args := map[string]interface{}{
|
||||
// Enable low latency rendering.
|
||||
// See https://developers.google.com/web/updates/2019/05/desynchronized.
|
||||
"desynchronized": true,
|
||||
"preserveDrawingBuffer": true,
|
||||
}
|
||||
ctx := w.cnv.Call("getContext", "webgl2", args)
|
||||
if ctx.IsNull() {
|
||||
ctx = w.cnv.Call("getContext", "webgl", args)
|
||||
}
|
||||
if ctx.IsNull() {
|
||||
return nil, errors.New("app: webgl is not supported")
|
||||
}
|
||||
c := &glContext{
|
||||
ctx: ctx,
|
||||
cnv: w.cnv,
|
||||
w: w,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *glContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
if c.w.contextStatus != contextStatusOkay {
|
||||
return nil, gpu.ErrDeviceLost
|
||||
}
|
||||
return gpu.OpenGLRenderTarget{}, nil
|
||||
}
|
||||
|
||||
func (c *glContext) API() gpu.API {
|
||||
return gpu.OpenGL{Context: gl.Context(c.ctx)}
|
||||
}
|
||||
|
||||
func (c *glContext) Release() {
|
||||
}
|
||||
|
||||
func (c *glContext) Present() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Lock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Unlock() {}
|
||||
|
||||
func (c *glContext) Refresh() error {
|
||||
switch c.w.contextStatus {
|
||||
case contextStatusLost:
|
||||
return errOutOfDate
|
||||
case contextStatusRestored:
|
||||
c.w.contextStatus = contextStatusOkay
|
||||
return gpu.ErrDeviceLost
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (context, error) {
|
||||
return newContext(w)
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && !ios && nometal
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/gl"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION -xobjective-c -fobjc-arc
|
||||
#cgo LDFLAGS: -framework OpenGL
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <AppKit/AppKit.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createGLContext(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_setContextView(CFTypeRef ctx, CFTypeRef view);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_makeCurrentContext(CFTypeRef ctx);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_updateContext(CFTypeRef ctx);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_flushContextBuffer(CFTypeRef ctx);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_clearCurrentContext(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_lockContext(CFTypeRef ctxRef);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_unlockContext(CFTypeRef ctxRef);
|
||||
|
||||
typedef void (*PFN_glFlush)(void);
|
||||
|
||||
static void glFlush(PFN_glFlush f) {
|
||||
f();
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type glContext struct {
|
||||
c *gl.Functions
|
||||
ctx C.CFTypeRef
|
||||
view C.CFTypeRef
|
||||
|
||||
glFlush C.PFN_glFlush
|
||||
}
|
||||
|
||||
func newContext(w *window) (*glContext, error) {
|
||||
clib := C.CString("/System/Library/Frameworks/OpenGL.framework/OpenGL")
|
||||
defer C.free(unsafe.Pointer(clib))
|
||||
lib, err := C.dlopen(clib, C.RTLD_NOW|C.RTLD_LOCAL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csym := C.CString("glFlush")
|
||||
defer C.free(unsafe.Pointer(csym))
|
||||
glFlush := C.PFN_glFlush(C.dlsym(lib, csym))
|
||||
if glFlush == nil {
|
||||
return nil, errors.New("gl: missing symbol glFlush in the OpenGL framework")
|
||||
}
|
||||
view := w.contextView()
|
||||
ctx := C.gio_createGLContext()
|
||||
if ctx == 0 {
|
||||
return nil, errors.New("gl: failed to create NSOpenGLContext")
|
||||
}
|
||||
C.gio_setContextView(ctx, view)
|
||||
c := &glContext{
|
||||
ctx: ctx,
|
||||
view: view,
|
||||
glFlush: glFlush,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *glContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
return gpu.OpenGLRenderTarget{}, nil
|
||||
}
|
||||
|
||||
func (c *glContext) API() gpu.API {
|
||||
return gpu.OpenGL{}
|
||||
}
|
||||
|
||||
func (c *glContext) Release() {
|
||||
if c.ctx != 0 {
|
||||
C.gio_clearCurrentContext()
|
||||
C.CFRelease(c.ctx)
|
||||
c.ctx = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (c *glContext) Present() error {
|
||||
// Assume the caller already locked the context.
|
||||
C.glFlush(c.glFlush)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Lock() error {
|
||||
// OpenGL contexts are implicit and thread-local. Lock the OS thread.
|
||||
runtime.LockOSThread()
|
||||
|
||||
C.gio_lockContext(c.ctx)
|
||||
C.gio_makeCurrentContext(c.ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Unlock() {
|
||||
C.gio_clearCurrentContext()
|
||||
C.gio_unlockContext(c.ctx)
|
||||
}
|
||||
|
||||
func (c *glContext) Refresh() error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
C.gio_updateContext(c.ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (context, error) {
|
||||
return newContext(w)
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,!ios,nometal
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#include "_cgo_export.h"
|
||||
|
||||
CALayer *gio_layerFactory(BOOL presentWithTrans) {
|
||||
@autoreleasepool {
|
||||
return [CALayer layer];
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_createGLContext(void) {
|
||||
@autoreleasepool {
|
||||
NSOpenGLPixelFormatAttribute attr[] = {
|
||||
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
|
||||
NSOpenGLPFAColorSize, 24,
|
||||
NSOpenGLPFAAccelerated,
|
||||
// Opt-in to automatic GPU switching. CGL-only property.
|
||||
kCGLPFASupportsAutomaticGraphicsSwitching,
|
||||
NSOpenGLPFAAllowOfflineRenderers,
|
||||
0
|
||||
};
|
||||
NSOpenGLPixelFormat *pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
|
||||
|
||||
NSOpenGLContext *ctx = [[NSOpenGLContext alloc] initWithFormat:pixFormat shareContext: nil];
|
||||
return CFBridgingRetain(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void gio_setContextView(CFTypeRef ctxRef, CFTypeRef viewRef) {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
[view setWantsBestResolutionOpenGLSurface:YES];
|
||||
[ctx setView:view];
|
||||
}
|
||||
|
||||
void gio_clearCurrentContext(void) {
|
||||
@autoreleasepool {
|
||||
[NSOpenGLContext clearCurrentContext];
|
||||
}
|
||||
}
|
||||
|
||||
void gio_updateContext(CFTypeRef ctxRef) {
|
||||
@autoreleasepool {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
[ctx update];
|
||||
}
|
||||
}
|
||||
|
||||
void gio_makeCurrentContext(CFTypeRef ctxRef) {
|
||||
@autoreleasepool {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
[ctx makeCurrentContext];
|
||||
}
|
||||
}
|
||||
|
||||
void gio_lockContext(CFTypeRef ctxRef) {
|
||||
@autoreleasepool {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
CGLLockContext([ctx CGLContextObj]);
|
||||
}
|
||||
}
|
||||
|
||||
void gio_unlockContext(CFTypeRef ctxRef) {
|
||||
@autoreleasepool {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
CGLUnlockContext([ctx CGLContextObj]);
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"gioui.org/io/input"
|
||||
"gioui.org/io/key"
|
||||
)
|
||||
|
||||
type editorState struct {
|
||||
input.EditorState
|
||||
compose key.Range
|
||||
}
|
||||
|
||||
func (e *editorState) Replace(r key.Range, text string) {
|
||||
if r.Start > r.End {
|
||||
r.Start, r.End = r.End, r.Start
|
||||
}
|
||||
runes := []rune(text)
|
||||
newEnd := r.Start + len(runes)
|
||||
adjust := func(pos int) int {
|
||||
switch {
|
||||
case newEnd < pos && pos <= r.End:
|
||||
return newEnd
|
||||
case r.End < pos:
|
||||
diff := newEnd - r.End
|
||||
return pos + diff
|
||||
}
|
||||
return pos
|
||||
}
|
||||
e.Selection.Start = adjust(e.Selection.Start)
|
||||
e.Selection.End = adjust(e.Selection.End)
|
||||
if e.compose.Start != -1 {
|
||||
e.compose.Start = adjust(e.compose.Start)
|
||||
e.compose.End = adjust(e.compose.End)
|
||||
}
|
||||
s := e.Snippet
|
||||
if r.End < s.Start || r.Start > s.End {
|
||||
// Discard snippet if it doesn't overlap with replacement.
|
||||
s = key.Snippet{
|
||||
Range: key.Range{
|
||||
Start: r.Start,
|
||||
End: r.Start,
|
||||
},
|
||||
}
|
||||
}
|
||||
var newSnippet []rune
|
||||
snippet := []rune(s.Text)
|
||||
// Append first part of existing snippet.
|
||||
if end := r.Start - s.Start; end > 0 {
|
||||
newSnippet = append(newSnippet, snippet[:end]...)
|
||||
}
|
||||
// Append replacement.
|
||||
newSnippet = append(newSnippet, runes...)
|
||||
// Append last part of existing snippet.
|
||||
if start := r.End; start < s.End {
|
||||
newSnippet = append(newSnippet, snippet[start-s.Start:]...)
|
||||
}
|
||||
// Adjust snippet range to include replacement.
|
||||
if r.Start < s.Start {
|
||||
s.Start = r.Start
|
||||
}
|
||||
s.End = s.Start + len(newSnippet)
|
||||
s.Text = string(newSnippet)
|
||||
e.Snippet = s
|
||||
}
|
||||
|
||||
// UTF16Index converts the given index in runes into an index in utf16 characters.
|
||||
func (e *editorState) UTF16Index(runes int) int {
|
||||
if runes == -1 {
|
||||
return -1
|
||||
}
|
||||
if runes < e.Snippet.Start {
|
||||
// Assume runes before sippet are one UTF-16 character each.
|
||||
return runes
|
||||
}
|
||||
chars := e.Snippet.Start
|
||||
runes -= e.Snippet.Start
|
||||
for _, r := range e.Snippet.Text {
|
||||
if runes == 0 {
|
||||
break
|
||||
}
|
||||
runes--
|
||||
chars++
|
||||
if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar {
|
||||
chars++
|
||||
}
|
||||
}
|
||||
// Assume runes after snippets are one UTF-16 character each.
|
||||
return chars + runes
|
||||
}
|
||||
|
||||
// RunesIndex converts the given index in utf16 characters to an index in runes.
|
||||
func (e *editorState) RunesIndex(chars int) int {
|
||||
if chars == -1 {
|
||||
return -1
|
||||
}
|
||||
if chars < e.Snippet.Start {
|
||||
// Assume runes before offset are one UTF-16 character each.
|
||||
return chars
|
||||
}
|
||||
runes := e.Snippet.Start
|
||||
chars -= e.Snippet.Start
|
||||
for _, r := range e.Snippet.Text {
|
||||
if chars == 0 {
|
||||
break
|
||||
}
|
||||
chars--
|
||||
runes++
|
||||
if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar {
|
||||
chars--
|
||||
}
|
||||
}
|
||||
// Assume runes after snippets are one UTF-16 character each.
|
||||
return runes + chars
|
||||
}
|
||||
|
||||
// areSnippetsConsistent reports whether the content of the old snippet is
|
||||
// consistent with the content of the new.
|
||||
func areSnippetsConsistent(old, new key.Snippet) bool {
|
||||
// Compute the overlapping range.
|
||||
r := old.Range
|
||||
r.Start = max(r.Start, new.Start)
|
||||
r.End = max(r.End, r.Start)
|
||||
r.End = min(r.End, new.End)
|
||||
return snippetSubstring(old, r) == snippetSubstring(new, r)
|
||||
}
|
||||
|
||||
func snippetSubstring(s key.Snippet, r key.Range) string {
|
||||
for r.Start > s.Start && r.Start < s.End {
|
||||
_, n := utf8.DecodeRuneInString(s.Text)
|
||||
s.Text = s.Text[n:]
|
||||
s.Start++
|
||||
}
|
||||
for r.End < s.End && r.End > s.Start {
|
||||
_, n := utf8.DecodeLastRuneInString(s.Text)
|
||||
s.Text = s.Text[:len(s.Text)-n]
|
||||
s.End--
|
||||
}
|
||||
return s.Text
|
||||
}
|
||||
+997
@@ -0,0 +1,997 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type CompositionForm struct {
|
||||
dwStyle uint32
|
||||
ptCurrentPos Point
|
||||
rcArea Rect
|
||||
}
|
||||
|
||||
type CandidateForm struct {
|
||||
dwIndex uint32
|
||||
dwStyle uint32
|
||||
ptCurrentPos Point
|
||||
rcArea Rect
|
||||
}
|
||||
|
||||
type Rect struct {
|
||||
Left, Top, Right, Bottom int32
|
||||
}
|
||||
|
||||
type WndClassEx struct {
|
||||
CbSize uint32
|
||||
Style uint32
|
||||
LpfnWndProc uintptr
|
||||
CnClsExtra int32
|
||||
CbWndExtra int32
|
||||
HInstance syscall.Handle
|
||||
HIcon syscall.Handle
|
||||
HCursor syscall.Handle
|
||||
HbrBackground syscall.Handle
|
||||
LpszMenuName *uint16
|
||||
LpszClassName *uint16
|
||||
HIconSm syscall.Handle
|
||||
}
|
||||
|
||||
type Margins struct {
|
||||
CxLeftWidth int32
|
||||
CxRightWidth int32
|
||||
CyTopHeight int32
|
||||
CyBottomHeight int32
|
||||
}
|
||||
|
||||
type Msg struct {
|
||||
Hwnd syscall.Handle
|
||||
Message uint32
|
||||
WParam uintptr
|
||||
LParam uintptr
|
||||
Time uint32
|
||||
Pt Point
|
||||
LPrivate uint32
|
||||
}
|
||||
|
||||
type Point struct {
|
||||
X, Y int32
|
||||
}
|
||||
|
||||
type MinMaxInfo struct {
|
||||
PtReserved Point
|
||||
PtMaxSize Point
|
||||
PtMaxPosition Point
|
||||
PtMinTrackSize Point
|
||||
PtMaxTrackSize Point
|
||||
}
|
||||
|
||||
type NCCalcSizeParams struct {
|
||||
Rgrc [3]Rect
|
||||
LpPos *WindowPos
|
||||
}
|
||||
|
||||
type WindowPos struct {
|
||||
HWND syscall.Handle
|
||||
HWNDInsertAfter syscall.Handle
|
||||
x int32
|
||||
y int32
|
||||
cx int32
|
||||
cy int32
|
||||
flags uint32
|
||||
}
|
||||
|
||||
type WindowPlacement struct {
|
||||
length uint32
|
||||
flags uint32
|
||||
showCmd uint32
|
||||
ptMinPosition Point
|
||||
ptMaxPosition Point
|
||||
rcNormalPosition Rect
|
||||
rcDevice Rect
|
||||
}
|
||||
|
||||
type MonitorInfo struct {
|
||||
cbSize uint32
|
||||
Monitor Rect
|
||||
WorkArea Rect
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
type CopyDataStruct struct {
|
||||
DwData uintptr
|
||||
CbData uint32
|
||||
LpData uintptr
|
||||
}
|
||||
|
||||
type POINTER_INPUT_TYPE int32
|
||||
|
||||
const (
|
||||
PT_POINTER POINTER_INPUT_TYPE = 1
|
||||
PT_TOUCH POINTER_INPUT_TYPE = 2
|
||||
PT_PEN POINTER_INPUT_TYPE = 3
|
||||
PT_MOUSE POINTER_INPUT_TYPE = 4
|
||||
PT_TOUCHPAD POINTER_INPUT_TYPE = 5
|
||||
)
|
||||
|
||||
type POINTER_INFO_POINTER_FLAGS int32
|
||||
|
||||
const (
|
||||
POINTER_FLAG_NEW POINTER_INFO_POINTER_FLAGS = 0x00000001
|
||||
POINTER_FLAG_INRANGE POINTER_INFO_POINTER_FLAGS = 0x00000002
|
||||
POINTER_FLAG_INCONTACT POINTER_INFO_POINTER_FLAGS = 0x00000004
|
||||
POINTER_FLAG_FIRSTBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000010
|
||||
POINTER_FLAG_SECONDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000020
|
||||
POINTER_FLAG_THIRDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000040
|
||||
POINTER_FLAG_FOURTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000080
|
||||
POINTER_FLAG_FIFTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000100
|
||||
POINTER_FLAG_PRIMARY POINTER_INFO_POINTER_FLAGS = 0x00002000
|
||||
POINTER_FLAG_CONFIDENCE POINTER_INFO_POINTER_FLAGS = 0x00004000
|
||||
POINTER_FLAG_CANCELED POINTER_INFO_POINTER_FLAGS = 0x00008000
|
||||
POINTER_FLAG_DOWN POINTER_INFO_POINTER_FLAGS = 0x00010000
|
||||
POINTER_FLAG_UPDATE POINTER_INFO_POINTER_FLAGS = 0x00020000
|
||||
POINTER_FLAG_UP POINTER_INFO_POINTER_FLAGS = 0x00040000
|
||||
POINTER_FLAG_WHEEL POINTER_INFO_POINTER_FLAGS = 0x00080000
|
||||
POINTER_FLAG_HWHEEL POINTER_INFO_POINTER_FLAGS = 0x00100000
|
||||
POINTER_FLAG_CAPTURECHANGED POINTER_INFO_POINTER_FLAGS = 0x00200000
|
||||
POINTER_FLAG_HASTRANSFORM POINTER_INFO_POINTER_FLAGS = 0x00400000
|
||||
)
|
||||
|
||||
type POINTER_BUTTON_CHANGE_TYPE int32
|
||||
|
||||
const (
|
||||
POINTER_CHANGE_NONE POINTER_BUTTON_CHANGE_TYPE = 0
|
||||
POINTER_CHANGE_FIRSTBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 1
|
||||
POINTER_CHANGE_FIRSTBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 2
|
||||
POINTER_CHANGE_SECONDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 3
|
||||
POINTER_CHANGE_SECONDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 4
|
||||
POINTER_CHANGE_THIRDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 5
|
||||
POINTER_CHANGE_THIRDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 6
|
||||
POINTER_CHANGE_FOURTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 7
|
||||
POINTER_CHANGE_FOURTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 8
|
||||
POINTER_CHANGE_FIFTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 9
|
||||
POINTER_CHANGE_FIFTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 10
|
||||
)
|
||||
|
||||
type PointerInfo struct {
|
||||
PointerType POINTER_INPUT_TYPE
|
||||
PointerId uint32
|
||||
FrameId uint32
|
||||
PointerFlags POINTER_INFO_POINTER_FLAGS
|
||||
SourceDevice syscall.Handle
|
||||
HwndTarget syscall.Handle
|
||||
PtPixelLocation Point
|
||||
PtHimetricLocation Point
|
||||
PtPixelLocationRaw Point
|
||||
PtHimetricLocationRaw Point
|
||||
DwTime uint32
|
||||
HistoryCount uint32
|
||||
InputData int32
|
||||
DwKeyStates uint32
|
||||
PerformanceCount uint64
|
||||
ButtonChangeType POINTER_BUTTON_CHANGE_TYPE
|
||||
}
|
||||
|
||||
const (
|
||||
TRUE = 1
|
||||
|
||||
CPS_CANCEL = 0x0004
|
||||
|
||||
CS_HREDRAW = 0x0002
|
||||
CS_INSERTCHAR = 0x2000
|
||||
CS_NOMOVECARET = 0x4000
|
||||
CS_VREDRAW = 0x0001
|
||||
CS_OWNDC = 0x0020
|
||||
|
||||
CW_USEDEFAULT = -2147483648
|
||||
|
||||
GWL_STYLE = ^(uintptr(16) - 1) // -16
|
||||
|
||||
GCS_COMPSTR = 0x0008
|
||||
GCS_COMPREADSTR = 0x0001
|
||||
GCS_CURSORPOS = 0x0080
|
||||
GCS_DELTASTART = 0x0100
|
||||
GCS_RESULTREADSTR = 0x0200
|
||||
GCS_RESULTSTR = 0x0800
|
||||
|
||||
CFS_POINT = 0x0002
|
||||
CFS_CANDIDATEPOS = 0x0040
|
||||
|
||||
HWND_TOP = syscall.Handle(0)
|
||||
HWND_TOPMOST = ^(syscall.Handle(1) - 1) // -1
|
||||
HWND_NOTOPMOST = ^(syscall.Handle(2) - 1) // -2
|
||||
|
||||
HTCAPTION = 2
|
||||
HTCLIENT = 1
|
||||
HTLEFT = 10
|
||||
HTRIGHT = 11
|
||||
HTTOP = 12
|
||||
HTTOPLEFT = 13
|
||||
HTTOPRIGHT = 14
|
||||
HTBOTTOM = 15
|
||||
HTBOTTOMLEFT = 16
|
||||
HTBOTTOMRIGHT = 17
|
||||
|
||||
IDC_APPSTARTING = 32650 // Standard arrow and small hourglass
|
||||
IDC_ARROW = 32512 // Standard arrow
|
||||
IDC_CROSS = 32515 // Crosshair
|
||||
IDC_HAND = 32649 // Hand
|
||||
IDC_HELP = 32651 // Arrow and question mark
|
||||
IDC_IBEAM = 32513 // I-beam
|
||||
IDC_NO = 32648 // Slashed circle
|
||||
IDC_SIZEALL = 32646 // Four-pointed arrow pointing north, south, east, and west
|
||||
IDC_SIZENESW = 32643 // Double-pointed arrow pointing northeast and southwest
|
||||
IDC_SIZENS = 32645 // Double-pointed arrow pointing north and south
|
||||
IDC_SIZENWSE = 32642 // Double-pointed arrow pointing northwest and southeast
|
||||
IDC_SIZEWE = 32644 // Double-pointed arrow pointing west and east
|
||||
IDC_UPARROW = 32516 // Vertical arrow
|
||||
IDC_WAIT = 32514 // Hour
|
||||
|
||||
INFINITE = 0xFFFFFFFF
|
||||
|
||||
LOGPIXELSX = 88
|
||||
|
||||
MDT_EFFECTIVE_DPI = 0
|
||||
|
||||
MONITOR_DEFAULTTOPRIMARY = 1
|
||||
|
||||
NI_COMPOSITIONSTR = 0x0015
|
||||
|
||||
SIZE_MAXIMIZED = 2
|
||||
SIZE_MINIMIZED = 1
|
||||
SIZE_RESTORED = 0
|
||||
|
||||
SCS_SETSTR = GCS_COMPREADSTR | GCS_COMPSTR
|
||||
|
||||
SM_CXSIZEFRAME = 32
|
||||
SM_CYSIZEFRAME = 33
|
||||
|
||||
SW_SHOWDEFAULT = 10
|
||||
SW_SHOWMINIMIZED = 2
|
||||
SW_SHOWMAXIMIZED = 3
|
||||
SW_SHOWNORMAL = 1
|
||||
SW_SHOW = 5
|
||||
|
||||
SWP_FRAMECHANGED = 0x0020
|
||||
SWP_NOMOVE = 0x0002
|
||||
SWP_NOOWNERZORDER = 0x0200
|
||||
SWP_NOSIZE = 0x0001
|
||||
SWP_NOZORDER = 0x0004
|
||||
SWP_SHOWWINDOW = 0x0040
|
||||
|
||||
USER_TIMER_MINIMUM = 0x0000000A
|
||||
|
||||
VK_CONTROL = 0x11
|
||||
VK_LWIN = 0x5B
|
||||
VK_MENU = 0x12
|
||||
VK_RWIN = 0x5C
|
||||
VK_SHIFT = 0x10
|
||||
|
||||
VK_BACK = 0x08
|
||||
VK_DELETE = 0x2e
|
||||
VK_DOWN = 0x28
|
||||
VK_END = 0x23
|
||||
VK_ESCAPE = 0x1b
|
||||
VK_HOME = 0x24
|
||||
VK_LEFT = 0x25
|
||||
VK_NEXT = 0x22
|
||||
VK_PRIOR = 0x21
|
||||
VK_RIGHT = 0x27
|
||||
VK_RETURN = 0x0d
|
||||
VK_SPACE = 0x20
|
||||
VK_TAB = 0x09
|
||||
VK_UP = 0x26
|
||||
|
||||
VK_F1 = 0x70
|
||||
VK_F2 = 0x71
|
||||
VK_F3 = 0x72
|
||||
VK_F4 = 0x73
|
||||
VK_F5 = 0x74
|
||||
VK_F6 = 0x75
|
||||
VK_F7 = 0x76
|
||||
VK_F8 = 0x77
|
||||
VK_F9 = 0x78
|
||||
VK_F10 = 0x79
|
||||
VK_F11 = 0x7A
|
||||
VK_F12 = 0x7B
|
||||
|
||||
VK_OEM_1 = 0xba
|
||||
VK_OEM_PLUS = 0xbb
|
||||
VK_OEM_COMMA = 0xbc
|
||||
VK_OEM_MINUS = 0xbd
|
||||
VK_OEM_PERIOD = 0xbe
|
||||
VK_OEM_2 = 0xbf
|
||||
VK_OEM_3 = 0xc0
|
||||
VK_OEM_4 = 0xdb
|
||||
VK_OEM_5 = 0xdc
|
||||
VK_OEM_6 = 0xdd
|
||||
VK_OEM_7 = 0xde
|
||||
VK_OEM_102 = 0xe2
|
||||
|
||||
UNICODE_NOCHAR = 65535
|
||||
|
||||
WM_CANCELMODE = 0x001F
|
||||
WM_CHAR = 0x0102
|
||||
WM_CLOSE = 0x0010
|
||||
WM_COPYDATA = 0x004A
|
||||
WM_CREATE = 0x0001
|
||||
WM_DPICHANGED = 0x02E0
|
||||
WM_DESTROY = 0x0002
|
||||
WM_ERASEBKGND = 0x0014
|
||||
WM_GETMINMAXINFO = 0x0024
|
||||
WM_IME_COMPOSITION = 0x010F
|
||||
WM_IME_ENDCOMPOSITION = 0x010E
|
||||
WM_IME_STARTCOMPOSITION = 0x010D
|
||||
WM_KEYDOWN = 0x0100
|
||||
WM_KEYUP = 0x0101
|
||||
WM_KILLFOCUS = 0x0008
|
||||
WM_LBUTTONDOWN = 0x0201
|
||||
WM_LBUTTONUP = 0x0202
|
||||
WM_MBUTTONDOWN = 0x0207
|
||||
WM_MBUTTONUP = 0x0208
|
||||
WM_MOUSEMOVE = 0x0200
|
||||
WM_MOUSEWHEEL = 0x020A
|
||||
WM_MOUSEHWHEEL = 0x020E
|
||||
WM_NCACTIVATE = 0x0086
|
||||
WM_NCHITTEST = 0x0084
|
||||
WM_NCCALCSIZE = 0x0083
|
||||
WM_PAINT = 0x000F
|
||||
WM_POINTERCAPTURECHANGED = 0x024C
|
||||
WM_POINTERDOWN = 0x0246
|
||||
WM_POINTERUP = 0x0247
|
||||
WM_POINTERUPDATE = 0x0245
|
||||
WM_POINTERWHEEL = 0x024E
|
||||
WM_POINTERHWHEEL = 0x024F
|
||||
WM_QUIT = 0x0012
|
||||
WM_RBUTTONDOWN = 0x0204
|
||||
WM_RBUTTONUP = 0x0205
|
||||
WM_SETCURSOR = 0x0020
|
||||
WM_SETFOCUS = 0x0007
|
||||
WM_SHOWWINDOW = 0x0018
|
||||
WM_SIZE = 0x0005
|
||||
WM_STYLECHANGED = 0x007D
|
||||
WM_SYSKEYDOWN = 0x0104
|
||||
WM_SYSKEYUP = 0x0105
|
||||
WM_TIMER = 0x0113
|
||||
WM_UNICHAR = 0x0109
|
||||
WM_USER = 0x0400
|
||||
WM_WINDOWPOSCHANGED = 0x0047
|
||||
|
||||
WS_CLIPCHILDREN = 0x02000000
|
||||
WS_CLIPSIBLINGS = 0x04000000
|
||||
WS_MAXIMIZE = 0x01000000
|
||||
WS_ICONIC = 0x20000000
|
||||
WS_VISIBLE = 0x10000000
|
||||
WS_OVERLAPPED = 0x00000000
|
||||
WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME |
|
||||
WS_MINIMIZEBOX | WS_MAXIMIZEBOX
|
||||
WS_CAPTION = 0x00C00000
|
||||
WS_SYSMENU = 0x00080000
|
||||
WS_THICKFRAME = 0x00040000
|
||||
WS_MINIMIZEBOX = 0x00020000
|
||||
WS_MAXIMIZEBOX = 0x00010000
|
||||
|
||||
WS_EX_APPWINDOW = 0x00040000
|
||||
WS_EX_WINDOWEDGE = 0x00000100
|
||||
|
||||
QS_ALLINPUT = 0x04FF
|
||||
|
||||
MWMO_WAITALL = 0x0001
|
||||
MWMO_INPUTAVAILABLE = 0x0004
|
||||
|
||||
WAIT_OBJECT_0 = 0
|
||||
|
||||
PM_REMOVE = 0x0001
|
||||
PM_NOREMOVE = 0x0000
|
||||
|
||||
GHND = 0x0042
|
||||
|
||||
CF_UNICODETEXT = 13
|
||||
IMAGE_BITMAP = 0
|
||||
IMAGE_ICON = 1
|
||||
IMAGE_CURSOR = 2
|
||||
|
||||
LR_CREATEDIBSECTION = 0x00002000
|
||||
LR_DEFAULTCOLOR = 0x00000000
|
||||
LR_DEFAULTSIZE = 0x00000040
|
||||
LR_LOADFROMFILE = 0x00000010
|
||||
LR_LOADMAP3DCOLORS = 0x00001000
|
||||
LR_LOADTRANSPARENT = 0x00000020
|
||||
LR_MONOCHROME = 0x00000001
|
||||
LR_SHARED = 0x00008000
|
||||
LR_VGACOLOR = 0x00000080
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazySystemDLL("kernel32.dll")
|
||||
_GetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
|
||||
_GlobalAlloc = kernel32.NewProc("GlobalAlloc")
|
||||
_GlobalFree = kernel32.NewProc("GlobalFree")
|
||||
_GlobalLock = kernel32.NewProc("GlobalLock")
|
||||
_GlobalUnlock = kernel32.NewProc("GlobalUnlock")
|
||||
|
||||
user32 = syscall.NewLazySystemDLL("user32.dll")
|
||||
_AdjustWindowRectEx = user32.NewProc("AdjustWindowRectEx")
|
||||
_CallMsgFilter = user32.NewProc("CallMsgFilterW")
|
||||
_CloseClipboard = user32.NewProc("CloseClipboard")
|
||||
_CreateWindowEx = user32.NewProc("CreateWindowExW")
|
||||
_DefWindowProc = user32.NewProc("DefWindowProcW")
|
||||
_DestroyWindow = user32.NewProc("DestroyWindow")
|
||||
_DispatchMessage = user32.NewProc("DispatchMessageW")
|
||||
_FindWindow = user32.NewProc("FindWindowW")
|
||||
_EmptyClipboard = user32.NewProc("EmptyClipboard")
|
||||
_EnableMouseInPointer = user32.NewProc("EnableMouseInPointer")
|
||||
_GetWindowRect = user32.NewProc("GetWindowRect")
|
||||
_GetClientRect = user32.NewProc("GetClientRect")
|
||||
_GetClipboardData = user32.NewProc("GetClipboardData")
|
||||
_GetDC = user32.NewProc("GetDC")
|
||||
_GetDpiForWindow = user32.NewProc("GetDpiForWindow")
|
||||
_GetKeyState = user32.NewProc("GetKeyState")
|
||||
_GetMessage = user32.NewProc("GetMessageW")
|
||||
_GetMessageTime = user32.NewProc("GetMessageTime")
|
||||
_GetMonitorInfo = user32.NewProc("GetMonitorInfoW")
|
||||
_GetPointerInfo = user32.NewProc("GetPointerInfo")
|
||||
_GetSystemMetrics = user32.NewProc("GetSystemMetrics")
|
||||
_GetWindowLong = user32.NewProc("GetWindowLongPtrW")
|
||||
_GetWindowLong32 = user32.NewProc("GetWindowLongW")
|
||||
_GetWindowPlacement = user32.NewProc("GetWindowPlacement")
|
||||
_KillTimer = user32.NewProc("KillTimer")
|
||||
_LoadCursor = user32.NewProc("LoadCursorW")
|
||||
_LoadImage = user32.NewProc("LoadImageW")
|
||||
_MonitorFromPoint = user32.NewProc("MonitorFromPoint")
|
||||
_MonitorFromWindow = user32.NewProc("MonitorFromWindow")
|
||||
_MoveWindow = user32.NewProc("MoveWindow")
|
||||
_MsgWaitForMultipleObjectsEx = user32.NewProc("MsgWaitForMultipleObjectsEx")
|
||||
_OpenClipboard = user32.NewProc("OpenClipboard")
|
||||
_PeekMessage = user32.NewProc("PeekMessageW")
|
||||
_PostMessage = user32.NewProc("PostMessageW")
|
||||
_PostQuitMessage = user32.NewProc("PostQuitMessage")
|
||||
_ReleaseCapture = user32.NewProc("ReleaseCapture")
|
||||
_RegisterClassExW = user32.NewProc("RegisterClassExW")
|
||||
_RegisterTouchWindow = user32.NewProc("RegisterTouchWindow")
|
||||
_ReleaseDC = user32.NewProc("ReleaseDC")
|
||||
_ScreenToClient = user32.NewProc("ScreenToClient")
|
||||
_ShowWindow = user32.NewProc("ShowWindow")
|
||||
_SendMessage = user32.NewProc("SendMessageW")
|
||||
_SetCapture = user32.NewProc("SetCapture")
|
||||
_SetCursor = user32.NewProc("SetCursor")
|
||||
_SetClipboardData = user32.NewProc("SetClipboardData")
|
||||
_SetForegroundWindow = user32.NewProc("SetForegroundWindow")
|
||||
_SetFocus = user32.NewProc("SetFocus")
|
||||
_SetProcessDPIAware = user32.NewProc("SetProcessDPIAware")
|
||||
_SetTimer = user32.NewProc("SetTimer")
|
||||
_SetWindowLong = user32.NewProc("SetWindowLongPtrW")
|
||||
_SetWindowLong32 = user32.NewProc("SetWindowLongW")
|
||||
_SetWindowPlacement = user32.NewProc("SetWindowPlacement")
|
||||
_SetWindowPos = user32.NewProc("SetWindowPos")
|
||||
_SetWindowText = user32.NewProc("SetWindowTextW")
|
||||
_TranslateMessage = user32.NewProc("TranslateMessage")
|
||||
_UnregisterClass = user32.NewProc("UnregisterClassW")
|
||||
_UpdateWindow = user32.NewProc("UpdateWindow")
|
||||
|
||||
shcore = syscall.NewLazySystemDLL("shcore")
|
||||
_GetDpiForMonitor = shcore.NewProc("GetDpiForMonitor")
|
||||
|
||||
gdi32 = syscall.NewLazySystemDLL("gdi32")
|
||||
_GetDeviceCaps = gdi32.NewProc("GetDeviceCaps")
|
||||
|
||||
imm32 = syscall.NewLazySystemDLL("imm32")
|
||||
_ImmGetContext = imm32.NewProc("ImmGetContext")
|
||||
_ImmGetCompositionString = imm32.NewProc("ImmGetCompositionStringW")
|
||||
_ImmNotifyIME = imm32.NewProc("ImmNotifyIME")
|
||||
_ImmReleaseContext = imm32.NewProc("ImmReleaseContext")
|
||||
_ImmSetCandidateWindow = imm32.NewProc("ImmSetCandidateWindow")
|
||||
_ImmSetCompositionWindow = imm32.NewProc("ImmSetCompositionWindow")
|
||||
|
||||
dwmapi = syscall.NewLazySystemDLL("dwmapi")
|
||||
_DwmExtendFrameIntoClientArea = dwmapi.NewProc("DwmExtendFrameIntoClientArea")
|
||||
)
|
||||
|
||||
func AdjustWindowRectEx(r *Rect, dwStyle uint32, bMenu int, dwExStyle uint32) {
|
||||
_AdjustWindowRectEx.Call(uintptr(unsafe.Pointer(r)), uintptr(dwStyle), uintptr(bMenu), uintptr(dwExStyle))
|
||||
}
|
||||
|
||||
func CallMsgFilter(m *Msg, nCode uintptr) bool {
|
||||
r, _, _ := _CallMsgFilter.Call(uintptr(unsafe.Pointer(m)), nCode)
|
||||
return r != 0
|
||||
}
|
||||
|
||||
func CloseClipboard() error {
|
||||
r, _, err := _CloseClipboard.Call()
|
||||
if r == 0 {
|
||||
return fmt.Errorf("CloseClipboard: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, dwStyle uint32, x, y, w, h int32, hWndParent, hMenu, hInstance syscall.Handle, lpParam uintptr) (syscall.Handle, error) {
|
||||
wname, err := syscall.UTF16PtrFromString(lpWindowName)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("CreateWindowEx failed: %v", err)
|
||||
}
|
||||
hwnd, _, err := _CreateWindowEx.Call(
|
||||
uintptr(dwExStyle),
|
||||
uintptr(lpClassName),
|
||||
uintptr(unsafe.Pointer(wname)),
|
||||
uintptr(dwStyle),
|
||||
uintptr(x), uintptr(y),
|
||||
uintptr(w), uintptr(h),
|
||||
uintptr(hWndParent),
|
||||
uintptr(hMenu),
|
||||
uintptr(hInstance),
|
||||
uintptr(lpParam))
|
||||
if hwnd == 0 {
|
||||
return 0, fmt.Errorf("CreateWindowEx failed: %v", err)
|
||||
}
|
||||
return syscall.Handle(hwnd), nil
|
||||
}
|
||||
|
||||
func GetPointerInfo(pointerId uint32) (PointerInfo, error) {
|
||||
var info PointerInfo
|
||||
r1, _, err := _GetPointerInfo.Call(uintptr(pointerId), uintptr(unsafe.Pointer(&info)))
|
||||
if r1 == 0 {
|
||||
return PointerInfo{}, fmt.Errorf("GetPointerInfo failed: %v", err)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func RegisterTouchWindow(hwnd syscall.Handle, flags uint32) error {
|
||||
r1, _, err := _RegisterTouchWindow.Call(uintptr(hwnd), uintptr(flags))
|
||||
if r1 == 0 {
|
||||
return fmt.Errorf("RegisterTouchWindow failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnableMouseInPointer(enable uint) error {
|
||||
r1, _, err := _EnableMouseInPointer.Call(uintptr(enable))
|
||||
if r1 == 0 {
|
||||
return fmt.Errorf("EnableMouseInPointer failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DefWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
|
||||
r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
|
||||
return r
|
||||
}
|
||||
|
||||
func DestroyWindow(hwnd syscall.Handle) {
|
||||
_DestroyWindow.Call(uintptr(hwnd))
|
||||
}
|
||||
|
||||
func DispatchMessage(m *Msg) {
|
||||
_DispatchMessage.Call(uintptr(unsafe.Pointer(m)))
|
||||
}
|
||||
|
||||
func DwmExtendFrameIntoClientArea(hwnd syscall.Handle, margins Margins) error {
|
||||
r, _, _ := _DwmExtendFrameIntoClientArea.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&margins)))
|
||||
if r != 0 {
|
||||
return fmt.Errorf("DwmExtendFrameIntoClientArea: %#x", r)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func EmptyClipboard() error {
|
||||
r, _, err := _EmptyClipboard.Call()
|
||||
if r == 0 {
|
||||
return fmt.Errorf("EmptyClipboard: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FindWindow(lpClassName string) (syscall.Handle, error) {
|
||||
className, err := syscall.UTF16PtrFromString(lpClassName)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("FindWindow failed: %v", err)
|
||||
}
|
||||
hwnd, _, err := _FindWindow.Call(uintptr(unsafe.Pointer(className)), 0)
|
||||
if hwnd == 0 {
|
||||
return 0, fmt.Errorf("FindWindow failed: %v", err)
|
||||
}
|
||||
return syscall.Handle(hwnd), nil
|
||||
}
|
||||
|
||||
func GetWindowRect(hwnd syscall.Handle) Rect {
|
||||
var r Rect
|
||||
_GetWindowRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&r)))
|
||||
return r
|
||||
}
|
||||
|
||||
func GetClientRect(hwnd syscall.Handle) Rect {
|
||||
var r Rect
|
||||
_GetClientRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&r)))
|
||||
return r
|
||||
}
|
||||
|
||||
func GetClipboardData(format uint32) (syscall.Handle, error) {
|
||||
r, _, err := _GetClipboardData.Call(uintptr(format))
|
||||
if r == 0 {
|
||||
return 0, fmt.Errorf("GetClipboardData: %v", err)
|
||||
}
|
||||
return syscall.Handle(r), nil
|
||||
}
|
||||
|
||||
func GetDC(hwnd syscall.Handle) (syscall.Handle, error) {
|
||||
hdc, _, err := _GetDC.Call(uintptr(hwnd))
|
||||
if hdc == 0 {
|
||||
return 0, fmt.Errorf("GetDC failed: %v", err)
|
||||
}
|
||||
return syscall.Handle(hdc), nil
|
||||
}
|
||||
|
||||
func GetModuleHandle() (syscall.Handle, error) {
|
||||
h, _, err := _GetModuleHandleW.Call(uintptr(0))
|
||||
if h == 0 {
|
||||
return 0, fmt.Errorf("GetModuleHandleW failed: %v", err)
|
||||
}
|
||||
return syscall.Handle(h), nil
|
||||
}
|
||||
|
||||
func getDeviceCaps(hdc syscall.Handle, index int32) int {
|
||||
c, _, _ := _GetDeviceCaps.Call(uintptr(hdc), uintptr(index))
|
||||
return int(c)
|
||||
}
|
||||
|
||||
func getDpiForMonitor(hmonitor syscall.Handle, dpiType uint32) int {
|
||||
var dpiX, dpiY uintptr
|
||||
_GetDpiForMonitor.Call(uintptr(hmonitor), uintptr(dpiType), uintptr(unsafe.Pointer(&dpiX)), uintptr(unsafe.Pointer(&dpiY)))
|
||||
return int(dpiX)
|
||||
}
|
||||
|
||||
// GetSystemDPI returns the effective DPI of the system.
|
||||
func GetSystemDPI() int {
|
||||
// Check for GetDpiForMonitor, introduced in Windows 8.1.
|
||||
if _GetDpiForMonitor.Find() == nil {
|
||||
hmon := monitorFromPoint(Point{}, MONITOR_DEFAULTTOPRIMARY)
|
||||
return getDpiForMonitor(hmon, MDT_EFFECTIVE_DPI)
|
||||
} else {
|
||||
// Fall back to the physical device DPI.
|
||||
screenDC, err := GetDC(0)
|
||||
if err != nil {
|
||||
return 96
|
||||
}
|
||||
defer ReleaseDC(screenDC)
|
||||
return getDeviceCaps(screenDC, LOGPIXELSX)
|
||||
}
|
||||
}
|
||||
|
||||
func GetKeyState(nVirtKey int32) int16 {
|
||||
c, _, _ := _GetKeyState.Call(uintptr(nVirtKey))
|
||||
return int16(c)
|
||||
}
|
||||
|
||||
func GetMessage(m *Msg, hwnd syscall.Handle, wMsgFilterMin, wMsgFilterMax uint32) int32 {
|
||||
r, _, _ := _GetMessage.Call(uintptr(unsafe.Pointer(m)),
|
||||
uintptr(hwnd),
|
||||
uintptr(wMsgFilterMin),
|
||||
uintptr(wMsgFilterMax))
|
||||
return int32(r)
|
||||
}
|
||||
|
||||
func GetMessageTime() time.Duration {
|
||||
r, _, _ := _GetMessageTime.Call()
|
||||
return time.Duration(r) * time.Millisecond
|
||||
}
|
||||
|
||||
func GetSystemMetrics(nIndex int) int {
|
||||
r, _, _ := _GetSystemMetrics.Call(uintptr(nIndex))
|
||||
return int(r)
|
||||
}
|
||||
|
||||
// GetWindowDPI returns the effective DPI of the window.
|
||||
func GetWindowDPI(hwnd syscall.Handle) int {
|
||||
// Check for GetDpiForWindow, introduced in Windows 10.
|
||||
if _GetDpiForWindow.Find() == nil {
|
||||
dpi, _, _ := _GetDpiForWindow.Call(uintptr(hwnd))
|
||||
return int(dpi)
|
||||
} else {
|
||||
return GetSystemDPI()
|
||||
}
|
||||
}
|
||||
|
||||
func GetWindowPlacement(hwnd syscall.Handle) *WindowPlacement {
|
||||
var wp WindowPlacement
|
||||
wp.length = uint32(unsafe.Sizeof(wp))
|
||||
_GetWindowPlacement.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&wp)))
|
||||
return &wp
|
||||
}
|
||||
|
||||
func GetMonitorInfo(hwnd syscall.Handle) MonitorInfo {
|
||||
var mi MonitorInfo
|
||||
mi.cbSize = uint32(unsafe.Sizeof(mi))
|
||||
v, _, _ := _MonitorFromWindow.Call(uintptr(hwnd), MONITOR_DEFAULTTOPRIMARY)
|
||||
_GetMonitorInfo.Call(v, uintptr(unsafe.Pointer(&mi)))
|
||||
return mi
|
||||
}
|
||||
|
||||
func GetWindowLong(hwnd syscall.Handle, index uintptr) (val uintptr) {
|
||||
if runtime.GOARCH == "386" {
|
||||
val, _, _ = _GetWindowLong32.Call(uintptr(hwnd), index)
|
||||
} else {
|
||||
val, _, _ = _GetWindowLong.Call(uintptr(hwnd), index)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ImmGetContext(hwnd syscall.Handle) syscall.Handle {
|
||||
h, _, _ := _ImmGetContext.Call(uintptr(hwnd))
|
||||
return syscall.Handle(h)
|
||||
}
|
||||
|
||||
func ImmReleaseContext(hwnd, imc syscall.Handle) {
|
||||
_ImmReleaseContext.Call(uintptr(hwnd), uintptr(imc))
|
||||
}
|
||||
|
||||
func ImmNotifyIME(imc syscall.Handle, action, index, value int) {
|
||||
_ImmNotifyIME.Call(uintptr(imc), uintptr(action), uintptr(index), uintptr(value))
|
||||
}
|
||||
|
||||
func ImmGetCompositionString(imc syscall.Handle, key int) string {
|
||||
size, _, _ := _ImmGetCompositionString.Call(uintptr(imc), uintptr(key), 0, 0)
|
||||
if int32(size) <= 0 {
|
||||
return ""
|
||||
}
|
||||
u16 := make([]uint16, size/unsafe.Sizeof(uint16(0)))
|
||||
_ImmGetCompositionString.Call(uintptr(imc), uintptr(key), uintptr(unsafe.Pointer(&u16[0])), size)
|
||||
return string(utf16.Decode(u16))
|
||||
}
|
||||
|
||||
func ImmGetCompositionValue(imc syscall.Handle, key int) int {
|
||||
val, _, _ := _ImmGetCompositionString.Call(uintptr(imc), uintptr(key), 0, 0)
|
||||
return int(int32(val))
|
||||
}
|
||||
|
||||
func ImmSetCompositionWindow(imc syscall.Handle, x, y int) {
|
||||
f := CompositionForm{
|
||||
dwStyle: CFS_POINT,
|
||||
ptCurrentPos: Point{
|
||||
X: int32(x), Y: int32(y),
|
||||
},
|
||||
}
|
||||
_ImmSetCompositionWindow.Call(uintptr(imc), uintptr(unsafe.Pointer(&f)))
|
||||
}
|
||||
|
||||
func ImmSetCandidateWindow(imc syscall.Handle, x, y int) {
|
||||
f := CandidateForm{
|
||||
dwStyle: CFS_CANDIDATEPOS,
|
||||
ptCurrentPos: Point{
|
||||
X: int32(x), Y: int32(y),
|
||||
},
|
||||
}
|
||||
_ImmSetCandidateWindow.Call(uintptr(imc), uintptr(unsafe.Pointer(&f)))
|
||||
}
|
||||
|
||||
func SetWindowLong(hwnd syscall.Handle, idx uintptr, style uintptr) {
|
||||
if runtime.GOARCH == "386" {
|
||||
_SetWindowLong32.Call(uintptr(hwnd), idx, style)
|
||||
} else {
|
||||
_SetWindowLong.Call(uintptr(hwnd), idx, style)
|
||||
}
|
||||
}
|
||||
|
||||
func SetWindowPlacement(hwnd syscall.Handle, wp *WindowPlacement) {
|
||||
_SetWindowPlacement.Call(uintptr(hwnd), uintptr(unsafe.Pointer(wp)))
|
||||
}
|
||||
|
||||
func SetWindowPos(hwnd, hwndInsertAfter syscall.Handle, x, y, dx, dy int32, style uintptr) {
|
||||
_SetWindowPos.Call(uintptr(hwnd), uintptr(hwndInsertAfter),
|
||||
uintptr(x), uintptr(y),
|
||||
uintptr(dx), uintptr(dy),
|
||||
style,
|
||||
)
|
||||
}
|
||||
|
||||
func SetWindowText(hwnd syscall.Handle, title string) {
|
||||
wname, err := syscall.UTF16PtrFromString(title)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_SetWindowText.Call(uintptr(hwnd), uintptr(unsafe.Pointer(wname)))
|
||||
}
|
||||
|
||||
func GlobalAlloc(size int) (syscall.Handle, error) {
|
||||
r, _, err := _GlobalAlloc.Call(GHND, uintptr(size))
|
||||
if r == 0 {
|
||||
return 0, fmt.Errorf("GlobalAlloc: %v", err)
|
||||
}
|
||||
return syscall.Handle(r), nil
|
||||
}
|
||||
|
||||
func GlobalFree(h syscall.Handle) {
|
||||
_GlobalFree.Call(uintptr(h))
|
||||
}
|
||||
|
||||
func GlobalLock(h syscall.Handle) (unsafe.Pointer, error) {
|
||||
r, _, err := _GlobalLock.Call(uintptr(h))
|
||||
if r == 0 {
|
||||
return nil, fmt.Errorf("GlobalLock: %v", err)
|
||||
}
|
||||
return unsafe.Pointer(r), nil
|
||||
}
|
||||
|
||||
func GlobalUnlock(h syscall.Handle) {
|
||||
_GlobalUnlock.Call(uintptr(h))
|
||||
}
|
||||
|
||||
func KillTimer(hwnd syscall.Handle, nIDEvent uintptr) error {
|
||||
r, _, err := _SetTimer.Call(uintptr(hwnd), uintptr(nIDEvent), 0, 0)
|
||||
if r == 0 {
|
||||
return fmt.Errorf("KillTimer failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadCursor(curID uint16) (syscall.Handle, error) {
|
||||
h, _, err := _LoadCursor.Call(0, uintptr(curID))
|
||||
if h == 0 {
|
||||
return 0, fmt.Errorf("LoadCursorW failed: %v", err)
|
||||
}
|
||||
return syscall.Handle(h), nil
|
||||
}
|
||||
|
||||
func LoadImage(hInst syscall.Handle, res uint32, typ uint32, cx, cy int, fuload uint32) (syscall.Handle, error) {
|
||||
h, _, err := _LoadImage.Call(uintptr(hInst), uintptr(res), uintptr(typ), uintptr(cx), uintptr(cy), uintptr(fuload))
|
||||
if h == 0 {
|
||||
return 0, fmt.Errorf("LoadImageW failed: %v", err)
|
||||
}
|
||||
return syscall.Handle(h), nil
|
||||
}
|
||||
|
||||
func MoveWindow(hwnd syscall.Handle, x, y, width, height int32, repaint bool) {
|
||||
var paint uintptr
|
||||
if repaint {
|
||||
paint = TRUE
|
||||
}
|
||||
_MoveWindow.Call(uintptr(hwnd), uintptr(x), uintptr(y), uintptr(width), uintptr(height), paint)
|
||||
}
|
||||
|
||||
func monitorFromPoint(pt Point, flags uint32) syscall.Handle {
|
||||
r, _, _ := _MonitorFromPoint.Call(uintptr(pt.X), uintptr(pt.Y), uintptr(flags))
|
||||
return syscall.Handle(r)
|
||||
}
|
||||
|
||||
func MsgWaitForMultipleObjectsEx(nCount uint32, pHandles uintptr, millis, mask, flags uint32) (uint32, error) {
|
||||
r, _, err := _MsgWaitForMultipleObjectsEx.Call(uintptr(nCount), pHandles, uintptr(millis), uintptr(mask), uintptr(flags))
|
||||
res := uint32(r)
|
||||
if res == 0xFFFFFFFF {
|
||||
return 0, fmt.Errorf("MsgWaitForMultipleObjectsEx failed: %v", err)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func OpenClipboard(hwnd syscall.Handle) error {
|
||||
r, _, err := _OpenClipboard.Call(uintptr(hwnd))
|
||||
if r == 0 {
|
||||
return fmt.Errorf("OpenClipboard: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func PeekMessage(m *Msg, hwnd syscall.Handle, wMsgFilterMin, wMsgFilterMax, wRemoveMsg uint32) bool {
|
||||
r, _, _ := _PeekMessage.Call(uintptr(unsafe.Pointer(m)), uintptr(hwnd), uintptr(wMsgFilterMin), uintptr(wMsgFilterMax), uintptr(wRemoveMsg))
|
||||
return r != 0
|
||||
}
|
||||
|
||||
func PostQuitMessage(exitCode uintptr) {
|
||||
_PostQuitMessage.Call(exitCode)
|
||||
}
|
||||
|
||||
func PostMessage(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) error {
|
||||
r, _, err := _PostMessage.Call(uintptr(hwnd), uintptr(msg), wParam, lParam)
|
||||
if r == 0 {
|
||||
return fmt.Errorf("PostMessage failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReleaseCapture() bool {
|
||||
r, _, _ := _ReleaseCapture.Call()
|
||||
return r != 0
|
||||
}
|
||||
|
||||
func RegisterClassEx(cls *WndClassEx) (uint16, error) {
|
||||
a, _, err := _RegisterClassExW.Call(uintptr(unsafe.Pointer(cls)))
|
||||
if a == 0 {
|
||||
return 0, fmt.Errorf("RegisterClassExW failed: %v", err)
|
||||
}
|
||||
return uint16(a), nil
|
||||
}
|
||||
|
||||
func ReleaseDC(hdc syscall.Handle) {
|
||||
_ReleaseDC.Call(uintptr(hdc))
|
||||
}
|
||||
|
||||
func SendMessage(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) error {
|
||||
r, _, err := _SendMessage.Call(uintptr(hwnd), uintptr(msg), wParam, lParam)
|
||||
if r == 0 {
|
||||
return fmt.Errorf("SendMessage failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetForegroundWindow(hwnd syscall.Handle) {
|
||||
_SetForegroundWindow.Call(uintptr(hwnd))
|
||||
}
|
||||
|
||||
func SetFocus(hwnd syscall.Handle) {
|
||||
_SetFocus.Call(uintptr(hwnd))
|
||||
}
|
||||
|
||||
func SetProcessDPIAware() {
|
||||
_SetProcessDPIAware.Call()
|
||||
}
|
||||
|
||||
func SetCapture(hwnd syscall.Handle) syscall.Handle {
|
||||
r, _, _ := _SetCapture.Call(uintptr(hwnd))
|
||||
return syscall.Handle(r)
|
||||
}
|
||||
|
||||
func SetClipboardData(format uint32, mem syscall.Handle) error {
|
||||
r, _, err := _SetClipboardData.Call(uintptr(format), uintptr(mem))
|
||||
if r == 0 {
|
||||
return fmt.Errorf("SetClipboardData: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetCursor(h syscall.Handle) {
|
||||
_SetCursor.Call(uintptr(h))
|
||||
}
|
||||
|
||||
func SetTimer(hwnd syscall.Handle, nIDEvent uintptr, uElapse uint32, timerProc uintptr) error {
|
||||
r, _, err := _SetTimer.Call(uintptr(hwnd), uintptr(nIDEvent), uintptr(uElapse), timerProc)
|
||||
if r == 0 {
|
||||
return fmt.Errorf("SetTimer failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ScreenToClient(hwnd syscall.Handle, p *Point) {
|
||||
_ScreenToClient.Call(uintptr(hwnd), uintptr(unsafe.Pointer(p)))
|
||||
}
|
||||
|
||||
func ShowWindow(hwnd syscall.Handle, nCmdShow int32) {
|
||||
_ShowWindow.Call(uintptr(hwnd), uintptr(nCmdShow))
|
||||
}
|
||||
|
||||
func TranslateMessage(m *Msg) {
|
||||
_TranslateMessage.Call(uintptr(unsafe.Pointer(m)))
|
||||
}
|
||||
|
||||
func UnregisterClass(cls uint16, hInst syscall.Handle) {
|
||||
_UnregisterClass.Call(uintptr(cls), uintptr(hInst))
|
||||
}
|
||||
|
||||
func UpdateWindow(hwnd syscall.Handle) {
|
||||
_UpdateWindow.Call(uintptr(hwnd))
|
||||
}
|
||||
|
||||
func (p WindowPlacement) Rect() Rect {
|
||||
return p.rcNormalPosition
|
||||
}
|
||||
|
||||
func (p WindowPlacement) IsMinimized() bool {
|
||||
return p.showCmd == SW_SHOWMINIMIZED
|
||||
}
|
||||
|
||||
func (p WindowPlacement) IsMaximized() bool {
|
||||
return p.showCmd == SW_SHOWMAXIMIZED
|
||||
}
|
||||
|
||||
func (p *WindowPlacement) Set(Left, Top, Right, Bottom int) {
|
||||
p.rcNormalPosition.Left = int32(Left)
|
||||
p.rcNormalPosition.Top = int32(Top)
|
||||
p.rcNormalPosition.Right = int32(Right)
|
||||
p.rcNormalPosition.Bottom = int32(Bottom)
|
||||
}
|
||||
+375
@@ -0,0 +1,375 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build (linux && !android) || freebsd || openbsd
|
||||
// +build linux,!android freebsd openbsd
|
||||
|
||||
// Package xkb implements a Go interface for the X Keyboard Extension library.
|
||||
package xkb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: xkbcommon
|
||||
#cgo freebsd openbsd CFLAGS: -I/usr/local/include
|
||||
#cgo freebsd openbsd LDFLAGS: -L/usr/local/lib -lxkbcommon
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <xkbcommon/xkbcommon-compose.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type Context struct {
|
||||
Ctx *C.struct_xkb_context
|
||||
keyMap *C.struct_xkb_keymap
|
||||
state *C.struct_xkb_state
|
||||
compTable *C.struct_xkb_compose_table
|
||||
compState *C.struct_xkb_compose_state
|
||||
utf8Buf []byte
|
||||
}
|
||||
|
||||
var (
|
||||
_XKB_MOD_NAME_CTRL = []byte("Control\x00")
|
||||
_XKB_MOD_NAME_SHIFT = []byte("Shift\x00")
|
||||
_XKB_MOD_NAME_ALT = []byte("Mod1\x00")
|
||||
_XKB_MOD_NAME_LOGO = []byte("Mod4\x00")
|
||||
)
|
||||
|
||||
func (x *Context) Destroy() {
|
||||
if x.compState != nil {
|
||||
C.xkb_compose_state_unref(x.compState)
|
||||
x.compState = nil
|
||||
}
|
||||
if x.compTable != nil {
|
||||
C.xkb_compose_table_unref(x.compTable)
|
||||
x.compTable = nil
|
||||
}
|
||||
x.DestroyKeymapState()
|
||||
if x.Ctx != nil {
|
||||
C.xkb_context_unref(x.Ctx)
|
||||
x.Ctx = nil
|
||||
}
|
||||
}
|
||||
|
||||
func New() (*Context, error) {
|
||||
ctx := &Context{
|
||||
Ctx: C.xkb_context_new(C.XKB_CONTEXT_NO_FLAGS),
|
||||
}
|
||||
if ctx.Ctx == nil {
|
||||
return nil, errors.New("newXKB: xkb_context_new failed")
|
||||
}
|
||||
locale := os.Getenv("LC_ALL")
|
||||
if locale == "" {
|
||||
locale = os.Getenv("LC_CTYPE")
|
||||
}
|
||||
if locale == "" {
|
||||
locale = os.Getenv("LANG")
|
||||
}
|
||||
if locale == "" {
|
||||
locale = "C"
|
||||
}
|
||||
cloc := C.CString(locale)
|
||||
defer C.free(unsafe.Pointer(cloc))
|
||||
ctx.compTable = C.xkb_compose_table_new_from_locale(ctx.Ctx, cloc, C.XKB_COMPOSE_COMPILE_NO_FLAGS)
|
||||
if ctx.compTable == nil {
|
||||
ctx.Destroy()
|
||||
return nil, errors.New("newXKB: xkb_compose_table_new_from_locale failed")
|
||||
}
|
||||
ctx.compState = C.xkb_compose_state_new(ctx.compTable, C.XKB_COMPOSE_STATE_NO_FLAGS)
|
||||
if ctx.compState == nil {
|
||||
ctx.Destroy()
|
||||
return nil, errors.New("newXKB: xkb_compose_state_new failed")
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (x *Context) DestroyKeymapState() {
|
||||
if x.state != nil {
|
||||
C.xkb_state_unref(x.state)
|
||||
x.state = nil
|
||||
}
|
||||
if x.keyMap != nil {
|
||||
C.xkb_keymap_unref(x.keyMap)
|
||||
x.keyMap = nil
|
||||
}
|
||||
}
|
||||
|
||||
// SetKeymap sets the keymap and state. The context takes ownership of the
|
||||
// keymap and state and frees them in Destroy.
|
||||
func (x *Context) SetKeymap(xkbKeyMap, xkbState unsafe.Pointer) {
|
||||
x.DestroyKeymapState()
|
||||
x.keyMap = (*C.struct_xkb_keymap)(xkbKeyMap)
|
||||
x.state = (*C.struct_xkb_state)(xkbState)
|
||||
}
|
||||
|
||||
func (x *Context) LoadKeymap(format int, fd int, size int) error {
|
||||
x.DestroyKeymapState()
|
||||
mapData, err := syscall.Mmap(int(fd), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
|
||||
if err != nil {
|
||||
return fmt.Errorf("newXKB: mmap of keymap failed: %v", err)
|
||||
}
|
||||
defer syscall.Munmap(mapData)
|
||||
keyMap := C.xkb_keymap_new_from_buffer(x.Ctx, (*C.char)(unsafe.Pointer(&mapData[0])), C.size_t(size-1), C.XKB_KEYMAP_FORMAT_TEXT_V1, C.XKB_KEYMAP_COMPILE_NO_FLAGS)
|
||||
if keyMap == nil {
|
||||
return errors.New("newXKB: xkb_keymap_new_from_buffer failed")
|
||||
}
|
||||
state := C.xkb_state_new(keyMap)
|
||||
if state == nil {
|
||||
C.xkb_keymap_unref(keyMap)
|
||||
return errors.New("newXKB: xkb_state_new failed")
|
||||
}
|
||||
x.keyMap = keyMap
|
||||
x.state = state
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Context) Modifiers() key.Modifiers {
|
||||
var mods key.Modifiers
|
||||
if x.state == nil {
|
||||
return mods
|
||||
}
|
||||
|
||||
if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_CTRL[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
|
||||
mods |= key.ModCtrl
|
||||
}
|
||||
if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_SHIFT[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
|
||||
mods |= key.ModShift
|
||||
}
|
||||
if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_ALT[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
|
||||
mods |= key.ModAlt
|
||||
}
|
||||
if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_LOGO[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
|
||||
mods |= key.ModSuper
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
func (x *Context) DispatchKey(keyCode uint32, state key.State) (events []event.Event) {
|
||||
if x.state == nil {
|
||||
return
|
||||
}
|
||||
kc := C.xkb_keycode_t(keyCode)
|
||||
if len(x.utf8Buf) == 0 {
|
||||
x.utf8Buf = make([]byte, 1)
|
||||
}
|
||||
sym := C.xkb_state_key_get_one_sym(x.state, kc)
|
||||
if name, ok := convertKeysym(sym); ok {
|
||||
cmd := key.Event{
|
||||
Name: name,
|
||||
Modifiers: x.Modifiers(),
|
||||
State: state,
|
||||
}
|
||||
// Ensure that a physical backtab key is translated to
|
||||
// Shift-Tab.
|
||||
if sym == C.XKB_KEY_ISO_Left_Tab {
|
||||
cmd.Modifiers |= key.ModShift
|
||||
}
|
||||
events = append(events, cmd)
|
||||
}
|
||||
C.xkb_compose_state_feed(x.compState, sym)
|
||||
var str []byte
|
||||
switch C.xkb_compose_state_get_status(x.compState) {
|
||||
case C.XKB_COMPOSE_CANCELLED, C.XKB_COMPOSE_COMPOSING:
|
||||
return
|
||||
case C.XKB_COMPOSE_COMPOSED:
|
||||
size := C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
|
||||
if int(size) >= len(x.utf8Buf) {
|
||||
x.utf8Buf = make([]byte, size+1)
|
||||
size = C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
|
||||
}
|
||||
C.xkb_compose_state_reset(x.compState)
|
||||
str = x.utf8Buf[:size]
|
||||
case C.XKB_COMPOSE_NOTHING:
|
||||
mod := x.Modifiers()
|
||||
if mod&(key.ModCtrl|key.ModAlt|key.ModSuper) == 0 {
|
||||
str = x.charsForKeycode(kc)
|
||||
}
|
||||
}
|
||||
// Report only printable runes.
|
||||
var n int
|
||||
for n < len(str) {
|
||||
r, s := utf8.DecodeRune(str)
|
||||
if unicode.IsPrint(r) {
|
||||
n += s
|
||||
} else {
|
||||
copy(str[n:], str[n+s:])
|
||||
str = str[:len(str)-s]
|
||||
}
|
||||
}
|
||||
if state == key.Press && len(str) > 0 {
|
||||
events = append(events, key.EditEvent{Text: string(str)})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (x *Context) charsForKeycode(keyCode C.xkb_keycode_t) []byte {
|
||||
size := C.xkb_state_key_get_utf8(x.state, keyCode, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
|
||||
if int(size) >= len(x.utf8Buf) {
|
||||
x.utf8Buf = make([]byte, size+1)
|
||||
size = C.xkb_state_key_get_utf8(x.state, keyCode, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
|
||||
}
|
||||
return x.utf8Buf[:size]
|
||||
}
|
||||
|
||||
func (x *Context) IsRepeatKey(keyCode uint32) bool {
|
||||
if x.state == nil {
|
||||
return false
|
||||
}
|
||||
kc := C.xkb_keycode_t(keyCode)
|
||||
return C.xkb_keymap_key_repeats(x.keyMap, kc) == 1
|
||||
}
|
||||
|
||||
func (x *Context) UpdateMask(depressed, latched, locked, depressedGroup, latchedGroup, lockedGroup uint32) {
|
||||
if x.state == nil {
|
||||
return
|
||||
}
|
||||
C.xkb_state_update_mask(x.state, C.xkb_mod_mask_t(depressed), C.xkb_mod_mask_t(latched), C.xkb_mod_mask_t(locked),
|
||||
C.xkb_layout_index_t(depressedGroup), C.xkb_layout_index_t(latchedGroup), C.xkb_layout_index_t(lockedGroup))
|
||||
}
|
||||
|
||||
func convertKeysym(s C.xkb_keysym_t) (key.Name, bool) {
|
||||
if 'a' <= s && s <= 'z' {
|
||||
return key.Name(rune(s - 'a' + 'A')), true
|
||||
}
|
||||
if C.XKB_KEY_KP_0 <= s && s <= C.XKB_KEY_KP_9 {
|
||||
return key.Name(rune(s - C.XKB_KEY_KP_0 + '0')), true
|
||||
}
|
||||
if ' ' < s && s <= '~' {
|
||||
return key.Name(rune(s)), true
|
||||
}
|
||||
var n key.Name
|
||||
switch s {
|
||||
case C.XKB_KEY_Escape:
|
||||
n = key.NameEscape
|
||||
case C.XKB_KEY_Left:
|
||||
n = key.NameLeftArrow
|
||||
case C.XKB_KEY_Right:
|
||||
n = key.NameRightArrow
|
||||
case C.XKB_KEY_Return:
|
||||
n = key.NameReturn
|
||||
case C.XKB_KEY_Up:
|
||||
n = key.NameUpArrow
|
||||
case C.XKB_KEY_Down:
|
||||
n = key.NameDownArrow
|
||||
case C.XKB_KEY_Home:
|
||||
n = key.NameHome
|
||||
case C.XKB_KEY_End:
|
||||
n = key.NameEnd
|
||||
case C.XKB_KEY_BackSpace:
|
||||
n = key.NameDeleteBackward
|
||||
case C.XKB_KEY_Delete:
|
||||
n = key.NameDeleteForward
|
||||
case C.XKB_KEY_Page_Up:
|
||||
n = key.NamePageUp
|
||||
case C.XKB_KEY_Page_Down:
|
||||
n = key.NamePageDown
|
||||
case C.XKB_KEY_F1:
|
||||
n = key.NameF1
|
||||
case C.XKB_KEY_F2:
|
||||
n = key.NameF2
|
||||
case C.XKB_KEY_F3:
|
||||
n = key.NameF3
|
||||
case C.XKB_KEY_F4:
|
||||
n = key.NameF4
|
||||
case C.XKB_KEY_F5:
|
||||
n = key.NameF5
|
||||
case C.XKB_KEY_F6:
|
||||
n = key.NameF6
|
||||
case C.XKB_KEY_F7:
|
||||
n = key.NameF7
|
||||
case C.XKB_KEY_F8:
|
||||
n = key.NameF8
|
||||
case C.XKB_KEY_F9:
|
||||
n = key.NameF9
|
||||
case C.XKB_KEY_F10:
|
||||
n = key.NameF10
|
||||
case C.XKB_KEY_F11:
|
||||
n = key.NameF11
|
||||
case C.XKB_KEY_F12:
|
||||
n = key.NameF12
|
||||
case C.XKB_KEY_Tab, C.XKB_KEY_ISO_Left_Tab:
|
||||
n = key.NameTab
|
||||
case 0x20:
|
||||
n = key.NameSpace
|
||||
case C.XKB_KEY_Control_L, C.XKB_KEY_Control_R:
|
||||
n = key.NameCtrl
|
||||
case C.XKB_KEY_Shift_L, C.XKB_KEY_Shift_R:
|
||||
n = key.NameShift
|
||||
case C.XKB_KEY_Alt_L, C.XKB_KEY_Alt_R:
|
||||
n = key.NameAlt
|
||||
case C.XKB_KEY_Super_L, C.XKB_KEY_Super_R:
|
||||
n = key.NameSuper
|
||||
|
||||
case C.XKB_KEY_KP_Space:
|
||||
n = key.NameSpace
|
||||
case C.XKB_KEY_KP_Tab:
|
||||
n = key.NameTab
|
||||
case C.XKB_KEY_KP_Enter:
|
||||
n = key.NameEnter
|
||||
case C.XKB_KEY_KP_F1:
|
||||
n = key.NameF1
|
||||
case C.XKB_KEY_KP_F2:
|
||||
n = key.NameF2
|
||||
case C.XKB_KEY_KP_F3:
|
||||
n = key.NameF3
|
||||
case C.XKB_KEY_KP_F4:
|
||||
n = key.NameF4
|
||||
case C.XKB_KEY_KP_Home:
|
||||
n = key.NameHome
|
||||
case C.XKB_KEY_KP_Left:
|
||||
n = key.NameLeftArrow
|
||||
case C.XKB_KEY_KP_Up:
|
||||
n = key.NameUpArrow
|
||||
case C.XKB_KEY_KP_Right:
|
||||
n = key.NameRightArrow
|
||||
case C.XKB_KEY_KP_Down:
|
||||
n = key.NameDownArrow
|
||||
case C.XKB_KEY_KP_Prior:
|
||||
// not supported
|
||||
return "", false
|
||||
case C.XKB_KEY_KP_Next:
|
||||
// not supported
|
||||
return "", false
|
||||
case C.XKB_KEY_KP_End:
|
||||
n = key.NameEnd
|
||||
case C.XKB_KEY_KP_Begin:
|
||||
n = key.NameHome
|
||||
case C.XKB_KEY_KP_Insert:
|
||||
// not supported
|
||||
return "", false
|
||||
case C.XKB_KEY_KP_Delete:
|
||||
n = key.NameDeleteForward
|
||||
case C.XKB_KEY_KP_Multiply:
|
||||
n = "*"
|
||||
case C.XKB_KEY_KP_Add:
|
||||
n = "+"
|
||||
case C.XKB_KEY_KP_Separator:
|
||||
// not supported
|
||||
return "", false
|
||||
case C.XKB_KEY_KP_Subtract:
|
||||
n = "-"
|
||||
case C.XKB_KEY_KP_Decimal:
|
||||
// TODO(dh): does a German keyboard layout also translate the numpad key to XKB_KEY_KP_DECIMAL? Because in
|
||||
// German, the decimal is a comma, not a period.
|
||||
n = "."
|
||||
case C.XKB_KEY_KP_Divide:
|
||||
n = "/"
|
||||
case C.XKB_KEY_KP_Equal:
|
||||
n = "="
|
||||
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -llog
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <android/log.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// 1024 is the truncation limit from android/log.h, plus a \n.
|
||||
const logLineLimit = 1024
|
||||
|
||||
var logTag = C.CString(ID)
|
||||
|
||||
func init() {
|
||||
// Android's logcat already includes timestamps.
|
||||
log.SetFlags(log.Flags() &^ log.LstdFlags)
|
||||
log.SetOutput(new(androidLogWriter))
|
||||
|
||||
// Redirect stdout and stderr to the Android logger.
|
||||
logFd(os.Stdout.Fd())
|
||||
logFd(os.Stderr.Fd())
|
||||
}
|
||||
|
||||
type androidLogWriter struct {
|
||||
// buf has room for the maximum log line, plus a terminating '\0'.
|
||||
buf [logLineLimit + 1]byte
|
||||
}
|
||||
|
||||
func (w *androidLogWriter) Write(data []byte) (int, error) {
|
||||
n := 0
|
||||
for len(data) > 0 {
|
||||
msg := data
|
||||
// Truncate the buffer, leaving space for the '\0'.
|
||||
if max := len(w.buf) - 1; len(msg) > max {
|
||||
msg = msg[:max]
|
||||
}
|
||||
buf := w.buf[:len(msg)+1]
|
||||
copy(buf, msg)
|
||||
// Terminating '\0'.
|
||||
buf[len(msg)] = 0
|
||||
C.__android_log_write(C.ANDROID_LOG_INFO, logTag, (*C.char)(unsafe.Pointer(&buf[0])))
|
||||
n += len(msg)
|
||||
data = data[len(msg):]
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func logFd(fd uintptr) {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := syscall.Dup3(int(w.Fd()), int(fd), syscall.O_CLOEXEC); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go func() {
|
||||
lineBuf := bufio.NewReaderSize(r, logLineLimit)
|
||||
// The buffer to pass to C, including the terminating '\0'.
|
||||
buf := make([]byte, lineBuf.Size()+1)
|
||||
cbuf := (*C.char)(unsafe.Pointer(&buf[0]))
|
||||
for {
|
||||
line, _, err := lineBuf.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
copy(buf, line)
|
||||
buf[len(line)] = 0
|
||||
C.__android_log_write(C.ANDROID_LOG_INFO, logTag, cbuf)
|
||||
}
|
||||
// The garbage collector doesn't know that w's fd was dup'ed.
|
||||
// Avoid finalizing w, and thereby avoid its finalizer closing its fd.
|
||||
runtime.KeepAlive(w)
|
||||
}()
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && ios
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
|
||||
|
||||
@import Foundation;
|
||||
|
||||
static void nslog(char *str) {
|
||||
NSLog(@"%@", @(str));
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"log"
|
||||
"unsafe"
|
||||
|
||||
_ "gioui.org/internal/cocoainit"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// macOS Console already includes timestamps.
|
||||
log.SetFlags(log.Flags() &^ log.LstdFlags)
|
||||
log.SetOutput(newNSLogWriter())
|
||||
}
|
||||
|
||||
func newNSLogWriter() io.Writer {
|
||||
r, w := io.Pipe()
|
||||
go func() {
|
||||
// 1024 is an arbitrary truncation limit, taken from Android's
|
||||
// log buffer size.
|
||||
lineBuf := bufio.NewReaderSize(r, 1024)
|
||||
// The buffer to pass to C, including the terminating '\0'.
|
||||
buf := make([]byte, lineBuf.Size()+1)
|
||||
cbuf := (*C.char)(unsafe.Pointer(&buf[0]))
|
||||
for {
|
||||
line, _, err := lineBuf.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
copy(buf, line)
|
||||
buf[len(line)] = 0
|
||||
C.nslog(cbuf)
|
||||
}
|
||||
}()
|
||||
return w
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"log"
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type logger struct{}
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazySystemDLL("kernel32")
|
||||
outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
|
||||
debugView *logger
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Windows DebugView already includes timestamps.
|
||||
if syscall.Stderr == 0 {
|
||||
log.SetFlags(log.Flags() &^ log.LstdFlags)
|
||||
log.SetOutput(debugView)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *logger) Write(buf []byte) (int, error) {
|
||||
p, err := syscall.UTF16PtrFromString(string(buf))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
outputDebugStringW.Call(uintptr(unsafe.Pointer(p)))
|
||||
return len(buf), nil
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !nometal
|
||||
// +build !nometal
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"gioui.org/gpu"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -xobjective-c -fobjc-arc
|
||||
#cgo LDFLAGS: -framework QuartzCore -framework Metal
|
||||
|
||||
#import <Metal/Metal.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
static CFTypeRef createMetalDevice(void) {
|
||||
@autoreleasepool {
|
||||
id<MTLDevice> dev = MTLCreateSystemDefaultDevice();
|
||||
return CFBridgingRetain(dev);
|
||||
}
|
||||
}
|
||||
|
||||
static void setupLayer(CFTypeRef layerRef, CFTypeRef devRef) {
|
||||
@autoreleasepool {
|
||||
CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
|
||||
id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
|
||||
layer.device = dev;
|
||||
// Package gpu assumes an sRGB-encoded framebuffer.
|
||||
layer.pixelFormat = MTLPixelFormatBGRA8Unorm_sRGB;
|
||||
if (@available(iOS 11.0, *)) {
|
||||
// Never let nextDrawable time out and return nil.
|
||||
layer.allowsNextDrawableTimeout = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef nextDrawable(CFTypeRef layerRef) {
|
||||
@autoreleasepool {
|
||||
CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
|
||||
return CFBridgingRetain([layer nextDrawable]);
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef drawableTexture(CFTypeRef drawableRef) {
|
||||
@autoreleasepool {
|
||||
id<CAMetalDrawable> drawable = (__bridge id<CAMetalDrawable>)drawableRef;
|
||||
return CFBridgingRetain(drawable.texture);
|
||||
}
|
||||
}
|
||||
|
||||
static void presentDrawable(CFTypeRef queueRef, CFTypeRef drawableRef) {
|
||||
@autoreleasepool {
|
||||
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
|
||||
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
|
||||
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
|
||||
[cmdBuffer commit];
|
||||
[cmdBuffer waitUntilScheduled];
|
||||
[drawable present];
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef newCommandQueue(CFTypeRef devRef) {
|
||||
@autoreleasepool {
|
||||
id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
|
||||
return CFBridgingRetain([dev newCommandQueue]);
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type mtlContext struct {
|
||||
dev C.CFTypeRef
|
||||
view C.CFTypeRef
|
||||
layer C.CFTypeRef
|
||||
queue C.CFTypeRef
|
||||
drawable C.CFTypeRef
|
||||
texture C.CFTypeRef
|
||||
}
|
||||
|
||||
func newMtlContext(w *window) (*mtlContext, error) {
|
||||
dev := C.createMetalDevice()
|
||||
if dev == 0 {
|
||||
return nil, errors.New("metal: MTLCreateSystemDefaultDevice failed")
|
||||
}
|
||||
view := w.contextView()
|
||||
layer := getMetalLayer(view)
|
||||
if layer == 0 {
|
||||
C.CFRelease(dev)
|
||||
return nil, errors.New("metal: CAMetalLayer construction failed")
|
||||
}
|
||||
queue := C.newCommandQueue(dev)
|
||||
if queue == 0 {
|
||||
C.CFRelease(dev)
|
||||
C.CFRelease(layer)
|
||||
return nil, errors.New("metal: [MTLDevice newCommandQueue] failed")
|
||||
}
|
||||
C.setupLayer(layer, dev)
|
||||
c := &mtlContext{
|
||||
dev: dev,
|
||||
view: view,
|
||||
layer: layer,
|
||||
queue: queue,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *mtlContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
if c.drawable != 0 || c.texture != 0 {
|
||||
return nil, errors.New("metal:a previous RenderTarget wasn't Presented")
|
||||
}
|
||||
c.drawable = C.nextDrawable(c.layer)
|
||||
if c.drawable == 0 {
|
||||
return nil, errors.New("metal: [CAMetalLayer nextDrawable] failed")
|
||||
}
|
||||
c.texture = C.drawableTexture(c.drawable)
|
||||
if c.texture == 0 {
|
||||
return nil, errors.New("metal: CADrawable.texture is nil")
|
||||
}
|
||||
return gpu.MetalRenderTarget{
|
||||
Texture: uintptr(c.texture),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *mtlContext) API() gpu.API {
|
||||
return gpu.Metal{
|
||||
Device: uintptr(c.dev),
|
||||
Queue: uintptr(c.queue),
|
||||
PixelFormat: int(C.MTLPixelFormatBGRA8Unorm_sRGB),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *mtlContext) Release() {
|
||||
C.CFRelease(c.queue)
|
||||
C.CFRelease(c.dev)
|
||||
C.CFRelease(c.layer)
|
||||
if c.drawable != 0 {
|
||||
C.CFRelease(c.drawable)
|
||||
}
|
||||
if c.texture != 0 {
|
||||
C.CFRelease(c.texture)
|
||||
}
|
||||
*c = mtlContext{}
|
||||
}
|
||||
|
||||
func (c *mtlContext) Present() error {
|
||||
C.CFRelease(c.texture)
|
||||
c.texture = 0
|
||||
C.presentDrawable(c.queue, c.drawable)
|
||||
C.CFRelease(c.drawable)
|
||||
c.drawable = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mtlContext) Lock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mtlContext) Unlock() {}
|
||||
|
||||
func (c *mtlContext) Refresh() error {
|
||||
resizeDrawable(c.view, c.layer)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (context, error) {
|
||||
return newMtlContext(w)
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !nometal
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -xobjective-c -fmodules -fobjc-arc
|
||||
|
||||
@import UIKit;
|
||||
|
||||
@import QuartzCore.CAMetalLayer;
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
Class gio_layerClass(void) {
|
||||
return [CAMetalLayer class];
|
||||
}
|
||||
|
||||
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
||||
@autoreleasepool {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
CAMetalLayer *l = (CAMetalLayer *)view.layer;
|
||||
l.needsDisplayOnBoundsChange = YES;
|
||||
l.presentsWithTransaction = YES;
|
||||
return CFBridgingRetain(l);
|
||||
}
|
||||
}
|
||||
|
||||
static void resizeDrawable(CFTypeRef viewRef, CFTypeRef layerRef) {
|
||||
@autoreleasepool {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
|
||||
layer.contentsScale = view.contentScaleFactor;
|
||||
CGSize size = layer.bounds.size;
|
||||
size.width *= layer.contentsScale;
|
||||
size.height *= layer.contentsScale;
|
||||
layer.drawableSize = size;
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func getMetalLayer(view C.CFTypeRef) C.CFTypeRef {
|
||||
return C.getMetalLayer(view)
|
||||
}
|
||||
|
||||
func resizeDrawable(view, layer C.CFTypeRef) {
|
||||
C.resizeDrawable(view, layer)
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && !ios && !nometal
|
||||
// +build darwin,!ios,!nometal
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -xobjective-c -fobjc-arc
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
CALayer *gio_layerFactory(BOOL presentWithTrans) {
|
||||
@autoreleasepool {
|
||||
CAMetalLayer *l = [CAMetalLayer layer];
|
||||
l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
|
||||
l.needsDisplayOnBoundsChange = YES;
|
||||
l.presentsWithTransaction = presentWithTrans;
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return CFBridgingRetain(view.layer);
|
||||
}
|
||||
}
|
||||
|
||||
static void resizeDrawable(CFTypeRef viewRef, CFTypeRef layerRef) {
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
|
||||
CGSize size = layer.bounds.size;
|
||||
size.width *= layer.contentsScale;
|
||||
size.height *= layer.contentsScale;
|
||||
layer.drawableSize = size;
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func getMetalLayer(view C.CFTypeRef) C.CFTypeRef {
|
||||
return C.getMetalLayer(view)
|
||||
}
|
||||
|
||||
func resizeDrawable(view, layer C.CFTypeRef) {
|
||||
C.resizeDrawable(view, layer)
|
||||
}
|
||||
+367
@@ -0,0 +1,367 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/op"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
// errOutOfDate is reported when the GPU surface dimensions or properties no
|
||||
// longer match the window.
|
||||
var errOutOfDate = errors.New("app: GPU surface out of date")
|
||||
|
||||
// Config describes a Window configuration.
|
||||
type Config struct {
|
||||
// Size is the window dimensions (Width, Height).
|
||||
Size image.Point
|
||||
// MaxSize is the window maximum allowed dimensions.
|
||||
MaxSize image.Point
|
||||
// MinSize is the window minimum allowed dimensions.
|
||||
MinSize image.Point
|
||||
// Title is the window title displayed in its decoration bar.
|
||||
Title string
|
||||
// WindowMode is the window mode.
|
||||
Mode WindowMode
|
||||
// StatusColor is the color of the Android status bar.
|
||||
StatusColor color.NRGBA
|
||||
// NavigationColor is the color of the navigation bar
|
||||
// on Android, or the address bar in browsers.
|
||||
NavigationColor color.NRGBA
|
||||
// Orientation is the current window orientation.
|
||||
Orientation Orientation
|
||||
// CustomRenderer is true when the window content is rendered by the
|
||||
// client.
|
||||
CustomRenderer bool
|
||||
// Decorated reports whether window decorations are provided automatically.
|
||||
Decorated bool
|
||||
// TopMost windows render above all other non-top-most windows.
|
||||
TopMost bool
|
||||
// Focused reports whether the window is focused.
|
||||
Focused bool
|
||||
// decoHeight is the height of the fallback decoration for platforms such
|
||||
// as Wayland that may need fallback client-side decorations.
|
||||
decoHeight unit.Dp
|
||||
}
|
||||
|
||||
// ConfigEvent is sent whenever the configuration of a Window changes.
|
||||
type ConfigEvent struct {
|
||||
Config Config
|
||||
}
|
||||
|
||||
func (c *Config) apply(m unit.Metric, options []Option) {
|
||||
for _, o := range options {
|
||||
o(m, c)
|
||||
}
|
||||
}
|
||||
|
||||
type wakeupEvent struct{}
|
||||
|
||||
// WindowMode is the window mode (WindowMode.Option sets it).
|
||||
// Note that mode can be changed programatically as well as by the user
|
||||
// clicking on the minimize/maximize buttons on the window's title bar.
|
||||
type WindowMode uint8
|
||||
|
||||
const (
|
||||
// Windowed is the normal window mode with OS specific window decorations.
|
||||
Windowed WindowMode = iota
|
||||
// Fullscreen is the full screen window mode.
|
||||
Fullscreen
|
||||
// Minimized is for systems where the window can be minimized to an icon.
|
||||
Minimized
|
||||
// Maximized is for systems where the window can be made to fill the available monitor area.
|
||||
Maximized
|
||||
)
|
||||
|
||||
// Option changes the mode of a Window.
|
||||
func (m WindowMode) Option() Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.Mode = m
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the mode name.
|
||||
func (m WindowMode) String() string {
|
||||
switch m {
|
||||
case Windowed:
|
||||
return "windowed"
|
||||
case Fullscreen:
|
||||
return "fullscreen"
|
||||
case Minimized:
|
||||
return "minimized"
|
||||
case Maximized:
|
||||
return "maximized"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Orientation is the orientation of the app (Orientation.Option sets it).
|
||||
//
|
||||
// Supported platforms are Android and JS.
|
||||
type Orientation uint8
|
||||
|
||||
const (
|
||||
// AnyOrientation allows the window to be freely orientated.
|
||||
AnyOrientation Orientation = iota
|
||||
// LandscapeOrientation constrains the window to landscape orientations.
|
||||
LandscapeOrientation
|
||||
// PortraitOrientation constrains the window to portrait orientations.
|
||||
PortraitOrientation
|
||||
)
|
||||
|
||||
func (o Orientation) Option() Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.Orientation = o
|
||||
}
|
||||
}
|
||||
|
||||
func (o Orientation) String() string {
|
||||
switch o {
|
||||
case AnyOrientation:
|
||||
return "any"
|
||||
case LandscapeOrientation:
|
||||
return "landscape"
|
||||
case PortraitOrientation:
|
||||
return "portrait"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// eventLoop implements the functionality required for drivers where
|
||||
// window event loops must run on a separate thread.
|
||||
type eventLoop struct {
|
||||
win *callbacks
|
||||
// wakeup is the callback to wake up the event loop.
|
||||
wakeup func()
|
||||
// driverFuncs is a channel of functions to run the next
|
||||
// time the window loop waits for events.
|
||||
driverFuncs chan func()
|
||||
// invalidates is notified when an invalidate is requested by the client.
|
||||
invalidates chan struct{}
|
||||
// immediateInvalidates is an optimistic invalidates that doesn't require a wakeup.
|
||||
immediateInvalidates chan struct{}
|
||||
// events is where the platform backend delivers events bound for the
|
||||
// user program.
|
||||
events chan event.Event
|
||||
frames chan *op.Ops
|
||||
frameAck chan struct{}
|
||||
// delivering avoids re-entrant event delivery.
|
||||
delivering bool
|
||||
}
|
||||
|
||||
type frameEvent struct {
|
||||
FrameEvent
|
||||
|
||||
Sync bool
|
||||
}
|
||||
|
||||
type context interface {
|
||||
API() gpu.API
|
||||
RenderTarget() (gpu.RenderTarget, error)
|
||||
Present() error
|
||||
Refresh() error
|
||||
Release()
|
||||
Lock() error
|
||||
Unlock()
|
||||
}
|
||||
|
||||
// driver is the interface for the platform implementation
|
||||
// of a window.
|
||||
type driver interface {
|
||||
// Event blocks until an event is available and returns it.
|
||||
Event() event.Event
|
||||
// Invalidate requests a FrameEvent.
|
||||
Invalidate()
|
||||
// SetAnimating sets the animation flag. When the window is animating,
|
||||
// FrameEvents are delivered as fast as the display can handle them.
|
||||
SetAnimating(anim bool)
|
||||
// ShowTextInput updates the virtual keyboard state.
|
||||
ShowTextInput(show bool)
|
||||
SetInputHint(mode key.InputHint)
|
||||
NewContext() (context, error)
|
||||
// ReadClipboard requests the clipboard content.
|
||||
ReadClipboard()
|
||||
// WriteClipboard requests a clipboard write.
|
||||
WriteClipboard(mime string, s []byte)
|
||||
// Configure the window.
|
||||
Configure([]Option)
|
||||
// SetCursor updates the current cursor to name.
|
||||
SetCursor(cursor pointer.Cursor)
|
||||
// Perform actions on the window.
|
||||
Perform(system.Action)
|
||||
// EditorStateChanged notifies the driver that the editor state changed.
|
||||
EditorStateChanged(old, new editorState)
|
||||
// Run a function on the window thread.
|
||||
Run(f func())
|
||||
// Frame receives a frame.
|
||||
Frame(frame *op.Ops)
|
||||
// ProcessEvent processes an event.
|
||||
ProcessEvent(e event.Event)
|
||||
}
|
||||
|
||||
type windowRendezvous struct {
|
||||
in chan windowAndConfig
|
||||
out chan windowAndConfig
|
||||
windows chan struct{}
|
||||
}
|
||||
|
||||
type windowAndConfig struct {
|
||||
window *callbacks
|
||||
options []Option
|
||||
}
|
||||
|
||||
func newWindowRendezvous() *windowRendezvous {
|
||||
wr := &windowRendezvous{
|
||||
in: make(chan windowAndConfig),
|
||||
out: make(chan windowAndConfig),
|
||||
windows: make(chan struct{}),
|
||||
}
|
||||
go func() {
|
||||
in := wr.in
|
||||
var window windowAndConfig
|
||||
var out chan windowAndConfig
|
||||
for {
|
||||
select {
|
||||
case w := <-in:
|
||||
window = w
|
||||
out = wr.out
|
||||
case out <- window:
|
||||
}
|
||||
}
|
||||
}()
|
||||
return wr
|
||||
}
|
||||
|
||||
func newEventLoop(w *callbacks, wakeup func()) *eventLoop {
|
||||
return &eventLoop{
|
||||
win: w,
|
||||
wakeup: wakeup,
|
||||
events: make(chan event.Event),
|
||||
invalidates: make(chan struct{}, 1),
|
||||
immediateInvalidates: make(chan struct{}),
|
||||
frames: make(chan *op.Ops),
|
||||
frameAck: make(chan struct{}),
|
||||
driverFuncs: make(chan func(), 1),
|
||||
}
|
||||
}
|
||||
|
||||
// Frame receives a frame and waits for its processing. It is called by
|
||||
// the client goroutine.
|
||||
func (e *eventLoop) Frame(frame *op.Ops) {
|
||||
e.frames <- frame
|
||||
<-e.frameAck
|
||||
}
|
||||
|
||||
// Event returns the next available event. It is called by the client
|
||||
// goroutine.
|
||||
func (e *eventLoop) Event() event.Event {
|
||||
for {
|
||||
evt := <-e.events
|
||||
// Receiving a flushEvent indicates to the platform backend that
|
||||
// all previous events have been processed by the user program.
|
||||
if _, ok := evt.(flushEvent); ok {
|
||||
continue
|
||||
}
|
||||
return evt
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidate requests invalidation of the window. It is called by the client
|
||||
// goroutine.
|
||||
func (e *eventLoop) Invalidate() {
|
||||
select {
|
||||
case e.immediateInvalidates <- struct{}{}:
|
||||
// The event loop was waiting, no need for a wakeup.
|
||||
case e.invalidates <- struct{}{}:
|
||||
// The event loop is sleeping, wake it up.
|
||||
e.wakeup()
|
||||
default:
|
||||
// A redraw is pending.
|
||||
}
|
||||
}
|
||||
|
||||
// Run f in the window loop thread. It is called by the client goroutine.
|
||||
func (e *eventLoop) Run(f func()) {
|
||||
e.driverFuncs <- f
|
||||
e.wakeup()
|
||||
}
|
||||
|
||||
// FlushEvents delivers pending events to the client.
|
||||
func (e *eventLoop) FlushEvents() {
|
||||
if e.delivering {
|
||||
return
|
||||
}
|
||||
e.delivering = true
|
||||
defer func() { e.delivering = false }()
|
||||
for {
|
||||
evt, ok := e.win.nextEvent()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
e.deliverEvent(evt)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *eventLoop) deliverEvent(evt event.Event) {
|
||||
var frames <-chan *op.Ops
|
||||
for {
|
||||
select {
|
||||
case f := <-e.driverFuncs:
|
||||
f()
|
||||
case frame := <-frames:
|
||||
// The client called FrameEvent.Frame.
|
||||
frames = nil
|
||||
e.win.ProcessFrame(frame, e.frameAck)
|
||||
case e.events <- evt:
|
||||
switch evt.(type) {
|
||||
case flushEvent, DestroyEvent:
|
||||
// DestroyEvents are not flushed.
|
||||
return
|
||||
case FrameEvent:
|
||||
frames = e.frames
|
||||
}
|
||||
evt = theFlushEvent
|
||||
case <-e.invalidates:
|
||||
e.win.Invalidate()
|
||||
case <-e.immediateInvalidates:
|
||||
e.win.Invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *eventLoop) Wakeup() {
|
||||
for {
|
||||
select {
|
||||
case f := <-e.driverFuncs:
|
||||
f()
|
||||
case <-e.invalidates:
|
||||
e.win.Invalidate()
|
||||
case <-e.immediateInvalidates:
|
||||
e.win.Invalidate()
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func walkActions(actions system.Action, do func(system.Action)) {
|
||||
for a := system.Action(1); actions != 0; a <<= 1 {
|
||||
if actions&a != 0 {
|
||||
actions &^= a
|
||||
do(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wakeupEvent) ImplementsEvent() {}
|
||||
func (ConfigEvent) ImplementsEvent() {}
|
||||
+1508
File diff suppressed because it is too large
Load Diff
+265
@@ -0,0 +1,265 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_runOnMain(uintptr_t h);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createDisplayLink(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl);
|
||||
__attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
|
||||
__attribute__ ((visibility ("hidden"))) int gio_stopDisplayLink(CFTypeRef dl);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_hideCursor();
|
||||
__attribute__ ((visibility ("hidden"))) void gio_showCursor();
|
||||
__attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID);
|
||||
|
||||
static bool isMainThread() {
|
||||
return [NSThread isMainThread];
|
||||
}
|
||||
|
||||
static NSUInteger nsstringLength(CFTypeRef cstr) {
|
||||
NSString *str = (__bridge NSString *)cstr;
|
||||
return [str length];
|
||||
}
|
||||
|
||||
static void nsstringGetCharacters(CFTypeRef cstr, unichar *chars, NSUInteger loc, NSUInteger length) {
|
||||
NSString *str = (__bridge NSString *)cstr;
|
||||
[str getCharacters:chars range:NSMakeRange(loc, length)];
|
||||
}
|
||||
|
||||
static CFTypeRef newNSString(unichar *chars, NSUInteger length) {
|
||||
@autoreleasepool {
|
||||
NSString *s = [NSString string];
|
||||
if (length > 0) {
|
||||
s = [NSString stringWithCharacters:chars length:length];
|
||||
}
|
||||
return CFBridgingRetain(s);
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime/cgo"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/io/pointer"
|
||||
)
|
||||
|
||||
// displayLink is the state for a display link (CVDisplayLinkRef on macOS,
|
||||
// CADisplayLink on iOS). It runs a state-machine goroutine that keeps the
|
||||
// display link running for a while after being stopped to avoid the thread
|
||||
// start/stop overhead and because the CVDisplayLink sometimes fails to
|
||||
// start, stop and start again within a short duration.
|
||||
type displayLink struct {
|
||||
callback func()
|
||||
// states is for starting or stopping the display link.
|
||||
states chan bool
|
||||
// done is closed when the display link is destroyed.
|
||||
done chan struct{}
|
||||
// dids receives the display id when the callback owner is moved
|
||||
// to a different screen.
|
||||
dids chan uint64
|
||||
// running tracks the desired state of the link. running is accessed
|
||||
// with atomic.
|
||||
running uint32
|
||||
}
|
||||
|
||||
// displayLinks maps CFTypeRefs to *displayLinks.
|
||||
var displayLinks sync.Map
|
||||
|
||||
func isMainThread() bool {
|
||||
return bool(C.isMainThread())
|
||||
}
|
||||
|
||||
// runOnMain runs the function on the main thread.
|
||||
func runOnMain(f func()) {
|
||||
if isMainThread() {
|
||||
f()
|
||||
return
|
||||
}
|
||||
C.gio_runOnMain(C.uintptr_t(cgo.NewHandle(f)))
|
||||
}
|
||||
|
||||
//export gio_runFunc
|
||||
func gio_runFunc(h C.uintptr_t) {
|
||||
handle := cgo.Handle(h)
|
||||
defer handle.Delete()
|
||||
f := handle.Value().(func())
|
||||
f()
|
||||
}
|
||||
|
||||
// nsstringToString converts a NSString to a Go string.
|
||||
func nsstringToString(str C.CFTypeRef) string {
|
||||
if str == 0 {
|
||||
return ""
|
||||
}
|
||||
n := C.nsstringLength(str)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
chars := make([]uint16, n)
|
||||
C.nsstringGetCharacters(str, (*C.unichar)(unsafe.Pointer(&chars[0])), 0, n)
|
||||
utf8 := utf16.Decode(chars)
|
||||
return string(utf8)
|
||||
}
|
||||
|
||||
// stringToNSString converts a Go string to a retained NSString.
|
||||
func stringToNSString(str string) C.CFTypeRef {
|
||||
u16 := utf16.Encode([]rune(str))
|
||||
var chars *C.unichar
|
||||
if len(u16) > 0 {
|
||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||
}
|
||||
return C.newNSString(chars, C.NSUInteger(len(u16)))
|
||||
}
|
||||
|
||||
func newDisplayLink(callback func()) (*displayLink, error) {
|
||||
d := &displayLink{
|
||||
callback: callback,
|
||||
done: make(chan struct{}),
|
||||
states: make(chan bool),
|
||||
dids: make(chan uint64),
|
||||
}
|
||||
dl := C.gio_createDisplayLink()
|
||||
if dl == 0 {
|
||||
return nil, errors.New("app: failed to create display link")
|
||||
}
|
||||
go d.run(dl)
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *displayLink) run(dl C.CFTypeRef) {
|
||||
defer C.gio_releaseDisplayLink(dl)
|
||||
displayLinks.Store(dl, d)
|
||||
defer displayLinks.Delete(dl)
|
||||
var stopTimer *time.Timer
|
||||
var tchan <-chan time.Time
|
||||
started := false
|
||||
for {
|
||||
select {
|
||||
case <-tchan:
|
||||
tchan = nil
|
||||
started = false
|
||||
C.gio_stopDisplayLink(dl)
|
||||
case start := <-d.states:
|
||||
switch {
|
||||
case !start && tchan == nil:
|
||||
// stopTimeout is the delay before stopping the display link to
|
||||
// avoid the overhead of frequently starting and stopping the
|
||||
// link thread.
|
||||
const stopTimeout = 500 * time.Millisecond
|
||||
if stopTimer == nil {
|
||||
stopTimer = time.NewTimer(stopTimeout)
|
||||
} else {
|
||||
// stopTimer is always drained when tchan == nil.
|
||||
stopTimer.Reset(stopTimeout)
|
||||
}
|
||||
tchan = stopTimer.C
|
||||
atomic.StoreUint32(&d.running, 0)
|
||||
case start:
|
||||
if tchan != nil && !stopTimer.Stop() {
|
||||
<-tchan
|
||||
}
|
||||
tchan = nil
|
||||
atomic.StoreUint32(&d.running, 1)
|
||||
if !started {
|
||||
started = true
|
||||
C.gio_startDisplayLink(dl)
|
||||
}
|
||||
}
|
||||
case did := <-d.dids:
|
||||
C.gio_setDisplayLinkDisplay(dl, C.uint64_t(did))
|
||||
case <-d.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *displayLink) Start() {
|
||||
d.states <- true
|
||||
}
|
||||
|
||||
func (d *displayLink) Stop() {
|
||||
d.states <- false
|
||||
}
|
||||
|
||||
func (d *displayLink) Close() {
|
||||
close(d.done)
|
||||
}
|
||||
|
||||
func (d *displayLink) SetDisplayID(did uint64) {
|
||||
d.dids <- did
|
||||
}
|
||||
|
||||
//export gio_onFrameCallback
|
||||
func gio_onFrameCallback(ref C.CFTypeRef) {
|
||||
d, exists := displayLinks.Load(ref)
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
dl := d.(*displayLink)
|
||||
if atomic.LoadUint32(&dl.running) != 0 {
|
||||
dl.callback()
|
||||
}
|
||||
}
|
||||
|
||||
var macosCursorID = [...]byte{
|
||||
pointer.CursorDefault: 0,
|
||||
pointer.CursorNone: 1,
|
||||
pointer.CursorText: 2,
|
||||
pointer.CursorVerticalText: 3,
|
||||
pointer.CursorPointer: 4,
|
||||
pointer.CursorCrosshair: 5,
|
||||
pointer.CursorAllScroll: 6,
|
||||
pointer.CursorColResize: 7,
|
||||
pointer.CursorRowResize: 8,
|
||||
pointer.CursorGrab: 9,
|
||||
pointer.CursorGrabbing: 10,
|
||||
pointer.CursorNotAllowed: 11,
|
||||
pointer.CursorWait: 12,
|
||||
pointer.CursorProgress: 13,
|
||||
pointer.CursorNorthWestResize: 14,
|
||||
pointer.CursorNorthEastResize: 15,
|
||||
pointer.CursorSouthWestResize: 16,
|
||||
pointer.CursorSouthEastResize: 17,
|
||||
pointer.CursorNorthSouthResize: 18,
|
||||
pointer.CursorEastWestResize: 19,
|
||||
pointer.CursorWestResize: 20,
|
||||
pointer.CursorEastResize: 21,
|
||||
pointer.CursorNorthResize: 22,
|
||||
pointer.CursorSouthResize: 23,
|
||||
pointer.CursorNorthEastSouthWestResize: 24,
|
||||
pointer.CursorNorthWestSouthEastResize: 25,
|
||||
}
|
||||
|
||||
// windowSetCursor updates the cursor from the current one to a new one
|
||||
// and returns the new one.
|
||||
func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
|
||||
if from == to {
|
||||
return to
|
||||
}
|
||||
if to == pointer.CursorNone {
|
||||
C.gio_hideCursor()
|
||||
return to
|
||||
}
|
||||
if from == pointer.CursorNone {
|
||||
C.gio_showCursor()
|
||||
}
|
||||
C.gio_setCursor(C.NSUInteger(macosCursorID[to]))
|
||||
return to
|
||||
}
|
||||
|
||||
func (w *window) wakeup() {
|
||||
runOnMain(func() {
|
||||
w.loop.Wakeup()
|
||||
w.loop.FlushEvents()
|
||||
})
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
void gio_runOnMain(uintptr_t h) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
gio_runFunc(h);
|
||||
});
|
||||
}
|
||||
+456
@@ -0,0 +1,456 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && ios
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -DGLES_SILENCE_DEPRECATION -Werror -Wno-deprecated-declarations -fmodules -fobjc-arc -x objective-c
|
||||
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <UIKit/UIKit.h>
|
||||
#include <stdint.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) int gio_applicationMain(int argc, char *argv[]);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
|
||||
|
||||
struct drawParams {
|
||||
CGFloat dpi, sdpi;
|
||||
CGFloat width, height;
|
||||
CGFloat top, right, bottom, left;
|
||||
};
|
||||
|
||||
static void writeClipboard(unichar *chars, NSUInteger length) {
|
||||
#if !TARGET_OS_TV
|
||||
@autoreleasepool {
|
||||
NSString *s = [NSString string];
|
||||
if (length > 0) {
|
||||
s = [NSString stringWithCharacters:chars length:length];
|
||||
}
|
||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||
p.string = s;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static CFTypeRef readClipboard(void) {
|
||||
#if !TARGET_OS_TV
|
||||
@autoreleasepool {
|
||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||
return (__bridge_retained CFTypeRef)p.string;
|
||||
}
|
||||
#else
|
||||
return nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void showTextInput(CFTypeRef viewRef) {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
[view becomeFirstResponder];
|
||||
}
|
||||
|
||||
static void hideTextInput(CFTypeRef viewRef) {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
[view resignFirstResponder];
|
||||
}
|
||||
|
||||
static struct drawParams viewDrawParams(CFTypeRef viewRef) {
|
||||
UIView *v = (__bridge UIView *)viewRef;
|
||||
struct drawParams params;
|
||||
CGFloat scale = v.layer.contentsScale;
|
||||
// Use 163 as the standard ppi on iOS.
|
||||
params.dpi = 163*scale;
|
||||
params.sdpi = params.dpi;
|
||||
UIEdgeInsets insets = v.layoutMargins;
|
||||
if (@available(iOS 11.0, tvOS 11.0, *)) {
|
||||
UIFontMetrics *metrics = [UIFontMetrics defaultMetrics];
|
||||
params.sdpi = [metrics scaledValueForValue:params.sdpi];
|
||||
insets = v.safeAreaInsets;
|
||||
}
|
||||
params.width = v.bounds.size.width*scale;
|
||||
params.height = v.bounds.size.height*scale;
|
||||
params.top = insets.top*scale;
|
||||
params.right = insets.right*scale;
|
||||
params.bottom = insets.bottom*scale;
|
||||
params.left = insets.left*scale;
|
||||
return params;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"image"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/cgo"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type UIKitViewEvent struct {
|
||||
// ViewController is a CFTypeRef for the UIViewController backing a Window.
|
||||
ViewController uintptr
|
||||
}
|
||||
|
||||
type window struct {
|
||||
view C.CFTypeRef
|
||||
w *callbacks
|
||||
displayLink *displayLink
|
||||
loop *eventLoop
|
||||
|
||||
hidden bool
|
||||
cursor pointer.Cursor
|
||||
config Config
|
||||
|
||||
pointerMap []C.CFTypeRef
|
||||
}
|
||||
|
||||
var mainWindow = newWindowRendezvous()
|
||||
|
||||
func init() {
|
||||
// Darwin requires UI operations happen on the main thread only.
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
//export onCreate
|
||||
func onCreate(view, controller C.CFTypeRef) {
|
||||
wopts := <-mainWindow.out
|
||||
w := &window{
|
||||
view: view,
|
||||
w: wopts.window,
|
||||
}
|
||||
w.loop = newEventLoop(w.w, w.wakeup)
|
||||
w.w.SetDriver(w)
|
||||
mainWindow.windows <- struct{}{}
|
||||
dl, err := newDisplayLink(func() {
|
||||
w.draw(false)
|
||||
})
|
||||
if err != nil {
|
||||
w.w.ProcessEvent(DestroyEvent{Err: err})
|
||||
return
|
||||
}
|
||||
w.displayLink = dl
|
||||
C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
|
||||
w.Configure(wopts.options)
|
||||
w.ProcessEvent(UIKitViewEvent{ViewController: uintptr(controller)})
|
||||
}
|
||||
|
||||
func viewFor(h C.uintptr_t) *window {
|
||||
return cgo.Handle(h).Value().(*window)
|
||||
}
|
||||
|
||||
//export gio_onDraw
|
||||
func gio_onDraw(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
if w.hidden {
|
||||
return
|
||||
}
|
||||
params := C.viewDrawParams(w.view)
|
||||
if params.width == 0 || params.height == 0 {
|
||||
return
|
||||
}
|
||||
const inchPrDp = 1.0 / 163
|
||||
m := unit.Metric{
|
||||
PxPerDp: float32(params.dpi) * inchPrDp,
|
||||
PxPerSp: float32(params.sdpi) * inchPrDp,
|
||||
}
|
||||
dppp := unit.Dp(1. / m.PxPerDp)
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: int(params.width + .5),
|
||||
Y: int(params.height + .5),
|
||||
},
|
||||
Insets: Insets{
|
||||
Top: unit.Dp(params.top) * dppp,
|
||||
Bottom: unit.Dp(params.bottom) * dppp,
|
||||
Left: unit.Dp(params.left) * dppp,
|
||||
Right: unit.Dp(params.right) * dppp,
|
||||
},
|
||||
Metric: m,
|
||||
},
|
||||
Sync: sync,
|
||||
})
|
||||
}
|
||||
|
||||
//export onStop
|
||||
func onStop(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.hidden = true
|
||||
}
|
||||
|
||||
//export onStart
|
||||
func onStart(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.hidden = false
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
//export onDestroy
|
||||
func onDestroy(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.ProcessEvent(UIKitViewEvent{})
|
||||
w.ProcessEvent(DestroyEvent{})
|
||||
w.displayLink.Close()
|
||||
w.displayLink = nil
|
||||
cgo.Handle(h).Delete()
|
||||
w.view = 0
|
||||
}
|
||||
|
||||
//export onFocus
|
||||
func onFocus(h C.uintptr_t, focus int) {
|
||||
w := viewFor(h)
|
||||
w.config.Focused = focus != 0
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export onLowMemory
|
||||
func onLowMemory() {
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
}
|
||||
|
||||
//export onUpArrow
|
||||
func onUpArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameUpArrow)
|
||||
}
|
||||
|
||||
//export onDownArrow
|
||||
func onDownArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameDownArrow)
|
||||
}
|
||||
|
||||
//export onLeftArrow
|
||||
func onLeftArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameLeftArrow)
|
||||
}
|
||||
|
||||
//export onRightArrow
|
||||
func onRightArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameRightArrow)
|
||||
}
|
||||
|
||||
//export onDeleteBackward
|
||||
func onDeleteBackward(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameDeleteBackward)
|
||||
}
|
||||
|
||||
//export onText
|
||||
func onText(h C.uintptr_t, str C.CFTypeRef) {
|
||||
w := viewFor(h)
|
||||
w.w.EditorInsert(nsstringToString(str))
|
||||
}
|
||||
|
||||
//export onTouch
|
||||
func onTouch(h C.uintptr_t, last C.int, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
|
||||
var kind pointer.Kind
|
||||
switch phase {
|
||||
case C.UITouchPhaseBegan:
|
||||
kind = pointer.Press
|
||||
case C.UITouchPhaseMoved:
|
||||
kind = pointer.Move
|
||||
case C.UITouchPhaseEnded:
|
||||
kind = pointer.Release
|
||||
case C.UITouchPhaseCancelled:
|
||||
kind = pointer.Cancel
|
||||
default:
|
||||
return
|
||||
}
|
||||
w := viewFor(h)
|
||||
t := time.Duration(float64(ti) * float64(time.Second))
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Touch,
|
||||
PointerID: w.lookupTouch(last != 0, touchRef),
|
||||
Position: p,
|
||||
Time: t,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
cstr := C.readClipboard()
|
||||
defer C.CFRelease(cstr)
|
||||
content := nsstringToString(cstr)
|
||||
w.ProcessEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||
u16 := utf16.Encode([]rune(string(s)))
|
||||
var chars *C.unichar
|
||||
if len(u16) > 0 {
|
||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||
}
|
||||
C.writeClipboard(chars, C.NSUInteger(len(u16)))
|
||||
}
|
||||
|
||||
func (w *window) Configure([]Option) {
|
||||
// Decorations are never disabled.
|
||||
w.config.Decorated = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
||||
|
||||
func (w *window) Perform(system.Action) {}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
if anim {
|
||||
w.displayLink.Start()
|
||||
} else {
|
||||
w.displayLink.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
w.cursor = windowSetCursor(w.cursor, cursor)
|
||||
}
|
||||
|
||||
func (w *window) onKeyCommand(name key.Name) {
|
||||
w.ProcessEvent(key.Event{
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
|
||||
// lookupTouch maps an UITouch pointer value to an index. If
|
||||
// last is set, the map is cleared.
|
||||
func (w *window) lookupTouch(last bool, touch C.CFTypeRef) pointer.ID {
|
||||
id := -1
|
||||
for i, ref := range w.pointerMap {
|
||||
if ref == touch {
|
||||
id = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if id == -1 {
|
||||
id = len(w.pointerMap)
|
||||
w.pointerMap = append(w.pointerMap, touch)
|
||||
}
|
||||
if last {
|
||||
w.pointerMap = w.pointerMap[:0]
|
||||
}
|
||||
return pointer.ID(id)
|
||||
}
|
||||
|
||||
func (w *window) contextView() C.CFTypeRef {
|
||||
return w.view
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {
|
||||
if show {
|
||||
C.showTextInput(w.view)
|
||||
} else {
|
||||
C.hideTextInput(w.view)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||
|
||||
func (w *window) ProcessEvent(e event.Event) {
|
||||
w.w.ProcessEvent(e)
|
||||
w.loop.FlushEvents()
|
||||
}
|
||||
|
||||
func (w *window) Event() event.Event {
|
||||
return w.loop.Event()
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
w.loop.Invalidate()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
w.loop.Run(f)
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.loop.Frame(frame)
|
||||
}
|
||||
|
||||
func newWindow(win *callbacks, options []Option) {
|
||||
mainWindow.in <- windowAndConfig{win, options}
|
||||
<-mainWindow.windows
|
||||
}
|
||||
|
||||
var mainMode = mainModeUndefined
|
||||
|
||||
const (
|
||||
mainModeUndefined = iota
|
||||
mainModeExe
|
||||
mainModeLibrary
|
||||
)
|
||||
|
||||
func osMain() {
|
||||
switch mainMode {
|
||||
case mainModeUndefined:
|
||||
if !isMainThread() {
|
||||
panic("app.Main must be run on the main goroutine")
|
||||
}
|
||||
|
||||
mainMode = mainModeExe
|
||||
var argv []*C.char
|
||||
for _, arg := range os.Args {
|
||||
a := C.CString(arg)
|
||||
defer C.free(unsafe.Pointer(a))
|
||||
argv = append(argv, a)
|
||||
}
|
||||
C.gio_applicationMain(C.int(len(argv)), unsafe.SliceData(argv))
|
||||
case mainModeExe:
|
||||
panic("app.Main may be called only once")
|
||||
case mainModeLibrary:
|
||||
// Do nothing, we're embedded as a library.
|
||||
}
|
||||
select {}
|
||||
}
|
||||
|
||||
//export gio_onOpenURI
|
||||
func gio_onOpenURI(uri C.CFTypeRef) {
|
||||
evt, err := newURLEvent(nsstringToString(uri))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
processGlobalEvent(evt)
|
||||
}
|
||||
|
||||
//export gio_runMain
|
||||
func gio_runMain() {
|
||||
if !isMainThread() {
|
||||
panic("app.Main must be run on the main goroutine")
|
||||
}
|
||||
switch mainMode {
|
||||
case mainModeUndefined:
|
||||
mainMode = mainModeLibrary
|
||||
runMain()
|
||||
case mainModeExe:
|
||||
// Do nothing, main has already been called.
|
||||
}
|
||||
}
|
||||
|
||||
func (UIKitViewEvent) implementsViewEvent() {}
|
||||
func (UIKitViewEvent) ImplementsEvent() {}
|
||||
func (u UIKitViewEvent) Valid() bool {
|
||||
return u != (UIKitViewEvent{})
|
||||
}
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,ios
|
||||
|
||||
@import UIKit;
|
||||
|
||||
#include <stdint.h>
|
||||
#include "_cgo_export.h"
|
||||
#include "framework_ios.h"
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) Class gio_layerClass(void);
|
||||
|
||||
@interface GioView: UIView <UIKeyInput>
|
||||
@property uintptr_t handle;
|
||||
@end
|
||||
|
||||
@implementation GioViewController
|
||||
|
||||
CGFloat _keyboardHeight;
|
||||
|
||||
- (void)loadView {
|
||||
gio_runMain();
|
||||
|
||||
CGRect zeroFrame = CGRectMake(0, 0, 0, 0);
|
||||
self.view = [[UIView alloc] initWithFrame:zeroFrame];
|
||||
self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame];
|
||||
[self.view addSubview: drawView];
|
||||
#if !TARGET_OS_TV
|
||||
drawView.multipleTouchEnabled = YES;
|
||||
#endif
|
||||
drawView.preservesSuperviewLayoutMargins = YES;
|
||||
drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
onCreate((__bridge CFTypeRef)drawView, (__bridge CFTypeRef)self);
|
||||
#if !TARGET_OS_TV
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(keyboardWillChange:)
|
||||
name:UIKeyboardWillShowNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(keyboardWillChange:)
|
||||
name:UIKeyboardWillChangeFrameNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(keyboardWillHide:)
|
||||
name:UIKeyboardWillHideNotification
|
||||
object:nil];
|
||||
#endif
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self
|
||||
selector: @selector(applicationDidEnterBackground:)
|
||||
name: UIApplicationDidEnterBackgroundNotification
|
||||
object: nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self
|
||||
selector: @selector(applicationWillEnterForeground:)
|
||||
name: UIApplicationWillEnterForegroundNotification
|
||||
object: nil];
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
if (view != nil) {
|
||||
onStart(view.handle);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
if (view != nil) {
|
||||
onStop(view.handle);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
[super viewDidDisappear:animated];
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
onDestroy(view.handle);
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
CGRect frame = self.view.bounds;
|
||||
// Adjust view bounds to make room for the keyboard.
|
||||
frame.size.height -= _keyboardHeight;
|
||||
view.frame = frame;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
onLowMemory();
|
||||
[super didReceiveMemoryWarning];
|
||||
}
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
- (void)keyboardWillChange:(NSNotification *)note {
|
||||
NSDictionary *userInfo = note.userInfo;
|
||||
CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||
_keyboardHeight = f.size.height;
|
||||
[self.view setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)keyboardWillHide:(NSNotification *)note {
|
||||
_keyboardHeight = 0.0;
|
||||
[self.view setNeedsLayout];
|
||||
}
|
||||
#endif
|
||||
@end
|
||||
|
||||
static void handleTouches(int last, GioView *view, NSSet<UITouch *> *touches, UIEvent *event) {
|
||||
CGFloat scale = view.contentScaleFactor;
|
||||
NSUInteger i = 0;
|
||||
NSUInteger n = [touches count];
|
||||
for (UITouch *touch in touches) {
|
||||
CFTypeRef touchRef = (__bridge CFTypeRef)touch;
|
||||
i++;
|
||||
NSArray<UITouch *> *coalescedTouches = [event coalescedTouchesForTouch:touch];
|
||||
NSUInteger j = 0;
|
||||
NSUInteger m = [coalescedTouches count];
|
||||
for (UITouch *coalescedTouch in [event coalescedTouchesForTouch:touch]) {
|
||||
CGPoint loc = [coalescedTouch locationInView:view];
|
||||
j++;
|
||||
int lastTouch = last && i == n && j == m;
|
||||
onTouch(view.handle, lastTouch, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@implementation GioView
|
||||
NSArray<UIKeyCommand *> *_keyCommands;
|
||||
+ (void)onFrameCallback:(CADisplayLink *)link {
|
||||
gio_onFrameCallback((__bridge CFTypeRef)link);
|
||||
}
|
||||
+ (Class)layerClass {
|
||||
return gio_layerClass();
|
||||
}
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||
self.contentScaleFactor = newWindow.screen.nativeScale;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
[self registerSceneNotifications:newWindow];
|
||||
}else{
|
||||
[self registerWindowNotifications:newWindow];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)registerSceneNotifications:(UIWindow *)newWindow {
|
||||
if (self.window != nil) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UISceneDidActivateNotification
|
||||
object:self.window.windowScene];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UISceneWillDeactivateNotification
|
||||
object:self.window.windowScene];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onSceneDidActivate:)
|
||||
name:UISceneDidActivateNotification
|
||||
object:newWindow.windowScene];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onSceneWillDeactivate:)
|
||||
name:UISceneWillDeactivateNotification
|
||||
object:newWindow.windowScene];
|
||||
}
|
||||
|
||||
- (void)onSceneDidActivate:(NSNotification *)note API_AVAILABLE(ios(13.0)){
|
||||
onFocus(self.handle, YES);
|
||||
}
|
||||
|
||||
- (void)onSceneWillDeactivate:(NSNotification *)note API_AVAILABLE(ios(13.0)){
|
||||
onFocus(self.handle, NO);
|
||||
}
|
||||
|
||||
- (void)registerWindowNotifications:(UIWindow *)newWindow {
|
||||
if (self.window != nil) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UIWindowDidBecomeKeyNotification
|
||||
object:self.window];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UIWindowDidResignKeyNotification
|
||||
object:self.window];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onWindowDidBecomeKey:)
|
||||
name:UIWindowDidBecomeKeyNotification
|
||||
object:newWindow];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onWindowDidResignKey:)
|
||||
name:UIWindowDidResignKeyNotification
|
||||
object:newWindow];
|
||||
}
|
||||
|
||||
- (void)onWindowDidBecomeKey:(NSNotification *)note {
|
||||
if (self.isFirstResponder) {
|
||||
onFocus(self.handle, YES);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onWindowDidResignKey:(NSNotification *)note {
|
||||
if (self.isFirstResponder) {
|
||||
onFocus(self.handle, NO);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
handleTouches(0, self, touches, event);
|
||||
}
|
||||
|
||||
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
handleTouches(0, self, touches, event);
|
||||
}
|
||||
|
||||
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
handleTouches(1, self, touches, event);
|
||||
}
|
||||
|
||||
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
handleTouches(1, self, touches, event);
|
||||
}
|
||||
|
||||
- (void)insertText:(NSString *)text {
|
||||
onText(self.handle, (__bridge CFTypeRef)text);
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)hasText {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)deleteBackward {
|
||||
onDeleteBackward(self.handle);
|
||||
}
|
||||
|
||||
- (void)onUpArrow {
|
||||
onUpArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onDownArrow {
|
||||
onDownArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onLeftArrow {
|
||||
onLeftArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onRightArrow {
|
||||
onRightArrow(self.handle);
|
||||
}
|
||||
|
||||
- (NSArray<UIKeyCommand *> *)keyCommands {
|
||||
if (_keyCommands == nil) {
|
||||
_keyCommands = @[
|
||||
[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow
|
||||
modifierFlags:0
|
||||
action:@selector(onUpArrow)],
|
||||
[UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow
|
||||
modifierFlags:0
|
||||
action:@selector(onDownArrow)],
|
||||
[UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow
|
||||
modifierFlags:0
|
||||
action:@selector(onLeftArrow)],
|
||||
[UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow
|
||||
modifierFlags:0
|
||||
action:@selector(onRightArrow)]
|
||||
];
|
||||
}
|
||||
return _keyCommands;
|
||||
}
|
||||
@end
|
||||
|
||||
CFTypeRef gio_createDisplayLink(void) {
|
||||
CADisplayLink *dl = [CADisplayLink displayLinkWithTarget:[GioView class] selector:@selector(onFrameCallback:)];
|
||||
dl.paused = YES;
|
||||
NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
|
||||
[dl addToRunLoop:runLoop forMode:[runLoop currentMode]];
|
||||
return (__bridge_retained CFTypeRef)dl;
|
||||
}
|
||||
|
||||
int gio_startDisplayLink(CFTypeRef dlref) {
|
||||
CADisplayLink *dl = (__bridge CADisplayLink *)dlref;
|
||||
dl.paused = NO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gio_stopDisplayLink(CFTypeRef dlref) {
|
||||
CADisplayLink *dl = (__bridge CADisplayLink *)dlref;
|
||||
dl.paused = YES;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void gio_releaseDisplayLink(CFTypeRef dlref) {
|
||||
CADisplayLink *dl = (__bridge CADisplayLink *)dlref;
|
||||
[dl invalidate];
|
||||
CFRelease(dlref);
|
||||
}
|
||||
|
||||
void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did) {
|
||||
// Nothing to do on iOS.
|
||||
}
|
||||
|
||||
void gio_hideCursor() {
|
||||
// Not supported.
|
||||
}
|
||||
|
||||
void gio_showCursor() {
|
||||
// Not supported.
|
||||
}
|
||||
|
||||
void gio_setCursor(NSUInteger curID) {
|
||||
// Not supported.
|
||||
}
|
||||
|
||||
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||
GioView *v = (__bridge GioView *)viewRef;
|
||||
v.handle = handle;
|
||||
}
|
||||
|
||||
@interface _gioAppDelegate : UIResponder <UIApplicationDelegate>
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
@end
|
||||
|
||||
@implementation _gioAppDelegate
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
GioViewController *controller = [[GioViewController alloc] initWithNibName:nil bundle:nil];
|
||||
self.window.rootViewController = controller;
|
||||
[self.window makeKeyAndVisible];
|
||||
return YES;
|
||||
}
|
||||
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
|
||||
gio_onOpenURI((__bridge CFTypeRef)url.absoluteString);
|
||||
return YES;
|
||||
}
|
||||
@end
|
||||
|
||||
int gio_applicationMain(int argc, char *argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([_gioAppDelegate class]));
|
||||
}
|
||||
}
|
||||
+1161
File diff suppressed because it is too large
Load Diff
+1192
File diff suppressed because it is too large
Load Diff
+479
@@ -0,0 +1,479 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,!ios
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWithTrans);
|
||||
|
||||
@interface GioAppDelegate : NSObject<NSApplicationDelegate>
|
||||
@end
|
||||
|
||||
@interface GioWindowDelegate : NSObject<NSWindowDelegate>
|
||||
@end
|
||||
|
||||
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
||||
@property uintptr_t handle;
|
||||
@property BOOL presentWithTrans;
|
||||
@end
|
||||
|
||||
@implementation GioWindowDelegate
|
||||
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
- (void)windowWillExitFullScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onChangeScreen(view.handle, dispID);
|
||||
}
|
||||
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
if ([window firstResponder] == view) {
|
||||
gio_onFocus(view.handle, 1);
|
||||
}
|
||||
}
|
||||
- (void)windowDidResignKey:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
if ([window firstResponder] == view) {
|
||||
gio_onFocus(view.handle, 0);
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
|
||||
NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
|
||||
if (!event.hasPreciseScrollingDeltas) {
|
||||
// dx and dy are in rows and columns.
|
||||
dx *= 10;
|
||||
dy *= 10;
|
||||
}
|
||||
// Origin is in the lower left corner. Convert to upper left.
|
||||
CGFloat height = view.bounds.size.height;
|
||||
gio_onMouse(view.handle, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
||||
}
|
||||
|
||||
@implementation GioView
|
||||
- (void)setFrameSize:(NSSize)newSize {
|
||||
[super setFrameSize:newSize];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
// drawRect is called when OpenGL is used, displayLayer otherwise.
|
||||
// Don't know why.
|
||||
- (void)drawRect:(NSRect)r {
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (void)displayLayer:(CALayer *)layer {
|
||||
layer.contentsScale = self.window.backingScaleFactor;
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (CALayer *)makeBackingLayer {
|
||||
CALayer *layer = gio_layerFactory(self.presentWithTrans);
|
||||
layer.delegate = self;
|
||||
return layer;
|
||||
}
|
||||
- (void)viewDidMoveToWindow {
|
||||
gio_onAttached(self.handle, self.window != nil ? 1 : 0);
|
||||
}
|
||||
- (void)mouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||
}
|
||||
- (void)mouseUp:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_UP, 0, 0);
|
||||
}
|
||||
- (void)rightMouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||
}
|
||||
- (void)rightMouseUp:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_UP, 0, 0);
|
||||
}
|
||||
- (void)otherMouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||
}
|
||||
- (void)otherMouseUp:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_UP, 0, 0);
|
||||
}
|
||||
- (void)mouseMoved:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
||||
}
|
||||
- (void)mouseDragged:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
||||
}
|
||||
- (void)rightMouseDragged:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
||||
}
|
||||
- (void)otherMouseDragged:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
||||
}
|
||||
- (void)scrollWheel:(NSEvent *)event {
|
||||
CGFloat dx = -event.scrollingDeltaX;
|
||||
CGFloat dy = -event.scrollingDeltaY;
|
||||
handleMouse(self, event, MOUSE_SCROLL, dx, dy);
|
||||
}
|
||||
- (void)keyDown:(NSEvent *)event {
|
||||
NSString *keys = [event charactersIgnoringModifiers];
|
||||
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
|
||||
}
|
||||
- (void)flagsChanged:(NSEvent *)event {
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
gio_onFlagsChanged(self.handle, [event modifierFlags]);
|
||||
}
|
||||
- (void)keyUp:(NSEvent *)event {
|
||||
NSString *keys = [event charactersIgnoringModifiers];
|
||||
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
|
||||
}
|
||||
- (void)insertText:(id)string {
|
||||
gio_onText(self.handle, (__bridge CFTypeRef)string);
|
||||
}
|
||||
- (void)doCommandBySelector:(SEL)action {
|
||||
if (!gio_onCommandBySelector(self.handle)) {
|
||||
[super doCommandBySelector:action];
|
||||
}
|
||||
}
|
||||
- (BOOL)hasMarkedText {
|
||||
int res = gio_hasMarkedText(self.handle);
|
||||
return res ? YES : NO;
|
||||
}
|
||||
- (NSRange)markedRange {
|
||||
return gio_markedRange(self.handle);
|
||||
}
|
||||
- (NSRange)selectedRange {
|
||||
return gio_selectedRange(self.handle);
|
||||
}
|
||||
- (void)unmarkText {
|
||||
gio_unmarkText(self.handle);
|
||||
}
|
||||
- (void)setMarkedText:(id)string
|
||||
selectedRange:(NSRange)selRange
|
||||
replacementRange:(NSRange)replaceRange {
|
||||
NSString *str;
|
||||
// string is either an NSAttributedString or an NSString.
|
||||
if ([string isKindOfClass:[NSAttributedString class]]) {
|
||||
str = [string string];
|
||||
} else {
|
||||
str = string;
|
||||
}
|
||||
gio_setMarkedText(self.handle, (__bridge CFTypeRef)str, selRange, replaceRange);
|
||||
}
|
||||
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
|
||||
return nil;
|
||||
}
|
||||
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
|
||||
actualRange:(NSRangePointer)actualRange {
|
||||
NSString *str = CFBridgingRelease(gio_substringForProposedRange(self.handle, range, actualRange));
|
||||
return [[NSAttributedString alloc] initWithString:str attributes:nil];
|
||||
}
|
||||
- (void)insertText:(id)string
|
||||
replacementRange:(NSRange)replaceRange {
|
||||
NSString *str;
|
||||
// string is either an NSAttributedString or an NSString.
|
||||
if ([string isKindOfClass:[NSAttributedString class]]) {
|
||||
str = [string string];
|
||||
} else {
|
||||
str = string;
|
||||
}
|
||||
gio_insertText(self.handle, (__bridge CFTypeRef)str, replaceRange);
|
||||
}
|
||||
- (NSUInteger)characterIndexForPoint:(NSPoint)p {
|
||||
return gio_characterIndexForPoint(self.handle, p);
|
||||
}
|
||||
- (NSRect)firstRectForCharacterRange:(NSRange)rng
|
||||
actualRange:(NSRangePointer)actual {
|
||||
NSRect r = gio_firstRectForCharacterRange(self.handle, rng, actual);
|
||||
r = [self convertRect:r toView:nil];
|
||||
return [[self window] convertRectToScreen:r];
|
||||
}
|
||||
- (void)applicationWillUnhide:(NSNotification *)notification {
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (void)applicationDidHide:(NSNotification *)notification {
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (void)dealloc {
|
||||
gio_onDestroy(self.handle);
|
||||
}
|
||||
- (BOOL) becomeFirstResponder {
|
||||
gio_onFocus(self.handle, 1);
|
||||
return [super becomeFirstResponder];
|
||||
}
|
||||
- (BOOL) resignFirstResponder {
|
||||
gio_onFocus(self.handle, 0);
|
||||
return [super resignFirstResponder];
|
||||
}
|
||||
@end
|
||||
|
||||
// Delegates are weakly referenced from their peers. Nothing
|
||||
// else holds a strong reference to our window delegate, so
|
||||
// keep a single global reference instead.
|
||||
static GioWindowDelegate *globalWindowDel;
|
||||
|
||||
static CVReturn displayLinkCallback(CVDisplayLinkRef dl, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *handle) {
|
||||
gio_onFrameCallback(dl);
|
||||
return kCVReturnSuccess;
|
||||
}
|
||||
|
||||
CFTypeRef gio_createDisplayLink(void) {
|
||||
CVDisplayLinkRef dl;
|
||||
CVDisplayLinkCreateWithActiveCGDisplays(&dl);
|
||||
CVDisplayLinkSetOutputCallback(dl, displayLinkCallback, nil);
|
||||
return dl;
|
||||
}
|
||||
|
||||
int gio_startDisplayLink(CFTypeRef dl) {
|
||||
return CVDisplayLinkStart((CVDisplayLinkRef)dl);
|
||||
}
|
||||
|
||||
int gio_stopDisplayLink(CFTypeRef dl) {
|
||||
return CVDisplayLinkStop((CVDisplayLinkRef)dl);
|
||||
}
|
||||
|
||||
void gio_releaseDisplayLink(CFTypeRef dl) {
|
||||
CVDisplayLinkRelease((CVDisplayLinkRef)dl);
|
||||
}
|
||||
|
||||
void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did) {
|
||||
CVDisplayLinkSetCurrentCGDisplay((CVDisplayLinkRef)dl, (CGDirectDisplayID)did);
|
||||
}
|
||||
|
||||
void gio_hideCursor() {
|
||||
@autoreleasepool {
|
||||
[NSCursor hide];
|
||||
}
|
||||
}
|
||||
|
||||
void gio_showCursor() {
|
||||
@autoreleasepool {
|
||||
[NSCursor unhide];
|
||||
}
|
||||
}
|
||||
|
||||
// some cursors are not public, this tries to use a private cursor
|
||||
// and uses fallback when the use of private cursor fails.
|
||||
static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
|
||||
if ([NSCursor respondsToSelector:cursorName]) {
|
||||
id object = [NSCursor performSelector:cursorName];
|
||||
if ([object isKindOfClass:[NSCursor class]]) {
|
||||
[(NSCursor*)object set];
|
||||
return;
|
||||
}
|
||||
}
|
||||
[fallback set];
|
||||
}
|
||||
|
||||
void gio_setCursor(NSUInteger curID) {
|
||||
@autoreleasepool {
|
||||
switch (curID) {
|
||||
case 0: // pointer.CursorDefault
|
||||
[NSCursor.arrowCursor set];
|
||||
break;
|
||||
// case 1: // pointer.CursorNone
|
||||
case 2: // pointer.CursorText
|
||||
[NSCursor.IBeamCursor set];
|
||||
break;
|
||||
case 3: // pointer.CursorVerticalText
|
||||
[NSCursor.IBeamCursorForVerticalLayout set];
|
||||
break;
|
||||
case 4: // pointer.CursorPointer
|
||||
[NSCursor.pointingHandCursor set];
|
||||
break;
|
||||
case 5: // pointer.CursorCrosshair
|
||||
[NSCursor.crosshairCursor set];
|
||||
break;
|
||||
case 6: // pointer.CursorAllScroll
|
||||
// For some reason, using _moveCursor fails on Monterey.
|
||||
// trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
|
||||
[NSCursor.arrowCursor set];
|
||||
break;
|
||||
case 7: // pointer.CursorColResize
|
||||
[NSCursor.resizeLeftRightCursor set];
|
||||
break;
|
||||
case 8: // pointer.CursorRowResize
|
||||
[NSCursor.resizeUpDownCursor set];
|
||||
break;
|
||||
case 9: // pointer.CursorGrab
|
||||
[NSCursor.openHandCursor set];
|
||||
break;
|
||||
case 10: // pointer.CursorGrabbing
|
||||
[NSCursor.closedHandCursor set];
|
||||
break;
|
||||
case 11: // pointer.CursorNotAllowed
|
||||
[NSCursor.operationNotAllowedCursor set];
|
||||
break;
|
||||
case 12: // pointer.CursorWait
|
||||
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
break;
|
||||
case 13: // pointer.CursorProgress
|
||||
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
break;
|
||||
case 14: // pointer.CursorNorthWestResize
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 15: // pointer.CursorNorthEastResize
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 16: // pointer.CursorSouthWestResize
|
||||
trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 17: // pointer.CursorSouthEastResize
|
||||
trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 18: // pointer.CursorNorthSouthResize
|
||||
[NSCursor.resizeUpDownCursor set];
|
||||
break;
|
||||
case 19: // pointer.CursorEastWestResize
|
||||
[NSCursor.resizeLeftRightCursor set];
|
||||
break;
|
||||
case 20: // pointer.CursorWestResize
|
||||
[NSCursor.resizeLeftCursor set];
|
||||
break;
|
||||
case 21: // pointer.CursorEastResize
|
||||
[NSCursor.resizeRightCursor set];
|
||||
break;
|
||||
case 22: // pointer.CursorNorthResize
|
||||
[NSCursor.resizeUpCursor set];
|
||||
break;
|
||||
case 23: // pointer.CursorSouthResize
|
||||
[NSCursor.resizeDownCursor set];
|
||||
break;
|
||||
case 24: // pointer.CursorNorthEastSouthWestResize
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 25: // pointer.CursorNorthWestSouthEastResize
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
default:
|
||||
[NSCursor.arrowCursor set];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height) {
|
||||
@autoreleasepool {
|
||||
NSRect rect = NSMakeRect(0, 0, width, height);
|
||||
NSUInteger styleMask = NSTitledWindowMask |
|
||||
NSResizableWindowMask |
|
||||
NSMiniaturizableWindowMask |
|
||||
NSClosableWindowMask;
|
||||
|
||||
NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
|
||||
styleMask:styleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
[window setAcceptsMouseMovedEvents:YES];
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
[window setContentView:view];
|
||||
window.delegate = globalWindowDel;
|
||||
return (__bridge_retained CFTypeRef)window;
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_createView(int presentWithTrans) {
|
||||
@autoreleasepool {
|
||||
NSRect frame = NSMakeRect(0, 0, 0, 0);
|
||||
GioView* view = [[GioView alloc] initWithFrame:frame];
|
||||
view.presentWithTrans = presentWithTrans ? YES : NO;
|
||||
view.wantsLayer = YES;
|
||||
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:view
|
||||
selector:@selector(applicationWillUnhide:)
|
||||
name:NSApplicationWillUnhideNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:view
|
||||
selector:@selector(applicationDidHide:)
|
||||
name:NSApplicationDidHideNotification
|
||||
object:nil];
|
||||
return CFBridgingRetain(view);
|
||||
}
|
||||
}
|
||||
|
||||
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||
@autoreleasepool {
|
||||
GioView *v = (__bridge GioView *)viewRef;
|
||||
v.handle = handle;
|
||||
}
|
||||
}
|
||||
|
||||
@implementation GioAppDelegate
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
}
|
||||
- (void)application:(NSApplication *)application openURLs:(NSArray<NSURL *> *)urls {
|
||||
for (NSURL *url in urls) {
|
||||
gio_onOpenURI((__bridge CFTypeRef)url.absoluteString);
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
void gio_main() {
|
||||
@autoreleasepool {
|
||||
[NSApplication sharedApplication];
|
||||
GioAppDelegate *del = [[GioAppDelegate alloc] init];
|
||||
[NSApp setDelegate:del];
|
||||
|
||||
NSMenuItem *mainMenu = [NSMenuItem new];
|
||||
|
||||
NSMenu *menu = [NSMenu new];
|
||||
NSMenuItem *hideMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide"
|
||||
action:@selector(hide:)
|
||||
keyEquivalent:@"h"];
|
||||
[menu addItem:hideMenuItem];
|
||||
NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit"
|
||||
action:@selector(terminate:)
|
||||
keyEquivalent:@"q"];
|
||||
[menu addItem:quitMenuItem];
|
||||
[mainMenu setSubmenu:menu];
|
||||
NSMenu *menuBar = [NSMenu new];
|
||||
[menuBar addItem:mainMenu];
|
||||
[NSApp setMainMenu:menuBar];
|
||||
|
||||
globalWindowDel = [[GioWindowDelegate alloc] init];
|
||||
|
||||
[NSApp run];
|
||||
}
|
||||
}
|
||||
|
||||
@interface AppListener : NSObject
|
||||
@end
|
||||
|
||||
static AppListener *appListener;
|
||||
|
||||
@implementation AppListener
|
||||
- (void)launchFinished:(NSNotification *)notification {
|
||||
appListener = nil;
|
||||
gio_onFinishLaunching();
|
||||
}
|
||||
@end
|
||||
|
||||
void gio_init() {
|
||||
@autoreleasepool {
|
||||
appListener = [[AppListener alloc] init];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:appListener
|
||||
selector:@selector(launchFinished:)
|
||||
name:NSApplicationDidFinishLaunchingNotification
|
||||
object:nil];
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build (linux && !android) || freebsd || openbsd
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/io/pointer"
|
||||
)
|
||||
|
||||
type X11ViewEvent struct {
|
||||
// Display is a pointer to the X11 Display created by XOpenDisplay.
|
||||
Display unsafe.Pointer
|
||||
// Window is the X11 window ID as returned by XCreateWindow.
|
||||
Window uintptr
|
||||
}
|
||||
|
||||
func (X11ViewEvent) implementsViewEvent() {}
|
||||
func (X11ViewEvent) ImplementsEvent() {}
|
||||
func (x X11ViewEvent) Valid() bool {
|
||||
return x != (X11ViewEvent{})
|
||||
}
|
||||
|
||||
type WaylandViewEvent struct {
|
||||
// Display is the *wl_display returned by wl_display_connect.
|
||||
Display unsafe.Pointer
|
||||
// Surface is the *wl_surface returned by wl_compositor_create_surface.
|
||||
Surface unsafe.Pointer
|
||||
}
|
||||
|
||||
func (WaylandViewEvent) implementsViewEvent() {}
|
||||
func (WaylandViewEvent) ImplementsEvent() {}
|
||||
func (w WaylandViewEvent) Valid() bool {
|
||||
return w != (WaylandViewEvent{})
|
||||
}
|
||||
|
||||
func osMain() {
|
||||
select {}
|
||||
}
|
||||
|
||||
type windowDriver func(*callbacks, []Option) error
|
||||
|
||||
// Instead of creating files with build tags for each combination of wayland +/- x11
|
||||
// let each driver initialize these variables with their own version of createWindow.
|
||||
var wlDriver, x11Driver windowDriver
|
||||
|
||||
func newWindow(window *callbacks, options []Option) {
|
||||
var errFirst error
|
||||
for _, d := range []windowDriver{wlDriver, x11Driver} {
|
||||
if d == nil {
|
||||
continue
|
||||
}
|
||||
err := d(window, options)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if errFirst == nil {
|
||||
errFirst = err
|
||||
}
|
||||
}
|
||||
if errFirst == nil {
|
||||
errFirst = errors.New("app: no window driver available")
|
||||
}
|
||||
window.ProcessEvent(DestroyEvent{Err: errFirst})
|
||||
}
|
||||
|
||||
// xCursor contains mapping from pointer.Cursor to XCursor.
|
||||
var xCursor = [...]string{
|
||||
pointer.CursorDefault: "left_ptr",
|
||||
pointer.CursorNone: "",
|
||||
pointer.CursorText: "xterm",
|
||||
pointer.CursorVerticalText: "vertical-text",
|
||||
pointer.CursorPointer: "hand2",
|
||||
pointer.CursorCrosshair: "crosshair",
|
||||
pointer.CursorAllScroll: "fleur",
|
||||
pointer.CursorColResize: "sb_h_double_arrow",
|
||||
pointer.CursorRowResize: "sb_v_double_arrow",
|
||||
pointer.CursorGrab: "hand1",
|
||||
pointer.CursorGrabbing: "move",
|
||||
pointer.CursorNotAllowed: "crossed_circle",
|
||||
pointer.CursorWait: "watch",
|
||||
pointer.CursorProgress: "left_ptr_watch",
|
||||
pointer.CursorNorthWestResize: "top_left_corner",
|
||||
pointer.CursorNorthEastResize: "top_right_corner",
|
||||
pointer.CursorSouthWestResize: "bottom_left_corner",
|
||||
pointer.CursorSouthEastResize: "bottom_right_corner",
|
||||
pointer.CursorNorthSouthResize: "sb_v_double_arrow",
|
||||
pointer.CursorEastWestResize: "sb_h_double_arrow",
|
||||
pointer.CursorWestResize: "left_side",
|
||||
pointer.CursorEastResize: "right_side",
|
||||
pointer.CursorNorthResize: "top_side",
|
||||
pointer.CursorSouthResize: "bottom_side",
|
||||
pointer.CursorNorthEastSouthWestResize: "fd_double_arrow",
|
||||
pointer.CursorNorthWestSouthEastResize: "bd_double_arrow",
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build ((linux && !android) || freebsd) && !nowayland
|
||||
// +build linux,!android freebsd
|
||||
// +build !nowayland
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include "wayland_xdg_shell.h"
|
||||
#include "wayland_xdg_decoration.h"
|
||||
#include "wayland_text_input.h"
|
||||
#include "_cgo_export.h"
|
||||
|
||||
const struct wl_registry_listener gio_registry_listener = {
|
||||
// Cast away const parameter.
|
||||
.global = (void (*)(void *, struct wl_registry *, uint32_t, const char *, uint32_t))gio_onRegistryGlobal,
|
||||
.global_remove = gio_onRegistryGlobalRemove
|
||||
};
|
||||
|
||||
const struct wl_surface_listener gio_surface_listener = {
|
||||
.enter = gio_onSurfaceEnter,
|
||||
.leave = gio_onSurfaceLeave,
|
||||
};
|
||||
|
||||
const struct xdg_surface_listener gio_xdg_surface_listener = {
|
||||
.configure = gio_onXdgSurfaceConfigure,
|
||||
};
|
||||
|
||||
const struct xdg_toplevel_listener gio_xdg_toplevel_listener = {
|
||||
.configure = gio_onToplevelConfigure,
|
||||
.close = gio_onToplevelClose,
|
||||
};
|
||||
|
||||
const struct zxdg_toplevel_decoration_v1_listener gio_zxdg_toplevel_decoration_v1_listener = {
|
||||
.configure = gio_onToplevelDecorationConfigure,
|
||||
};
|
||||
|
||||
static void xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *wm, uint32_t serial) {
|
||||
xdg_wm_base_pong(wm, serial);
|
||||
}
|
||||
|
||||
const struct xdg_wm_base_listener gio_xdg_wm_base_listener = {
|
||||
.ping = xdg_wm_base_handle_ping,
|
||||
};
|
||||
|
||||
const struct wl_callback_listener gio_callback_listener = {
|
||||
.done = gio_onFrameDone,
|
||||
};
|
||||
|
||||
const struct wl_output_listener gio_output_listener = {
|
||||
// Cast away const parameter.
|
||||
.geometry = (void (*)(void *, struct wl_output *, int32_t, int32_t, int32_t, int32_t, int32_t, const char *, const char *, int32_t))gio_onOutputGeometry,
|
||||
.mode = gio_onOutputMode,
|
||||
.done = gio_onOutputDone,
|
||||
.scale = gio_onOutputScale,
|
||||
};
|
||||
|
||||
const struct wl_seat_listener gio_seat_listener = {
|
||||
.capabilities = gio_onSeatCapabilities,
|
||||
// Cast away const parameter.
|
||||
.name = (void (*)(void *, struct wl_seat *, const char *))gio_onSeatName,
|
||||
};
|
||||
|
||||
const struct wl_pointer_listener gio_pointer_listener = {
|
||||
.enter = gio_onPointerEnter,
|
||||
.leave = gio_onPointerLeave,
|
||||
.motion = gio_onPointerMotion,
|
||||
.button = gio_onPointerButton,
|
||||
.axis = gio_onPointerAxis,
|
||||
.frame = gio_onPointerFrame,
|
||||
.axis_source = gio_onPointerAxisSource,
|
||||
.axis_stop = gio_onPointerAxisStop,
|
||||
.axis_discrete = gio_onPointerAxisDiscrete,
|
||||
};
|
||||
|
||||
const struct wl_touch_listener gio_touch_listener = {
|
||||
.down = gio_onTouchDown,
|
||||
.up = gio_onTouchUp,
|
||||
.motion = gio_onTouchMotion,
|
||||
.frame = gio_onTouchFrame,
|
||||
.cancel = gio_onTouchCancel,
|
||||
};
|
||||
|
||||
const struct wl_keyboard_listener gio_keyboard_listener = {
|
||||
.keymap = gio_onKeyboardKeymap,
|
||||
.enter = gio_onKeyboardEnter,
|
||||
.leave = gio_onKeyboardLeave,
|
||||
.key = gio_onKeyboardKey,
|
||||
.modifiers = gio_onKeyboardModifiers,
|
||||
.repeat_info = gio_onKeyboardRepeatInfo
|
||||
};
|
||||
|
||||
const struct zwp_text_input_v3_listener gio_zwp_text_input_v3_listener = {
|
||||
.enter = gio_onTextInputEnter,
|
||||
.leave = gio_onTextInputLeave,
|
||||
// Cast away const parameter.
|
||||
.preedit_string = (void (*)(void *, struct zwp_text_input_v3 *, const char *, int32_t, int32_t))gio_onTextInputPreeditString,
|
||||
.commit_string = (void (*)(void *, struct zwp_text_input_v3 *, const char *))gio_onTextInputCommitString,
|
||||
.delete_surrounding_text = gio_onTextInputDeleteSurroundingText,
|
||||
.done = gio_onTextInputDone
|
||||
};
|
||||
|
||||
const struct wl_data_device_listener gio_data_device_listener = {
|
||||
.data_offer = gio_onDataDeviceOffer,
|
||||
.enter = gio_onDataDeviceEnter,
|
||||
.leave = gio_onDataDeviceLeave,
|
||||
.motion = gio_onDataDeviceMotion,
|
||||
.drop = gio_onDataDeviceDrop,
|
||||
.selection = gio_onDataDeviceSelection,
|
||||
};
|
||||
|
||||
const struct wl_data_offer_listener gio_data_offer_listener = {
|
||||
.offer = (void (*)(void *, struct wl_data_offer *, const char *))gio_onDataOfferOffer,
|
||||
.source_actions = gio_onDataOfferSourceActions,
|
||||
.action = gio_onDataOfferAction,
|
||||
};
|
||||
|
||||
const struct wl_data_source_listener gio_data_source_listener = {
|
||||
.target = (void (*)(void *, struct wl_data_source *, const char *))gio_onDataSourceTarget,
|
||||
.send = (void (*)(void *, struct wl_data_source *, const char *, int32_t))gio_onDataSourceSend,
|
||||
.cancelled = gio_onDataSourceCancelled,
|
||||
.dnd_drop_performed = gio_onDataSourceDNDDropPerformed,
|
||||
.dnd_finished = gio_onDataSourceDNDFinished,
|
||||
.action = gio_onDataSourceAction,
|
||||
};
|
||||
+1945
File diff suppressed because it is too large
Load Diff
+1303
File diff suppressed because it is too large
Load Diff
+928
@@ -0,0 +1,928 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build ((linux && !android) || freebsd || openbsd) && !nox11
|
||||
// +build linux,!android freebsd openbsd
|
||||
// +build !nox11
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo freebsd openbsd CFLAGS: -I/usr/X11R6/include -I/usr/local/include
|
||||
#cgo freebsd openbsd LDFLAGS: -L/usr/X11R6/lib -L/usr/local/lib
|
||||
#cgo freebsd openbsd LDFLAGS: -lX11 -lxkbcommon -lxkbcommon-x11 -lX11-xcb -lXcursor -lXfixes
|
||||
#cgo linux pkg-config: x11 xkbcommon xkbcommon-x11 x11-xcb xcursor xfixes
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <locale.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/Xresource.h>
|
||||
#include <X11/XKBlib.h>
|
||||
#include <X11/Xlib-xcb.h>
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
#include <X11/Xcursor/Xcursor.h>
|
||||
#include <xkbcommon/xkbcommon-x11.h>
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
|
||||
syscall "golang.org/x/sys/unix"
|
||||
|
||||
"gioui.org/app/internal/xkb"
|
||||
)
|
||||
|
||||
const (
|
||||
_NET_WM_STATE_REMOVE = 0
|
||||
_NET_WM_STATE_ADD = 1
|
||||
)
|
||||
|
||||
type x11Window struct {
|
||||
w *callbacks
|
||||
x *C.Display
|
||||
xkb *xkb.Context
|
||||
xkbEventBase C.int
|
||||
xw C.Window
|
||||
|
||||
atoms struct {
|
||||
// "UTF8_STRING".
|
||||
utf8string C.Atom
|
||||
// "text/plain;charset=utf-8".
|
||||
plaintext C.Atom
|
||||
// "TARGETS"
|
||||
targets C.Atom
|
||||
// "CLIPBOARD".
|
||||
clipboard C.Atom
|
||||
// "PRIMARY".
|
||||
primary C.Atom
|
||||
// "CLIPBOARD_CONTENT", the clipboard destination property.
|
||||
clipboardContent C.Atom
|
||||
// "WM_DELETE_WINDOW"
|
||||
evDelWindow C.Atom
|
||||
// "ATOM"
|
||||
atom C.Atom
|
||||
// "GTK_TEXT_BUFFER_CONTENTS"
|
||||
gtk_text_buffer_contents C.Atom
|
||||
// "_NET_WM_NAME"
|
||||
wmName C.Atom
|
||||
// "_NET_WM_STATE"
|
||||
wmState C.Atom
|
||||
// "_NET_WM_STATE_FULLSCREEN"
|
||||
wmStateFullscreen C.Atom
|
||||
// "_NET_ACTIVE_WINDOW"
|
||||
wmActiveWindow C.Atom
|
||||
// _NET_WM_STATE_MAXIMIZED_HORZ
|
||||
wmStateMaximizedHorz C.Atom
|
||||
// _NET_WM_STATE_MAXIMIZED_VERT
|
||||
wmStateMaximizedVert C.Atom
|
||||
}
|
||||
metric unit.Metric
|
||||
notify struct {
|
||||
read, write int
|
||||
}
|
||||
|
||||
animating bool
|
||||
|
||||
pointerBtns pointer.Buttons
|
||||
|
||||
clipboard struct {
|
||||
content []byte
|
||||
}
|
||||
cursor pointer.Cursor
|
||||
config Config
|
||||
|
||||
wakeups chan struct{}
|
||||
handler x11EventHandler
|
||||
buf [100]byte
|
||||
}
|
||||
|
||||
var (
|
||||
newX11EGLContext func(w *x11Window) (context, error)
|
||||
newX11VulkanContext func(w *x11Window) (context, error)
|
||||
)
|
||||
|
||||
// X11 and Vulkan doesn't work reliably on NVIDIA systems.
|
||||
// See https://gioui.org/issue/347.
|
||||
const vulkanBuggy = true
|
||||
|
||||
func (w *x11Window) NewContext() (context, error) {
|
||||
var firstErr error
|
||||
if f := newX11VulkanContext; f != nil && !vulkanBuggy {
|
||||
c, err := f(w)
|
||||
if err == nil {
|
||||
return c, nil
|
||||
}
|
||||
firstErr = err
|
||||
}
|
||||
if f := newX11EGLContext; f != nil {
|
||||
c, err := f(w)
|
||||
if err == nil {
|
||||
return c, nil
|
||||
}
|
||||
firstErr = err
|
||||
}
|
||||
if firstErr != nil {
|
||||
return nil, firstErr
|
||||
}
|
||||
return nil, errors.New("x11: no available GPU backends")
|
||||
}
|
||||
|
||||
func (w *x11Window) SetAnimating(anim bool) {
|
||||
w.animating = anim
|
||||
}
|
||||
|
||||
func (w *x11Window) ReadClipboard() {
|
||||
C.XDeleteProperty(w.x, w.xw, w.atoms.clipboardContent)
|
||||
C.XConvertSelection(w.x, w.atoms.clipboard, w.atoms.utf8string, w.atoms.clipboardContent, w.xw, C.CurrentTime)
|
||||
}
|
||||
|
||||
func (w *x11Window) WriteClipboard(mime string, s []byte) {
|
||||
w.clipboard.content = s
|
||||
C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
|
||||
C.XSetSelectionOwner(w.x, w.atoms.primary, w.xw, C.CurrentTime)
|
||||
}
|
||||
|
||||
func (w *x11Window) Configure(options []Option) {
|
||||
var shints C.XSizeHints
|
||||
prev := w.config
|
||||
cnf := w.config
|
||||
cnf.apply(w.metric, options)
|
||||
// Decorations are never disabled.
|
||||
cnf.Decorated = true
|
||||
|
||||
switch cnf.Mode {
|
||||
case Fullscreen:
|
||||
switch prev.Mode {
|
||||
case Fullscreen:
|
||||
case Minimized:
|
||||
w.raise()
|
||||
fallthrough
|
||||
default:
|
||||
w.config.Mode = Fullscreen
|
||||
w.sendWMStateEvent(_NET_WM_STATE_ADD, w.atoms.wmStateFullscreen, 0)
|
||||
}
|
||||
case Minimized:
|
||||
switch prev.Mode {
|
||||
case Minimized, Fullscreen:
|
||||
default:
|
||||
w.config.Mode = Minimized
|
||||
screen := C.XDefaultScreen(w.x)
|
||||
C.XIconifyWindow(w.x, w.xw, screen)
|
||||
}
|
||||
case Maximized:
|
||||
switch prev.Mode {
|
||||
case Fullscreen:
|
||||
case Minimized:
|
||||
w.raise()
|
||||
fallthrough
|
||||
default:
|
||||
w.config.Mode = Maximized
|
||||
w.sendWMStateEvent(_NET_WM_STATE_ADD, w.atoms.wmStateMaximizedHorz, w.atoms.wmStateMaximizedVert)
|
||||
w.setTitle(prev, cnf)
|
||||
}
|
||||
case Windowed:
|
||||
switch prev.Mode {
|
||||
case Fullscreen:
|
||||
w.config.Mode = Windowed
|
||||
w.sendWMStateEvent(_NET_WM_STATE_REMOVE, w.atoms.wmStateFullscreen, 0)
|
||||
C.XResizeWindow(w.x, w.xw, C.uint(cnf.Size.X), C.uint(cnf.Size.Y))
|
||||
case Minimized:
|
||||
w.config.Mode = Windowed
|
||||
w.raise()
|
||||
case Maximized:
|
||||
w.config.Mode = Windowed
|
||||
w.sendWMStateEvent(_NET_WM_STATE_REMOVE, w.atoms.wmStateMaximizedHorz, w.atoms.wmStateMaximizedVert)
|
||||
}
|
||||
w.setTitle(prev, cnf)
|
||||
if prev.Size != cnf.Size {
|
||||
w.config.Size = cnf.Size
|
||||
C.XResizeWindow(w.x, w.xw, C.uint(cnf.Size.X), C.uint(cnf.Size.Y))
|
||||
}
|
||||
if prev.MinSize != cnf.MinSize {
|
||||
w.config.MinSize = cnf.MinSize
|
||||
shints.min_width = C.int(cnf.MinSize.X)
|
||||
shints.min_height = C.int(cnf.MinSize.Y)
|
||||
shints.flags = C.PMinSize
|
||||
}
|
||||
if prev.MaxSize != cnf.MaxSize {
|
||||
w.config.MaxSize = cnf.MaxSize
|
||||
shints.max_width = C.int(cnf.MaxSize.X)
|
||||
shints.max_height = C.int(cnf.MaxSize.Y)
|
||||
shints.flags = shints.flags | C.PMaxSize
|
||||
}
|
||||
if shints.flags != 0 {
|
||||
C.XSetWMNormalHints(w.x, w.xw, &shints)
|
||||
}
|
||||
}
|
||||
if cnf.Decorated != prev.Decorated {
|
||||
w.config.Decorated = cnf.Decorated
|
||||
}
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *x11Window) setTitle(prev, cnf Config) {
|
||||
if prev.Title != cnf.Title {
|
||||
title := cnf.Title
|
||||
ctitle := C.CString(title)
|
||||
defer C.free(unsafe.Pointer(ctitle))
|
||||
C.XStoreName(w.x, w.xw, ctitle)
|
||||
// set _NET_WM_NAME as well for UTF-8 support in window title.
|
||||
C.XSetTextProperty(w.x, w.xw,
|
||||
&C.XTextProperty{
|
||||
value: (*C.uchar)(unsafe.Pointer(ctitle)),
|
||||
encoding: w.atoms.utf8string,
|
||||
format: 8,
|
||||
nitems: C.ulong(len(title)),
|
||||
},
|
||||
w.atoms.wmName)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) Perform(acts system.Action) {
|
||||
walkActions(acts, func(a system.Action) {
|
||||
switch a {
|
||||
case system.ActionCenter:
|
||||
w.center()
|
||||
case system.ActionRaise:
|
||||
w.raise()
|
||||
}
|
||||
})
|
||||
if acts&system.ActionClose != 0 {
|
||||
w.close()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) center() {
|
||||
screen := C.XDefaultScreen(w.x)
|
||||
width := C.XDisplayWidth(w.x, screen)
|
||||
height := C.XDisplayHeight(w.x, screen)
|
||||
|
||||
var attrs C.XWindowAttributes
|
||||
C.XGetWindowAttributes(w.x, w.xw, &attrs)
|
||||
width -= attrs.border_width
|
||||
height -= attrs.border_width
|
||||
|
||||
sz := w.config.Size
|
||||
x := (int(width) - sz.X) / 2
|
||||
y := (int(height) - sz.Y) / 2
|
||||
|
||||
C.XMoveResizeWindow(w.x, w.xw, C.int(x), C.int(y), C.uint(sz.X), C.uint(sz.Y))
|
||||
}
|
||||
|
||||
func (w *x11Window) raise() {
|
||||
var xev C.XEvent
|
||||
ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
|
||||
*ev = C.XClientMessageEvent{
|
||||
_type: C.ClientMessage,
|
||||
display: w.x,
|
||||
window: w.xw,
|
||||
message_type: w.atoms.wmActiveWindow,
|
||||
format: 32,
|
||||
}
|
||||
C.XSendEvent(
|
||||
w.x,
|
||||
C.XDefaultRootWindow(w.x), // MUST be the root window
|
||||
C.False,
|
||||
C.SubstructureNotifyMask|C.SubstructureRedirectMask,
|
||||
&xev,
|
||||
)
|
||||
C.XMapRaised(w.display(), w.xw)
|
||||
}
|
||||
|
||||
func (w *x11Window) SetCursor(cursor pointer.Cursor) {
|
||||
if cursor == pointer.CursorNone {
|
||||
w.cursor = cursor
|
||||
C.XFixesHideCursor(w.x, w.xw)
|
||||
return
|
||||
}
|
||||
|
||||
xcursor := xCursor[cursor]
|
||||
cname := C.CString(xcursor)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
c := C.XcursorLibraryLoadCursor(w.x, cname)
|
||||
if c == 0 {
|
||||
cursor = pointer.CursorDefault
|
||||
}
|
||||
w.cursor = cursor
|
||||
// If c if null (i.e. cursor was not found),
|
||||
// XDefineCursor will use the default cursor.
|
||||
C.XDefineCursor(w.x, w.xw, c)
|
||||
}
|
||||
|
||||
func (w *x11Window) ShowTextInput(show bool) {}
|
||||
|
||||
func (w *x11Window) SetInputHint(_ key.InputHint) {}
|
||||
|
||||
func (w *x11Window) EditorStateChanged(old, new editorState) {}
|
||||
|
||||
// close the window.
|
||||
func (w *x11Window) close() {
|
||||
var xev C.XEvent
|
||||
ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
|
||||
*ev = C.XClientMessageEvent{
|
||||
_type: C.ClientMessage,
|
||||
display: w.x,
|
||||
window: w.xw,
|
||||
message_type: w.atom("WM_PROTOCOLS", true),
|
||||
format: 32,
|
||||
}
|
||||
arr := (*[5]C.long)(unsafe.Pointer(&ev.data))
|
||||
arr[0] = C.long(w.atoms.evDelWindow)
|
||||
arr[1] = C.CurrentTime
|
||||
C.XSendEvent(w.x, w.xw, C.False, C.NoEventMask, &xev)
|
||||
}
|
||||
|
||||
// action is one of _NET_WM_STATE_REMOVE, _NET_WM_STATE_ADD.
|
||||
func (w *x11Window) sendWMStateEvent(action C.long, atom1, atom2 C.ulong) {
|
||||
var xev C.XEvent
|
||||
ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
|
||||
*ev = C.XClientMessageEvent{
|
||||
_type: C.ClientMessage,
|
||||
display: w.x,
|
||||
window: w.xw,
|
||||
message_type: w.atoms.wmState,
|
||||
format: 32,
|
||||
}
|
||||
data := (*[5]C.long)(unsafe.Pointer(&ev.data))
|
||||
data[0] = C.long(action)
|
||||
data[1] = C.long(atom1)
|
||||
data[2] = C.long(atom2)
|
||||
data[3] = 1 // application
|
||||
|
||||
C.XSendEvent(
|
||||
w.x,
|
||||
C.XDefaultRootWindow(w.x), // MUST be the root window
|
||||
C.False,
|
||||
C.SubstructureNotifyMask|C.SubstructureRedirectMask,
|
||||
&xev,
|
||||
)
|
||||
}
|
||||
|
||||
var x11OneByte = make([]byte, 1)
|
||||
|
||||
func (w *x11Window) ProcessEvent(e event.Event) {
|
||||
w.w.ProcessEvent(e)
|
||||
}
|
||||
|
||||
func (w *x11Window) shutdown(err error) {
|
||||
w.ProcessEvent(X11ViewEvent{})
|
||||
w.ProcessEvent(DestroyEvent{Err: err})
|
||||
w.destroy()
|
||||
}
|
||||
|
||||
func (w *x11Window) Event() event.Event {
|
||||
for {
|
||||
evt, ok := w.w.nextEvent()
|
||||
if !ok {
|
||||
w.dispatch()
|
||||
continue
|
||||
}
|
||||
return evt
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) Run(f func()) {
|
||||
f()
|
||||
}
|
||||
|
||||
func (w *x11Window) Frame(frame *op.Ops) {
|
||||
w.w.ProcessFrame(frame, nil)
|
||||
}
|
||||
|
||||
func (w *x11Window) Invalidate() {
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
if _, err := syscall.Write(w.notify.write, x11OneByte); err != nil && err != syscall.EAGAIN {
|
||||
panic(fmt.Errorf("failed to write to pipe: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) display() *C.Display {
|
||||
return w.x
|
||||
}
|
||||
|
||||
func (w *x11Window) window() (C.Window, int, int) {
|
||||
return w.xw, w.config.Size.X, w.config.Size.Y
|
||||
}
|
||||
|
||||
func (w *x11Window) dispatch() {
|
||||
if w.x == nil {
|
||||
// Only Invalidate can wake us up.
|
||||
<-w.wakeups
|
||||
w.w.Invalidate()
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-w.wakeups:
|
||||
w.w.Invalidate()
|
||||
default:
|
||||
}
|
||||
|
||||
xfd := C.XConnectionNumber(w.x)
|
||||
|
||||
// Poll for events and notifications.
|
||||
pollfds := []syscall.PollFd{
|
||||
{Fd: int32(xfd), Events: syscall.POLLIN | syscall.POLLERR},
|
||||
{Fd: int32(w.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
|
||||
}
|
||||
xEvents := &pollfds[0].Revents
|
||||
// Plenty of room for a backlog of notifications.
|
||||
|
||||
var syn, anim bool
|
||||
// Check for pending draw events before checking animation or blocking.
|
||||
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
||||
// poll will still block. This also prevents no-op calls to poll.
|
||||
syn = w.handler.handleEvents()
|
||||
if w.x == nil {
|
||||
// handleEvents received a close request and destroyed the window.
|
||||
return
|
||||
}
|
||||
if !syn {
|
||||
anim = w.animating
|
||||
if !anim {
|
||||
// Clear poll events.
|
||||
*xEvents = 0
|
||||
// Wait for X event or gio notification.
|
||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
||||
}
|
||||
switch {
|
||||
case *xEvents&syscall.POLLIN != 0:
|
||||
syn = w.handler.handleEvents()
|
||||
if w.x == nil {
|
||||
return
|
||||
}
|
||||
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clear notifications.
|
||||
for {
|
||||
_, err := syscall.Read(w.notify.read, w.buf[:])
|
||||
if err == syscall.EAGAIN {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
||||
}
|
||||
}
|
||||
if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: w.config.Size,
|
||||
Metric: w.metric,
|
||||
},
|
||||
Sync: syn,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) destroy() {
|
||||
if w.notify.write != 0 {
|
||||
syscall.Close(w.notify.write)
|
||||
w.notify.write = 0
|
||||
}
|
||||
if w.notify.read != 0 {
|
||||
syscall.Close(w.notify.read)
|
||||
w.notify.read = 0
|
||||
}
|
||||
if w.xkb != nil {
|
||||
w.xkb.Destroy()
|
||||
w.xkb = nil
|
||||
}
|
||||
C.XDestroyWindow(w.x, w.xw)
|
||||
C.XCloseDisplay(w.x)
|
||||
w.x = nil
|
||||
}
|
||||
|
||||
// atom is a wrapper around XInternAtom. Callers should cache the result
|
||||
// in order to limit round-trips to the X server.
|
||||
func (w *x11Window) atom(name string, onlyIfExists bool) C.Atom {
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
flag := C.Bool(C.False)
|
||||
if onlyIfExists {
|
||||
flag = C.True
|
||||
}
|
||||
return C.XInternAtom(w.x, cname, flag)
|
||||
}
|
||||
|
||||
// x11EventHandler wraps static variables for the main event loop.
|
||||
// Its sole purpose is to prevent heap allocation and reduce clutter
|
||||
// in x11window.loop.
|
||||
type x11EventHandler struct {
|
||||
w *x11Window
|
||||
text []byte
|
||||
xev *C.XEvent
|
||||
}
|
||||
|
||||
// handleEvents returns true if the window needs to be redrawn.
|
||||
func (h *x11EventHandler) handleEvents() bool {
|
||||
w := h.w
|
||||
xev := h.xev
|
||||
redraw := false
|
||||
for C.XPending(w.x) != 0 {
|
||||
C.XNextEvent(w.x, xev)
|
||||
if C.XFilterEvent(xev, C.None) == C.True {
|
||||
continue
|
||||
}
|
||||
switch _type := (*C.XAnyEvent)(unsafe.Pointer(xev))._type; _type {
|
||||
case h.w.xkbEventBase:
|
||||
xkbEvent := (*C.XkbAnyEvent)(unsafe.Pointer(xev))
|
||||
switch xkbEvent.xkb_type {
|
||||
case C.XkbNewKeyboardNotify, C.XkbMapNotify:
|
||||
if err := h.w.updateXkbKeymap(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case C.XkbStateNotify:
|
||||
state := (*C.XkbStateNotifyEvent)(unsafe.Pointer(xev))
|
||||
h.w.xkb.UpdateMask(uint32(state.base_mods), uint32(state.latched_mods), uint32(state.locked_mods),
|
||||
uint32(state.base_group), uint32(state.latched_group), uint32(state.locked_group))
|
||||
}
|
||||
case C.KeyPress, C.KeyRelease:
|
||||
ks := key.Press
|
||||
if _type == C.KeyRelease {
|
||||
ks = key.Release
|
||||
}
|
||||
kevt := (*C.XKeyPressedEvent)(unsafe.Pointer(xev))
|
||||
for _, e := range h.w.xkb.DispatchKey(uint32(kevt.keycode), ks) {
|
||||
if ee, ok := e.(key.EditEvent); ok {
|
||||
// There's no support for IME yet.
|
||||
w.w.EditorInsert(ee.Text)
|
||||
} else {
|
||||
w.ProcessEvent(e)
|
||||
}
|
||||
}
|
||||
case C.ButtonPress, C.ButtonRelease:
|
||||
bevt := (*C.XButtonEvent)(unsafe.Pointer(xev))
|
||||
ev := pointer.Event{
|
||||
Kind: pointer.Press,
|
||||
Source: pointer.Mouse,
|
||||
Position: f32.Point{
|
||||
X: float32(bevt.x),
|
||||
Y: float32(bevt.y),
|
||||
},
|
||||
Time: time.Duration(bevt.time) * time.Millisecond,
|
||||
Modifiers: w.xkb.Modifiers(),
|
||||
}
|
||||
if bevt._type == C.ButtonRelease {
|
||||
ev.Kind = pointer.Release
|
||||
}
|
||||
var btn pointer.Buttons
|
||||
const scrollScale = 10
|
||||
switch bevt.button {
|
||||
case C.Button1:
|
||||
btn = pointer.ButtonPrimary
|
||||
case C.Button2:
|
||||
btn = pointer.ButtonTertiary
|
||||
case C.Button3:
|
||||
btn = pointer.ButtonSecondary
|
||||
case C.Button4:
|
||||
ev.Kind = pointer.Scroll
|
||||
// scroll up or left (if shift is pressed).
|
||||
if ev.Modifiers == key.ModShift {
|
||||
ev.Scroll.X = -scrollScale
|
||||
} else {
|
||||
ev.Scroll.Y = -scrollScale
|
||||
}
|
||||
case C.Button5:
|
||||
// scroll down or right (if shift is pressed).
|
||||
ev.Kind = pointer.Scroll
|
||||
if ev.Modifiers == key.ModShift {
|
||||
ev.Scroll.X = +scrollScale
|
||||
} else {
|
||||
ev.Scroll.Y = +scrollScale
|
||||
}
|
||||
case 6:
|
||||
// http://xahlee.info/linux/linux_x11_mouse_button_number.html
|
||||
// scroll left.
|
||||
ev.Kind = pointer.Scroll
|
||||
ev.Scroll.X = -scrollScale * 2
|
||||
case 7:
|
||||
// scroll right
|
||||
ev.Kind = pointer.Scroll
|
||||
ev.Scroll.X = +scrollScale * 2
|
||||
default:
|
||||
continue
|
||||
}
|
||||
switch _type {
|
||||
case C.ButtonPress:
|
||||
w.pointerBtns |= btn
|
||||
case C.ButtonRelease:
|
||||
w.pointerBtns &^= btn
|
||||
}
|
||||
ev.Buttons = w.pointerBtns
|
||||
w.ProcessEvent(ev)
|
||||
case C.MotionNotify:
|
||||
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
Source: pointer.Mouse,
|
||||
Buttons: w.pointerBtns,
|
||||
Position: f32.Point{
|
||||
X: float32(mevt.x),
|
||||
Y: float32(mevt.y),
|
||||
},
|
||||
Time: time.Duration(mevt.time) * time.Millisecond,
|
||||
Modifiers: w.xkb.Modifiers(),
|
||||
})
|
||||
case C.Expose: // update
|
||||
// redraw only on the last expose event
|
||||
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
||||
case C.FocusIn:
|
||||
w.config.Focused = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
case C.FocusOut:
|
||||
w.config.Focused = false
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
case C.ConfigureNotify: // window configuration change
|
||||
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
||||
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
|
||||
w.config.Size = sz
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
// redraw will be done by a later expose event
|
||||
case C.SelectionNotify:
|
||||
cevt := (*C.XSelectionEvent)(unsafe.Pointer(xev))
|
||||
prop := w.atoms.clipboardContent
|
||||
if cevt.property != prop {
|
||||
break
|
||||
}
|
||||
if cevt.selection != w.atoms.clipboard {
|
||||
break
|
||||
}
|
||||
var text C.XTextProperty
|
||||
if st := C.XGetTextProperty(w.x, w.xw, &text, prop); st == 0 {
|
||||
// Failed; ignore.
|
||||
break
|
||||
}
|
||||
if text.format != 8 || text.encoding != w.atoms.utf8string {
|
||||
// Ignore non-utf-8 encoded strings.
|
||||
break
|
||||
}
|
||||
str := C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.nitems))
|
||||
w.ProcessEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(str))
|
||||
},
|
||||
})
|
||||
case C.SelectionRequest:
|
||||
cevt := (*C.XSelectionRequestEvent)(unsafe.Pointer(xev))
|
||||
if (cevt.selection != w.atoms.clipboard && cevt.selection != w.atoms.primary) || cevt.property == C.None {
|
||||
// Unsupported clipboard or obsolete requestor.
|
||||
break
|
||||
}
|
||||
notify := func() {
|
||||
var xev C.XEvent
|
||||
ev := (*C.XSelectionEvent)(unsafe.Pointer(&xev))
|
||||
*ev = C.XSelectionEvent{
|
||||
_type: C.SelectionNotify,
|
||||
display: cevt.display,
|
||||
requestor: cevt.requestor,
|
||||
selection: cevt.selection,
|
||||
target: cevt.target,
|
||||
property: cevt.property,
|
||||
time: cevt.time,
|
||||
}
|
||||
C.XSendEvent(w.x, cevt.requestor, 0, 0, &xev)
|
||||
}
|
||||
switch cevt.target {
|
||||
case w.atoms.targets:
|
||||
// The requestor wants the supported clipboard
|
||||
// formats. First write the targets...
|
||||
formats := [...]C.long{
|
||||
C.long(w.atoms.targets),
|
||||
C.long(w.atoms.utf8string),
|
||||
C.long(w.atoms.plaintext),
|
||||
// GTK clients need this.
|
||||
C.long(w.atoms.gtk_text_buffer_contents),
|
||||
}
|
||||
C.XChangeProperty(w.x, cevt.requestor, cevt.property, w.atoms.atom,
|
||||
32 /* bitwidth of formats */, C.PropModeReplace,
|
||||
(*C.uchar)(unsafe.Pointer(&formats)), C.int(len(formats)),
|
||||
)
|
||||
// ...then notify the requestor.
|
||||
notify()
|
||||
case w.atoms.plaintext, w.atoms.utf8string, w.atoms.gtk_text_buffer_contents:
|
||||
content := w.clipboard.content
|
||||
var ptr *C.uchar
|
||||
if len(content) > 0 {
|
||||
ptr = (*C.uchar)(unsafe.Pointer(&content[0]))
|
||||
}
|
||||
C.XChangeProperty(w.x, cevt.requestor, cevt.property, cevt.target,
|
||||
8 /* bitwidth */, C.PropModeReplace,
|
||||
ptr, C.int(len(content)),
|
||||
)
|
||||
notify()
|
||||
}
|
||||
case C.ClientMessage: // extensions
|
||||
cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
|
||||
switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
|
||||
case C.long(w.atoms.evDelWindow):
|
||||
w.shutdown(nil)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return redraw
|
||||
}
|
||||
|
||||
var x11Threads sync.Once
|
||||
|
||||
func init() {
|
||||
x11Driver = newX11Window
|
||||
}
|
||||
|
||||
func newX11Window(gioWin *callbacks, options []Option) error {
|
||||
var err error
|
||||
|
||||
pipe := make([]int, 2)
|
||||
if err := syscall.Pipe2(pipe, syscall.O_NONBLOCK|syscall.O_CLOEXEC); err != nil {
|
||||
return fmt.Errorf("NewX11Window: failed to create pipe: %w", err)
|
||||
}
|
||||
|
||||
x11Threads.Do(func() {
|
||||
if C.XInitThreads() == 0 {
|
||||
err = errors.New("x11: threads init failed")
|
||||
}
|
||||
C.XrmInitialize()
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dpy := C.XOpenDisplay(nil)
|
||||
if dpy == nil {
|
||||
return errors.New("x11: cannot connect to the X server")
|
||||
}
|
||||
var major, minor C.int = C.XkbMajorVersion, C.XkbMinorVersion
|
||||
var xkbEventBase C.int
|
||||
if C.XkbQueryExtension(dpy, nil, &xkbEventBase, nil, &major, &minor) != C.True {
|
||||
C.XCloseDisplay(dpy)
|
||||
return errors.New("x11: XkbQueryExtension failed")
|
||||
}
|
||||
const bits = C.uint(C.XkbNewKeyboardNotifyMask | C.XkbMapNotifyMask | C.XkbStateNotifyMask)
|
||||
if C.XkbSelectEvents(dpy, C.XkbUseCoreKbd, bits, bits) != C.True {
|
||||
C.XCloseDisplay(dpy)
|
||||
return errors.New("x11: XkbSelectEvents failed")
|
||||
}
|
||||
xkb, err := xkb.New()
|
||||
if err != nil {
|
||||
C.XCloseDisplay(dpy)
|
||||
return fmt.Errorf("x11: %v", err)
|
||||
}
|
||||
|
||||
ppsp := x11DetectUIScale(dpy)
|
||||
cfg := unit.Metric{PxPerDp: ppsp, PxPerSp: ppsp}
|
||||
// Only use cnf for getting the window size.
|
||||
var cnf Config
|
||||
cnf.apply(cfg, options)
|
||||
|
||||
swa := C.XSetWindowAttributes{
|
||||
event_mask: C.ExposureMask | C.FocusChangeMask | // update
|
||||
C.KeyPressMask | C.KeyReleaseMask | // keyboard
|
||||
C.ButtonPressMask | C.ButtonReleaseMask | // mouse clicks
|
||||
C.PointerMotionMask | // mouse movement
|
||||
C.StructureNotifyMask, // resize
|
||||
background_pixmap: C.None,
|
||||
override_redirect: C.False,
|
||||
}
|
||||
win := C.XCreateWindow(dpy, C.XDefaultRootWindow(dpy),
|
||||
0, 0, C.uint(cnf.Size.X), C.uint(cnf.Size.Y),
|
||||
0, C.CopyFromParent, C.InputOutput, nil,
|
||||
C.CWEventMask|C.CWBackPixmap|C.CWOverrideRedirect, &swa)
|
||||
|
||||
w := &x11Window{
|
||||
w: gioWin, x: dpy, xw: win,
|
||||
metric: cfg,
|
||||
xkb: xkb,
|
||||
xkbEventBase: xkbEventBase,
|
||||
wakeups: make(chan struct{}, 1),
|
||||
config: Config{Size: cnf.Size},
|
||||
}
|
||||
w.handler = x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
||||
w.notify.read = pipe[0]
|
||||
w.notify.write = pipe[1]
|
||||
w.w.SetDriver(w)
|
||||
|
||||
if err := w.updateXkbKeymap(); err != nil {
|
||||
w.destroy()
|
||||
return err
|
||||
}
|
||||
|
||||
var hints C.XWMHints
|
||||
hints.input = C.True
|
||||
hints.flags = C.InputHint
|
||||
C.XSetWMHints(dpy, win, &hints)
|
||||
|
||||
name := C.CString(ID)
|
||||
defer C.free(unsafe.Pointer(name))
|
||||
wmhints := C.XClassHint{name, name}
|
||||
C.XSetClassHint(dpy, win, &wmhints)
|
||||
|
||||
w.atoms.utf8string = w.atom("UTF8_STRING", false)
|
||||
w.atoms.plaintext = w.atom("text/plain;charset=utf-8", false)
|
||||
w.atoms.gtk_text_buffer_contents = w.atom("GTK_TEXT_BUFFER_CONTENTS", false)
|
||||
w.atoms.evDelWindow = w.atom("WM_DELETE_WINDOW", false)
|
||||
w.atoms.clipboard = w.atom("CLIPBOARD", false)
|
||||
w.atoms.primary = w.atom("PRIMARY", false)
|
||||
w.atoms.clipboardContent = w.atom("CLIPBOARD_CONTENT", false)
|
||||
w.atoms.atom = w.atom("ATOM", false)
|
||||
w.atoms.targets = w.atom("TARGETS", false)
|
||||
w.atoms.wmName = w.atom("_NET_WM_NAME", false)
|
||||
w.atoms.wmState = w.atom("_NET_WM_STATE", false)
|
||||
w.atoms.wmStateFullscreen = w.atom("_NET_WM_STATE_FULLSCREEN", false)
|
||||
w.atoms.wmActiveWindow = w.atom("_NET_ACTIVE_WINDOW", false)
|
||||
w.atoms.wmStateMaximizedHorz = w.atom("_NET_WM_STATE_MAXIMIZED_HORZ", false)
|
||||
w.atoms.wmStateMaximizedVert = w.atom("_NET_WM_STATE_MAXIMIZED_VERT", false)
|
||||
|
||||
// extensions
|
||||
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
|
||||
|
||||
// make the window visible on the screen
|
||||
C.XMapWindow(dpy, win)
|
||||
w.Configure(options)
|
||||
w.ProcessEvent(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
||||
return nil
|
||||
}
|
||||
|
||||
// detectUIScale reports the system UI scale, or 1.0 if it fails.
|
||||
func x11DetectUIScale(dpy *C.Display) float32 {
|
||||
// default fixed DPI value used in most desktop UI toolkits
|
||||
const defaultDesktopDPI = 96
|
||||
var scale float32 = 1.0
|
||||
|
||||
// Get actual DPI from X resource Xft.dpi (set by GTK and Qt).
|
||||
// This value is entirely based on user preferences and conflates both
|
||||
// screen (UI) scaling and font scale.
|
||||
rms := C.XResourceManagerString(dpy)
|
||||
if rms != nil {
|
||||
db := C.XrmGetStringDatabase(rms)
|
||||
if db != nil {
|
||||
var (
|
||||
t *C.char
|
||||
v C.XrmValue
|
||||
)
|
||||
if C.XrmGetResource(db, (*C.char)(unsafe.Pointer(&[]byte("Xft.dpi\x00")[0])),
|
||||
(*C.char)(unsafe.Pointer(&[]byte("Xft.Dpi\x00")[0])), &t, &v) != C.False {
|
||||
if t != nil && C.GoString(t) == "String" {
|
||||
f, err := strconv.ParseFloat(C.GoString(v.addr), 32)
|
||||
if err == nil {
|
||||
scale = float32(f) / defaultDesktopDPI
|
||||
}
|
||||
}
|
||||
}
|
||||
C.XrmDestroyDatabase(db)
|
||||
}
|
||||
}
|
||||
|
||||
return scale
|
||||
}
|
||||
|
||||
func (w *x11Window) updateXkbKeymap() error {
|
||||
w.xkb.DestroyKeymapState()
|
||||
ctx := (*C.struct_xkb_context)(unsafe.Pointer(w.xkb.Ctx))
|
||||
xcb := C.XGetXCBConnection(w.x)
|
||||
if xcb == nil {
|
||||
return errors.New("x11: XGetXCBConnection failed")
|
||||
}
|
||||
xkbDevID := C.xkb_x11_get_core_keyboard_device_id(xcb)
|
||||
if xkbDevID == -1 {
|
||||
return errors.New("x11: xkb_x11_get_core_keyboard_device_id failed")
|
||||
}
|
||||
keymap := C.xkb_x11_keymap_new_from_device(ctx, xcb, xkbDevID, C.XKB_KEYMAP_COMPILE_NO_FLAGS)
|
||||
if keymap == nil {
|
||||
return errors.New("x11: xkb_x11_keymap_new_from_device failed")
|
||||
}
|
||||
state := C.xkb_x11_state_new_from_device(keymap, xcb, xkbDevID)
|
||||
if state == nil {
|
||||
C.xkb_keymap_unref(keymap)
|
||||
return errors.New("x11: xkb_x11_keymap_new_from_device failed")
|
||||
}
|
||||
w.xkb.SetKeymap(unsafe.Pointer(keymap), unsafe.Pointer(state))
|
||||
return nil
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build android || (darwin && ios)
|
||||
|
||||
package app
|
||||
|
||||
// Android only supports non-Java programs as c-shared libraries.
|
||||
// Unfortunately, Go does not run a program's main function in
|
||||
// library mode. To make Gio programs simpler and uniform, we'll
|
||||
// link to the main function here and call it from Java.
|
||||
|
||||
import (
|
||||
"sync"
|
||||
_ "unsafe" // for go:linkname
|
||||
)
|
||||
|
||||
//go:linkname mainMain main.main
|
||||
func mainMain()
|
||||
|
||||
var runMainOnce sync.Once
|
||||
|
||||
func runMain() {
|
||||
runMainOnce.Do(func() {
|
||||
// Indirect call, since the linker does not know the address of main when
|
||||
// laying down this package.
|
||||
fn := mainMain
|
||||
go fn()
|
||||
})
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
// DestroyEvent is the last event sent through
|
||||
// a window event channel.
|
||||
type DestroyEvent struct {
|
||||
// Err is nil for normal window closures. If a
|
||||
// window is prematurely closed, Err is the cause.
|
||||
Err error
|
||||
}
|
||||
|
||||
func (DestroyEvent) ImplementsEvent() {}
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build (linux || freebsd) && !novulkan
|
||||
// +build linux freebsd
|
||||
// +build !novulkan
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/vk"
|
||||
)
|
||||
|
||||
type vkContext struct {
|
||||
physDev vk.PhysicalDevice
|
||||
inst vk.Instance
|
||||
dev vk.Device
|
||||
queueFam int
|
||||
queue vk.Queue
|
||||
acquireSem vk.Semaphore
|
||||
presentSem vk.Semaphore
|
||||
fence vk.Fence
|
||||
|
||||
swchain vk.Swapchain
|
||||
imgs []vk.Image
|
||||
views []vk.ImageView
|
||||
fbos []vk.Framebuffer
|
||||
format vk.Format
|
||||
presentIdx int
|
||||
}
|
||||
|
||||
func newVulkanContext(inst vk.Instance, surf vk.Surface) (*vkContext, error) {
|
||||
physDev, qFam, err := vk.ChoosePhysicalDevice(inst, surf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dev, err := vk.CreateDeviceAndQueue(physDev, qFam, "VK_KHR_swapchain")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
acquireSem, err := vk.CreateSemaphore(dev)
|
||||
if err != nil {
|
||||
vk.DestroyDevice(dev)
|
||||
return nil, err
|
||||
}
|
||||
presentSem, err := vk.CreateSemaphore(dev)
|
||||
if err != nil {
|
||||
vk.DestroySemaphore(dev, acquireSem)
|
||||
vk.DestroyDevice(dev)
|
||||
return nil, err
|
||||
}
|
||||
fence, err := vk.CreateFence(dev, vk.FENCE_CREATE_SIGNALED_BIT)
|
||||
if err != nil {
|
||||
vk.DestroySemaphore(dev, presentSem)
|
||||
vk.DestroySemaphore(dev, acquireSem)
|
||||
vk.DestroyDevice(dev)
|
||||
return nil, err
|
||||
}
|
||||
c := &vkContext{
|
||||
physDev: physDev,
|
||||
inst: inst,
|
||||
dev: dev,
|
||||
queueFam: qFam,
|
||||
queue: vk.GetDeviceQueue(dev, qFam, 0),
|
||||
acquireSem: acquireSem,
|
||||
presentSem: presentSem,
|
||||
fence: fence,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *vkContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
vk.WaitForFences(c.dev, c.fence)
|
||||
vk.ResetFences(c.dev, c.fence)
|
||||
|
||||
imgIdx, err := vk.AcquireNextImage(c.dev, c.swchain, c.acquireSem, 0)
|
||||
if err := mapSurfaceErr(err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.presentIdx = imgIdx
|
||||
return gpu.VulkanRenderTarget{
|
||||
WaitSem: uint64(c.acquireSem),
|
||||
SignalSem: uint64(c.presentSem),
|
||||
Fence: uint64(c.fence),
|
||||
Framebuffer: uint64(c.fbos[imgIdx]),
|
||||
Image: uint64(c.imgs[imgIdx]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *vkContext) api() gpu.API {
|
||||
return gpu.Vulkan{
|
||||
PhysDevice: unsafe.Pointer(c.physDev),
|
||||
Device: unsafe.Pointer(c.dev),
|
||||
Format: int(c.format),
|
||||
QueueFamily: c.queueFam,
|
||||
QueueIndex: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func mapErr(err error) error {
|
||||
var vkErr vk.Error
|
||||
if errors.As(err, &vkErr) && vkErr == vk.ERROR_DEVICE_LOST {
|
||||
return gpu.ErrDeviceLost
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func mapSurfaceErr(err error) error {
|
||||
var vkErr vk.Error
|
||||
if !errors.As(err, &vkErr) {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case vkErr == vk.SUBOPTIMAL_KHR:
|
||||
// Android reports VK_SUBOPTIMAL_KHR when presenting to a rotated
|
||||
// swapchain (preTransform != currentTransform). However, we don't
|
||||
// support transforming the output ourselves, so we'll live with it.
|
||||
return nil
|
||||
case vkErr == vk.ERROR_OUT_OF_DATE_KHR:
|
||||
return errOutOfDate
|
||||
case vkErr == vk.ERROR_SURFACE_LOST_KHR:
|
||||
// Treating a lost surface as a lost device isn't accurate, but
|
||||
// probably not worth optimizing.
|
||||
return gpu.ErrDeviceLost
|
||||
}
|
||||
return mapErr(err)
|
||||
}
|
||||
|
||||
func (c *vkContext) release() {
|
||||
vk.DeviceWaitIdle(c.dev)
|
||||
|
||||
c.destroySwapchain()
|
||||
vk.DestroyFence(c.dev, c.fence)
|
||||
vk.DestroySemaphore(c.dev, c.acquireSem)
|
||||
vk.DestroySemaphore(c.dev, c.presentSem)
|
||||
vk.DestroyDevice(c.dev)
|
||||
*c = vkContext{}
|
||||
}
|
||||
|
||||
func (c *vkContext) present() error {
|
||||
return mapSurfaceErr(vk.PresentQueue(c.queue, c.swchain, c.presentSem, c.presentIdx))
|
||||
}
|
||||
|
||||
func (c *vkContext) destroyImageViews() {
|
||||
for _, f := range c.fbos {
|
||||
vk.DestroyFramebuffer(c.dev, f)
|
||||
}
|
||||
c.fbos = nil
|
||||
for _, view := range c.views {
|
||||
vk.DestroyImageView(c.dev, view)
|
||||
}
|
||||
c.views = nil
|
||||
}
|
||||
|
||||
func (c *vkContext) destroySwapchain() {
|
||||
vk.DeviceWaitIdle(c.dev)
|
||||
|
||||
c.destroyImageViews()
|
||||
if c.swchain != 0 {
|
||||
vk.DestroySwapchain(c.dev, c.swchain)
|
||||
c.swchain = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (c *vkContext) refresh(surf vk.Surface, width, height int) error {
|
||||
vk.DeviceWaitIdle(c.dev)
|
||||
|
||||
c.destroyImageViews()
|
||||
// Check whether size is valid. That's needed on X11, where ConfigureNotify
|
||||
// is not always synchronized with the window extent.
|
||||
caps, err := vk.GetPhysicalDeviceSurfaceCapabilities(c.physDev, surf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
minExt, maxExt := vk.SurfaceCapabilitiesMinExtent(caps), vk.SurfaceCapabilitiesMaxExtent(caps)
|
||||
if width < minExt.X || maxExt.X < width || height < minExt.Y || maxExt.Y < height {
|
||||
return errOutOfDate
|
||||
}
|
||||
swchain, imgs, format, err := vk.CreateSwapchain(c.physDev, c.dev, surf, width, height, c.swchain)
|
||||
if c.swchain != 0 {
|
||||
vk.DestroySwapchain(c.dev, c.swchain)
|
||||
c.swchain = 0
|
||||
}
|
||||
if err := mapSurfaceErr(err); err != nil {
|
||||
return err
|
||||
}
|
||||
c.swchain = swchain
|
||||
c.imgs = imgs
|
||||
c.format = format
|
||||
pass, err := vk.CreateRenderPass(
|
||||
c.dev,
|
||||
format,
|
||||
vk.ATTACHMENT_LOAD_OP_CLEAR,
|
||||
vk.IMAGE_LAYOUT_UNDEFINED,
|
||||
vk.IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||
nil,
|
||||
)
|
||||
if err := mapErr(err); err != nil {
|
||||
return err
|
||||
}
|
||||
defer vk.DestroyRenderPass(c.dev, pass)
|
||||
for _, img := range imgs {
|
||||
view, err := vk.CreateImageView(c.dev, img, format)
|
||||
if err := mapErr(err); err != nil {
|
||||
return err
|
||||
}
|
||||
c.views = append(c.views, view)
|
||||
fbo, err := vk.CreateFramebuffer(c.dev, pass, view, width, height)
|
||||
if err := mapErr(err); err != nil {
|
||||
return err
|
||||
}
|
||||
c.fbos = append(c.fbos, fbo)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !novulkan
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/vk"
|
||||
)
|
||||
|
||||
type wlVkContext struct {
|
||||
win *window
|
||||
inst vk.Instance
|
||||
surf vk.Surface
|
||||
ctx *vkContext
|
||||
}
|
||||
|
||||
func init() {
|
||||
newAndroidVulkanContext = func(w *window) (context, error) {
|
||||
inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_android_surface")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
window, _, _ := w.nativeWindow()
|
||||
surf, err := vk.CreateAndroidSurface(inst, unsafe.Pointer(window))
|
||||
if err != nil {
|
||||
vk.DestroyInstance(inst)
|
||||
return nil, err
|
||||
}
|
||||
ctx, err := newVulkanContext(inst, surf)
|
||||
if err != nil {
|
||||
vk.DestroySurface(inst, surf)
|
||||
vk.DestroyInstance(inst)
|
||||
return nil, err
|
||||
}
|
||||
c := &wlVkContext{
|
||||
win: w,
|
||||
inst: inst,
|
||||
surf: surf,
|
||||
ctx: ctx,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *wlVkContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
return c.ctx.RenderTarget()
|
||||
}
|
||||
|
||||
func (c *wlVkContext) API() gpu.API {
|
||||
return c.ctx.api()
|
||||
}
|
||||
|
||||
func (c *wlVkContext) Release() {
|
||||
c.ctx.release()
|
||||
if c.surf != 0 {
|
||||
vk.DestroySurface(c.inst, c.surf)
|
||||
}
|
||||
vk.DestroyInstance(c.inst)
|
||||
*c = wlVkContext{}
|
||||
}
|
||||
|
||||
func (c *wlVkContext) Present() error {
|
||||
return c.ctx.present()
|
||||
}
|
||||
|
||||
func (c *wlVkContext) Lock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *wlVkContext) Unlock() {}
|
||||
|
||||
func (c *wlVkContext) Refresh() error {
|
||||
win, w, h := c.win.nativeWindow()
|
||||
if c.surf != 0 {
|
||||
c.ctx.destroySwapchain()
|
||||
vk.DestroySurface(c.inst, c.surf)
|
||||
c.surf = 0
|
||||
}
|
||||
surf, err := vk.CreateAndroidSurface(c.inst, unsafe.Pointer(win))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.surf = surf
|
||||
return c.ctx.refresh(c.surf, w, h)
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build ((linux && !android) || freebsd) && !nowayland && !novulkan
|
||||
// +build linux,!android freebsd
|
||||
// +build !nowayland
|
||||
// +build !novulkan
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/vk"
|
||||
)
|
||||
|
||||
type wlVkContext struct {
|
||||
win *window
|
||||
inst vk.Instance
|
||||
surf vk.Surface
|
||||
ctx *vkContext
|
||||
}
|
||||
|
||||
func init() {
|
||||
newWaylandVulkanContext = func(w *window) (context, error) {
|
||||
inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_wayland_surface")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disp := w.display()
|
||||
wlSurf, _, _ := w.surface()
|
||||
surf, err := vk.CreateWaylandSurface(inst, unsafe.Pointer(disp), unsafe.Pointer(wlSurf))
|
||||
if err != nil {
|
||||
vk.DestroyInstance(inst)
|
||||
return nil, err
|
||||
}
|
||||
ctx, err := newVulkanContext(inst, surf)
|
||||
if err != nil {
|
||||
vk.DestroySurface(inst, surf)
|
||||
vk.DestroyInstance(inst)
|
||||
return nil, err
|
||||
}
|
||||
c := &wlVkContext{
|
||||
win: w,
|
||||
inst: inst,
|
||||
surf: surf,
|
||||
ctx: ctx,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *wlVkContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
return c.ctx.RenderTarget()
|
||||
}
|
||||
|
||||
func (c *wlVkContext) API() gpu.API {
|
||||
return c.ctx.api()
|
||||
}
|
||||
|
||||
func (c *wlVkContext) Release() {
|
||||
c.ctx.release()
|
||||
vk.DestroySurface(c.inst, c.surf)
|
||||
vk.DestroyInstance(c.inst)
|
||||
*c = wlVkContext{}
|
||||
}
|
||||
|
||||
func (c *wlVkContext) Present() error {
|
||||
return c.ctx.present()
|
||||
}
|
||||
|
||||
func (c *wlVkContext) Lock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *wlVkContext) Unlock() {}
|
||||
|
||||
func (c *wlVkContext) Refresh() error {
|
||||
_, w, h := c.win.surface()
|
||||
return c.ctx.refresh(c.surf, w, h)
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build ((linux && !android) || freebsd) && !nox11 && !novulkan
|
||||
// +build linux,!android freebsd
|
||||
// +build !nox11
|
||||
// +build !novulkan
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/vk"
|
||||
)
|
||||
|
||||
type x11VkContext struct {
|
||||
win *x11Window
|
||||
inst vk.Instance
|
||||
surf vk.Surface
|
||||
ctx *vkContext
|
||||
}
|
||||
|
||||
func init() {
|
||||
newX11VulkanContext = func(w *x11Window) (context, error) {
|
||||
inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_xlib_surface")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disp := w.display()
|
||||
window, _, _ := w.window()
|
||||
surf, err := vk.CreateXlibSurface(inst, unsafe.Pointer(disp), uintptr(window))
|
||||
if err != nil {
|
||||
vk.DestroyInstance(inst)
|
||||
return nil, err
|
||||
}
|
||||
ctx, err := newVulkanContext(inst, surf)
|
||||
if err != nil {
|
||||
vk.DestroySurface(inst, surf)
|
||||
vk.DestroyInstance(inst)
|
||||
return nil, err
|
||||
}
|
||||
c := &x11VkContext{
|
||||
win: w,
|
||||
inst: inst,
|
||||
surf: surf,
|
||||
ctx: ctx,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *x11VkContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
return c.ctx.RenderTarget()
|
||||
}
|
||||
|
||||
func (c *x11VkContext) API() gpu.API {
|
||||
return c.ctx.api()
|
||||
}
|
||||
|
||||
func (c *x11VkContext) Release() {
|
||||
c.ctx.release()
|
||||
vk.DestroySurface(c.inst, c.surf)
|
||||
vk.DestroyInstance(c.inst)
|
||||
*c = x11VkContext{}
|
||||
}
|
||||
|
||||
func (c *x11VkContext) Present() error {
|
||||
return c.ctx.present()
|
||||
}
|
||||
|
||||
func (c *x11VkContext) Lock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *x11VkContext) Unlock() {}
|
||||
|
||||
func (c *x11VkContext) Refresh() error {
|
||||
_, w, h := c.win.window()
|
||||
return c.ctx.refresh(c.surf, w, h)
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
//go:build ((linux && !android) || freebsd) && !nowayland
|
||||
// +build linux,!android freebsd
|
||||
// +build !nowayland
|
||||
|
||||
/* Generated by wayland-scanner 1.19.0 */
|
||||
|
||||
/*
|
||||
* Copyright © 2012, 2013 Intel Corporation
|
||||
* Copyright © 2015, 2016 Jan Arne Petersen
|
||||
* Copyright © 2017, 2018 Red Hat, Inc.
|
||||
* Copyright © 2018 Purism SPC
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this
|
||||
* software and its documentation for any purpose is hereby granted
|
||||
* without fee, provided that the above copyright notice appear in
|
||||
* all copies and that both that copyright notice and this permission
|
||||
* notice appear in supporting documentation, and that the name of
|
||||
* the copyright holders not be used in advertising or publicity
|
||||
* pertaining to distribution of the software without specific,
|
||||
* written prior permission. The copyright holders make no
|
||||
* representations about the suitability of this software for any
|
||||
* purpose. It is provided "as is" without express or implied
|
||||
* warranty.
|
||||
*
|
||||
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
* THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include "wayland-util.h"
|
||||
|
||||
#ifndef __has_attribute
|
||||
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
|
||||
#endif
|
||||
|
||||
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
|
||||
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
|
||||
#else
|
||||
#define WL_PRIVATE
|
||||
#endif
|
||||
|
||||
extern const struct wl_interface wl_seat_interface;
|
||||
extern const struct wl_interface wl_surface_interface;
|
||||
extern const struct wl_interface zwp_text_input_v3_interface;
|
||||
|
||||
static const struct wl_interface *text_input_unstable_v3_types[] = {
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
&wl_surface_interface,
|
||||
&wl_surface_interface,
|
||||
&zwp_text_input_v3_interface,
|
||||
&wl_seat_interface,
|
||||
};
|
||||
|
||||
static const struct wl_message zwp_text_input_v3_requests[] = {
|
||||
{ "destroy", "", text_input_unstable_v3_types + 0 },
|
||||
{ "enable", "", text_input_unstable_v3_types + 0 },
|
||||
{ "disable", "", text_input_unstable_v3_types + 0 },
|
||||
{ "set_surrounding_text", "sii", text_input_unstable_v3_types + 0 },
|
||||
{ "set_text_change_cause", "u", text_input_unstable_v3_types + 0 },
|
||||
{ "set_content_type", "uu", text_input_unstable_v3_types + 0 },
|
||||
{ "set_cursor_rectangle", "iiii", text_input_unstable_v3_types + 0 },
|
||||
{ "commit", "", text_input_unstable_v3_types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message zwp_text_input_v3_events[] = {
|
||||
{ "enter", "o", text_input_unstable_v3_types + 4 },
|
||||
{ "leave", "o", text_input_unstable_v3_types + 5 },
|
||||
{ "preedit_string", "?sii", text_input_unstable_v3_types + 0 },
|
||||
{ "commit_string", "?s", text_input_unstable_v3_types + 0 },
|
||||
{ "delete_surrounding_text", "uu", text_input_unstable_v3_types + 0 },
|
||||
{ "done", "u", text_input_unstable_v3_types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface zwp_text_input_v3_interface = {
|
||||
"zwp_text_input_v3", 1,
|
||||
8, zwp_text_input_v3_requests,
|
||||
6, zwp_text_input_v3_events,
|
||||
};
|
||||
|
||||
static const struct wl_message zwp_text_input_manager_v3_requests[] = {
|
||||
{ "destroy", "", text_input_unstable_v3_types + 0 },
|
||||
{ "get_text_input", "no", text_input_unstable_v3_types + 6 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface zwp_text_input_manager_v3_interface = {
|
||||
"zwp_text_input_manager_v3", 1,
|
||||
2, zwp_text_input_manager_v3_requests,
|
||||
0, NULL,
|
||||
};
|
||||
|
||||
+836
@@ -0,0 +1,836 @@
|
||||
/* Generated by wayland-scanner 1.19.0 */
|
||||
|
||||
#ifndef TEXT_INPUT_UNSTABLE_V3_CLIENT_PROTOCOL_H
|
||||
#define TEXT_INPUT_UNSTABLE_V3_CLIENT_PROTOCOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "wayland-client.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @page page_text_input_unstable_v3 The text_input_unstable_v3 protocol
|
||||
* Protocol for composing text
|
||||
*
|
||||
* @section page_desc_text_input_unstable_v3 Description
|
||||
*
|
||||
* This protocol allows compositors to act as input methods and to send text
|
||||
* to applications. A text input object is used to manage state of what are
|
||||
* typically text entry fields in the application.
|
||||
*
|
||||
* This document adheres to the RFC 2119 when using words like "must",
|
||||
* "should", "may", etc.
|
||||
*
|
||||
* Warning! The protocol described in this file is experimental and
|
||||
* backward incompatible changes may be made. Backward compatible changes
|
||||
* may be added together with the corresponding interface version bump.
|
||||
* Backward incompatible changes are done by bumping the version number in
|
||||
* the protocol and interface names and resetting the interface version.
|
||||
* Once the protocol is to be declared stable, the 'z' prefix and the
|
||||
* version number in the protocol and interface names are removed and the
|
||||
* interface version number is reset.
|
||||
*
|
||||
* @section page_ifaces_text_input_unstable_v3 Interfaces
|
||||
* - @subpage page_iface_zwp_text_input_v3 - text input
|
||||
* - @subpage page_iface_zwp_text_input_manager_v3 - text input manager
|
||||
* @section page_copyright_text_input_unstable_v3 Copyright
|
||||
* <pre>
|
||||
*
|
||||
* Copyright © 2012, 2013 Intel Corporation
|
||||
* Copyright © 2015, 2016 Jan Arne Petersen
|
||||
* Copyright © 2017, 2018 Red Hat, Inc.
|
||||
* Copyright © 2018 Purism SPC
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this
|
||||
* software and its documentation for any purpose is hereby granted
|
||||
* without fee, provided that the above copyright notice appear in
|
||||
* all copies and that both that copyright notice and this permission
|
||||
* notice appear in supporting documentation, and that the name of
|
||||
* the copyright holders not be used in advertising or publicity
|
||||
* pertaining to distribution of the software without specific,
|
||||
* written prior permission. The copyright holders make no
|
||||
* representations about the suitability of this software for any
|
||||
* purpose. It is provided "as is" without express or implied
|
||||
* warranty.
|
||||
*
|
||||
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
* THIS SOFTWARE.
|
||||
* </pre>
|
||||
*/
|
||||
struct wl_seat;
|
||||
struct wl_surface;
|
||||
struct zwp_text_input_manager_v3;
|
||||
struct zwp_text_input_v3;
|
||||
|
||||
#ifndef ZWP_TEXT_INPUT_V3_INTERFACE
|
||||
#define ZWP_TEXT_INPUT_V3_INTERFACE
|
||||
/**
|
||||
* @page page_iface_zwp_text_input_v3 zwp_text_input_v3
|
||||
* @section page_iface_zwp_text_input_v3_desc Description
|
||||
*
|
||||
* The zwp_text_input_v3 interface represents text input and input methods
|
||||
* associated with a seat. It provides enter/leave events to follow the
|
||||
* text input focus for a seat.
|
||||
*
|
||||
* Requests are used to enable/disable the text-input object and set
|
||||
* state information like surrounding and selected text or the content type.
|
||||
* The information about the entered text is sent to the text-input object
|
||||
* via the preedit_string and commit_string events.
|
||||
*
|
||||
* Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices
|
||||
* must not point to middle bytes inside a code point: they must either
|
||||
* point to the first byte of a code point or to the end of the buffer.
|
||||
* Lengths must be measured between two valid indices.
|
||||
*
|
||||
* Focus moving throughout surfaces will result in the emission of
|
||||
* zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused
|
||||
* surface must commit zwp_text_input_v3.enable and
|
||||
* zwp_text_input_v3.disable requests as the keyboard focus moves across
|
||||
* editable and non-editable elements of the UI. Those two requests are not
|
||||
* expected to be paired with each other, the compositor must be able to
|
||||
* handle consecutive series of the same request.
|
||||
*
|
||||
* State is sent by the state requests (set_surrounding_text,
|
||||
* set_content_type and set_cursor_rectangle) and a commit request. After an
|
||||
* enter event or disable request all state information is invalidated and
|
||||
* needs to be resent by the client.
|
||||
* @section page_iface_zwp_text_input_v3_api API
|
||||
* See @ref iface_zwp_text_input_v3.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_zwp_text_input_v3 The zwp_text_input_v3 interface
|
||||
*
|
||||
* The zwp_text_input_v3 interface represents text input and input methods
|
||||
* associated with a seat. It provides enter/leave events to follow the
|
||||
* text input focus for a seat.
|
||||
*
|
||||
* Requests are used to enable/disable the text-input object and set
|
||||
* state information like surrounding and selected text or the content type.
|
||||
* The information about the entered text is sent to the text-input object
|
||||
* via the preedit_string and commit_string events.
|
||||
*
|
||||
* Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices
|
||||
* must not point to middle bytes inside a code point: they must either
|
||||
* point to the first byte of a code point or to the end of the buffer.
|
||||
* Lengths must be measured between two valid indices.
|
||||
*
|
||||
* Focus moving throughout surfaces will result in the emission of
|
||||
* zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused
|
||||
* surface must commit zwp_text_input_v3.enable and
|
||||
* zwp_text_input_v3.disable requests as the keyboard focus moves across
|
||||
* editable and non-editable elements of the UI. Those two requests are not
|
||||
* expected to be paired with each other, the compositor must be able to
|
||||
* handle consecutive series of the same request.
|
||||
*
|
||||
* State is sent by the state requests (set_surrounding_text,
|
||||
* set_content_type and set_cursor_rectangle) and a commit request. After an
|
||||
* enter event or disable request all state information is invalidated and
|
||||
* needs to be resent by the client.
|
||||
*/
|
||||
extern const struct wl_interface zwp_text_input_v3_interface;
|
||||
#endif
|
||||
#ifndef ZWP_TEXT_INPUT_MANAGER_V3_INTERFACE
|
||||
#define ZWP_TEXT_INPUT_MANAGER_V3_INTERFACE
|
||||
/**
|
||||
* @page page_iface_zwp_text_input_manager_v3 zwp_text_input_manager_v3
|
||||
* @section page_iface_zwp_text_input_manager_v3_desc Description
|
||||
*
|
||||
* A factory for text-input objects. This object is a global singleton.
|
||||
* @section page_iface_zwp_text_input_manager_v3_api API
|
||||
* See @ref iface_zwp_text_input_manager_v3.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_zwp_text_input_manager_v3 The zwp_text_input_manager_v3 interface
|
||||
*
|
||||
* A factory for text-input objects. This object is a global singleton.
|
||||
*/
|
||||
extern const struct wl_interface zwp_text_input_manager_v3_interface;
|
||||
#endif
|
||||
|
||||
#ifndef ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM
|
||||
#define ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
* text change reason
|
||||
*
|
||||
* Reason for the change of surrounding text or cursor posision.
|
||||
*/
|
||||
enum zwp_text_input_v3_change_cause {
|
||||
/**
|
||||
* input method caused the change
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD = 0,
|
||||
/**
|
||||
* something else than the input method caused the change
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_OTHER = 1,
|
||||
};
|
||||
#endif /* ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM */
|
||||
|
||||
#ifndef ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM
|
||||
#define ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
* content hint
|
||||
*
|
||||
* Content hint is a bitmask to allow to modify the behavior of the text
|
||||
* input.
|
||||
*/
|
||||
enum zwp_text_input_v3_content_hint {
|
||||
/**
|
||||
* no special behavior
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE = 0x0,
|
||||
/**
|
||||
* suggest word completions
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION = 0x1,
|
||||
/**
|
||||
* suggest word corrections
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK = 0x2,
|
||||
/**
|
||||
* switch to uppercase letters at the start of a sentence
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION = 0x4,
|
||||
/**
|
||||
* prefer lowercase letters
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_LOWERCASE = 0x8,
|
||||
/**
|
||||
* prefer uppercase letters
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE = 0x10,
|
||||
/**
|
||||
* prefer casing for titles and headings (can be language dependent)
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE = 0x20,
|
||||
/**
|
||||
* characters should be hidden
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT = 0x40,
|
||||
/**
|
||||
* typed text should not be stored
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA = 0x80,
|
||||
/**
|
||||
* just Latin characters should be entered
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_LATIN = 0x100,
|
||||
/**
|
||||
* the text input is multiline
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE = 0x200,
|
||||
};
|
||||
#endif /* ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM */
|
||||
|
||||
#ifndef ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM
|
||||
#define ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
* content purpose
|
||||
*
|
||||
* The content purpose allows to specify the primary purpose of a text
|
||||
* input.
|
||||
*
|
||||
* This allows an input method to show special purpose input panels with
|
||||
* extra characters or to disallow some characters.
|
||||
*/
|
||||
enum zwp_text_input_v3_content_purpose {
|
||||
/**
|
||||
* default input, allowing all characters
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL = 0,
|
||||
/**
|
||||
* allow only alphabetic characters
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ALPHA = 1,
|
||||
/**
|
||||
* allow only digits
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DIGITS = 2,
|
||||
/**
|
||||
* input a number (including decimal separator and sign)
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER = 3,
|
||||
/**
|
||||
* input a phone number
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE = 4,
|
||||
/**
|
||||
* input an URL
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_URL = 5,
|
||||
/**
|
||||
* input an email address
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL = 6,
|
||||
/**
|
||||
* input a name of a person
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME = 7,
|
||||
/**
|
||||
* input a password (combine with sensitive_data hint)
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD = 8,
|
||||
/**
|
||||
* input is a numeric password (combine with sensitive_data hint)
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN = 9,
|
||||
/**
|
||||
* input a date
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATE = 10,
|
||||
/**
|
||||
* input a time
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TIME = 11,
|
||||
/**
|
||||
* input a date and time
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATETIME = 12,
|
||||
/**
|
||||
* input for a terminal
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL = 13,
|
||||
};
|
||||
#endif /* ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM */
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
* @struct zwp_text_input_v3_listener
|
||||
*/
|
||||
struct zwp_text_input_v3_listener {
|
||||
/**
|
||||
* enter event
|
||||
*
|
||||
* Notification that this seat's text-input focus is on a certain
|
||||
* surface.
|
||||
*
|
||||
* If client has created multiple text input objects, compositor
|
||||
* must send this event to all of them.
|
||||
*
|
||||
* When the seat has the keyboard capability the text-input focus
|
||||
* follows the keyboard focus. This event sets the current surface
|
||||
* for the text-input object.
|
||||
*/
|
||||
void (*enter)(void *data,
|
||||
struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||
struct wl_surface *surface);
|
||||
/**
|
||||
* leave event
|
||||
*
|
||||
* Notification that this seat's text-input focus is no longer on
|
||||
* a certain surface. The client should reset any preedit string
|
||||
* previously set.
|
||||
*
|
||||
* The leave notification clears the current surface. It is sent
|
||||
* before the enter notification for the new focus. After leave
|
||||
* event, compositor must ignore requests from any text input
|
||||
* instances until next enter event.
|
||||
*
|
||||
* When the seat has the keyboard capability the text-input focus
|
||||
* follows the keyboard focus.
|
||||
*/
|
||||
void (*leave)(void *data,
|
||||
struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||
struct wl_surface *surface);
|
||||
/**
|
||||
* pre-edit
|
||||
*
|
||||
* Notify when a new composing text (pre-edit) should be set at
|
||||
* the current cursor position. Any previously set composing text
|
||||
* must be removed. Any previously existing selected text must be
|
||||
* removed.
|
||||
*
|
||||
* The argument text contains the pre-edit string buffer.
|
||||
*
|
||||
* The parameters cursor_begin and cursor_end are counted in bytes
|
||||
* relative to the beginning of the submitted text buffer. Cursor
|
||||
* should be hidden when both are equal to -1.
|
||||
*
|
||||
* They could be represented by the client as a line if both values
|
||||
* are the same, or as a text highlight otherwise.
|
||||
*
|
||||
* Values set with this event are double-buffered. They must be
|
||||
* applied and reset to initial on the next zwp_text_input_v3.done
|
||||
* event.
|
||||
*
|
||||
* The initial value of text is an empty string, and cursor_begin,
|
||||
* cursor_end and cursor_hidden are all 0.
|
||||
*/
|
||||
void (*preedit_string)(void *data,
|
||||
struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||
const char *text,
|
||||
int32_t cursor_begin,
|
||||
int32_t cursor_end);
|
||||
/**
|
||||
* text commit
|
||||
*
|
||||
* Notify when text should be inserted into the editor widget.
|
||||
* The text to commit could be either just a single character after
|
||||
* a key press or the result of some composing (pre-edit).
|
||||
*
|
||||
* Values set with this event are double-buffered. They must be
|
||||
* applied and reset to initial on the next zwp_text_input_v3.done
|
||||
* event.
|
||||
*
|
||||
* The initial value of text is an empty string.
|
||||
*/
|
||||
void (*commit_string)(void *data,
|
||||
struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||
const char *text);
|
||||
/**
|
||||
* delete surrounding text
|
||||
*
|
||||
* Notify when the text around the current cursor position should
|
||||
* be deleted.
|
||||
*
|
||||
* Before_length and after_length are the number of bytes before
|
||||
* and after the current cursor index (excluding the selection) to
|
||||
* delete.
|
||||
*
|
||||
* If a preedit text is present, in effect before_length is counted
|
||||
* from the beginning of it, and after_length from its end (see
|
||||
* done event sequence).
|
||||
*
|
||||
* Values set with this event are double-buffered. They must be
|
||||
* applied and reset to initial on the next zwp_text_input_v3.done
|
||||
* event.
|
||||
*
|
||||
* The initial values of both before_length and after_length are 0.
|
||||
* @param before_length length of text before current cursor position
|
||||
* @param after_length length of text after current cursor position
|
||||
*/
|
||||
void (*delete_surrounding_text)(void *data,
|
||||
struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||
uint32_t before_length,
|
||||
uint32_t after_length);
|
||||
/**
|
||||
* apply changes
|
||||
*
|
||||
* Instruct the application to apply changes to state requested
|
||||
* by the preedit_string, commit_string and delete_surrounding_text
|
||||
* events. The state relating to these events is double-buffered,
|
||||
* and each one modifies the pending state. This event replaces the
|
||||
* current state with the pending state.
|
||||
*
|
||||
* The application must proceed by evaluating the changes in the
|
||||
* following order:
|
||||
*
|
||||
* 1. Replace existing preedit string with the cursor. 2. Delete
|
||||
* requested surrounding text. 3. Insert commit string with the
|
||||
* cursor at its end. 4. Calculate surrounding text to send. 5.
|
||||
* Insert new preedit text in cursor position. 6. Place cursor
|
||||
* inside preedit text.
|
||||
*
|
||||
* The serial number reflects the last state of the
|
||||
* zwp_text_input_v3 object known to the compositor. The value of
|
||||
* the serial argument must be equal to the number of commit
|
||||
* requests already issued on that object. When the client receives
|
||||
* a done event with a serial different than the number of past
|
||||
* commit requests, it must proceed as normal, except it should not
|
||||
* change the current state of the zwp_text_input_v3 object.
|
||||
*/
|
||||
void (*done)(void *data,
|
||||
struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||
uint32_t serial);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
static inline int
|
||||
zwp_text_input_v3_add_listener(struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||
const struct zwp_text_input_v3_listener *listener, void *data)
|
||||
{
|
||||
return wl_proxy_add_listener((struct wl_proxy *) zwp_text_input_v3,
|
||||
(void (**)(void)) listener, data);
|
||||
}
|
||||
|
||||
#define ZWP_TEXT_INPUT_V3_DESTROY 0
|
||||
#define ZWP_TEXT_INPUT_V3_ENABLE 1
|
||||
#define ZWP_TEXT_INPUT_V3_DISABLE 2
|
||||
#define ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT 3
|
||||
#define ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE 4
|
||||
#define ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE 5
|
||||
#define ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE 6
|
||||
#define ZWP_TEXT_INPUT_V3_COMMIT 7
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_ENTER_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_LEAVE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_PREEDIT_STRING_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_COMMIT_STRING_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_DELETE_SURROUNDING_TEXT_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_DONE_SINCE_VERSION 1
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_DESTROY_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_ENABLE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_DISABLE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_COMMIT_SINCE_VERSION 1
|
||||
|
||||
/** @ingroup iface_zwp_text_input_v3 */
|
||||
static inline void
|
||||
zwp_text_input_v3_set_user_data(struct zwp_text_input_v3 *zwp_text_input_v3, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) zwp_text_input_v3, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_zwp_text_input_v3 */
|
||||
static inline void *
|
||||
zwp_text_input_v3_get_user_data(struct zwp_text_input_v3 *zwp_text_input_v3)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) zwp_text_input_v3);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
zwp_text_input_v3_get_version(struct zwp_text_input_v3 *zwp_text_input_v3)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*
|
||||
* Destroy the wp_text_input object. Also disables all surfaces enabled
|
||||
* through this wp_text_input object.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_v3_destroy(struct zwp_text_input_v3 *zwp_text_input_v3)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
|
||||
ZWP_TEXT_INPUT_V3_DESTROY);
|
||||
|
||||
wl_proxy_destroy((struct wl_proxy *) zwp_text_input_v3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*
|
||||
* Requests text input on the surface previously obtained from the enter
|
||||
* event.
|
||||
*
|
||||
* This request must be issued every time the active text input changes
|
||||
* to a new one, including within the current surface. Use
|
||||
* zwp_text_input_v3.disable when there is no longer any input focus on
|
||||
* the current surface.
|
||||
*
|
||||
* Clients must not enable more than one text input on the single seat
|
||||
* and should disable the current text input before enabling the new one.
|
||||
* At most one instance of text input may be in enabled state per instance,
|
||||
* Requests to enable the another text input when some text input is active
|
||||
* must be ignored by compositor.
|
||||
*
|
||||
* This request resets all state associated with previous enable, disable,
|
||||
* set_surrounding_text, set_text_change_cause, set_content_type, and
|
||||
* set_cursor_rectangle requests, as well as the state associated with
|
||||
* preedit_string, commit_string, and delete_surrounding_text events.
|
||||
*
|
||||
* The set_surrounding_text, set_content_type and set_cursor_rectangle
|
||||
* requests must follow if the text input supports the necessary
|
||||
* functionality.
|
||||
*
|
||||
* State set with this request is double-buffered. It will get applied on
|
||||
* the next zwp_text_input_v3.commit request, and stay valid until the
|
||||
* next committed enable or disable request.
|
||||
*
|
||||
* The changes must be applied by the compositor after issuing a
|
||||
* zwp_text_input_v3.commit request.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_v3_enable(struct zwp_text_input_v3 *zwp_text_input_v3)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
|
||||
ZWP_TEXT_INPUT_V3_ENABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*
|
||||
* Explicitly disable text input on the current surface (typically when
|
||||
* there is no focus on any text entry inside the surface).
|
||||
*
|
||||
* State set with this request is double-buffered. It will get applied on
|
||||
* the next zwp_text_input_v3.commit request.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_v3_disable(struct zwp_text_input_v3 *zwp_text_input_v3)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
|
||||
ZWP_TEXT_INPUT_V3_DISABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*
|
||||
* Sets the surrounding plain text around the input, excluding the preedit
|
||||
* text.
|
||||
*
|
||||
* The client should notify the compositor of any changes in any of the
|
||||
* values carried with this request, including changes caused by handling
|
||||
* incoming text-input events as well as changes caused by other
|
||||
* mechanisms like keyboard typing.
|
||||
*
|
||||
* If the client is unaware of the text around the cursor, it should not
|
||||
* issue this request, to signify lack of support to the compositor.
|
||||
*
|
||||
* Text is UTF-8 encoded, and should include the cursor position, the
|
||||
* complete selection and additional characters before and after them.
|
||||
* There is a maximum length of wayland messages, so text can not be
|
||||
* longer than 4000 bytes.
|
||||
*
|
||||
* Cursor is the byte offset of the cursor within text buffer.
|
||||
*
|
||||
* Anchor is the byte offset of the selection anchor within text buffer.
|
||||
* If there is no selected text, anchor is the same as cursor.
|
||||
*
|
||||
* If any preedit text is present, it is replaced with a cursor for the
|
||||
* purpose of this event.
|
||||
*
|
||||
* Values set with this request are double-buffered. They will get applied
|
||||
* on the next zwp_text_input_v3.commit request, and stay valid until the
|
||||
* next committed enable or disable request.
|
||||
*
|
||||
* The initial state for affected fields is empty, meaning that the text
|
||||
* input does not support sending surrounding text. If the empty values
|
||||
* get applied, subsequent attempts to change them may have no effect.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_v3_set_surrounding_text(struct zwp_text_input_v3 *zwp_text_input_v3, const char *text, int32_t cursor, int32_t anchor)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
|
||||
ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT, text, cursor, anchor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*
|
||||
* Tells the compositor why the text surrounding the cursor changed.
|
||||
*
|
||||
* Whenever the client detects an external change in text, cursor, or
|
||||
* anchor posision, it must issue this request to the compositor. This
|
||||
* request is intended to give the input method a chance to update the
|
||||
* preedit text in an appropriate way, e.g. by removing it when the user
|
||||
* starts typing with a keyboard.
|
||||
*
|
||||
* cause describes the source of the change.
|
||||
*
|
||||
* The value set with this request is double-buffered. It must be applied
|
||||
* and reset to initial at the next zwp_text_input_v3.commit request.
|
||||
*
|
||||
* The initial value of cause is input_method.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_v3_set_text_change_cause(struct zwp_text_input_v3 *zwp_text_input_v3, uint32_t cause)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
|
||||
ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*
|
||||
* Sets the content purpose and content hint. While the purpose is the
|
||||
* basic purpose of an input field, the hint flags allow to modify some of
|
||||
* the behavior.
|
||||
*
|
||||
* Values set with this request are double-buffered. They will get applied
|
||||
* on the next zwp_text_input_v3.commit request.
|
||||
* Subsequent attempts to update them may have no effect. The values
|
||||
* remain valid until the next committed enable or disable request.
|
||||
*
|
||||
* The initial value for hint is none, and the initial value for purpose
|
||||
* is normal.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_v3_set_content_type(struct zwp_text_input_v3 *zwp_text_input_v3, uint32_t hint, uint32_t purpose)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
|
||||
ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE, hint, purpose);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*
|
||||
* Marks an area around the cursor as a x, y, width, height rectangle in
|
||||
* surface local coordinates.
|
||||
*
|
||||
* Allows the compositor to put a window with word suggestions near the
|
||||
* cursor, without obstructing the text being input.
|
||||
*
|
||||
* If the client is unaware of the position of edited text, it should not
|
||||
* issue this request, to signify lack of support to the compositor.
|
||||
*
|
||||
* Values set with this request are double-buffered. They will get applied
|
||||
* on the next zwp_text_input_v3.commit request, and stay valid until the
|
||||
* next committed enable or disable request.
|
||||
*
|
||||
* The initial values describing a cursor rectangle are empty. That means
|
||||
* the text input does not support describing the cursor area. If the
|
||||
* empty values get applied, subsequent attempts to change them may have
|
||||
* no effect.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_v3_set_cursor_rectangle(struct zwp_text_input_v3 *zwp_text_input_v3, int32_t x, int32_t y, int32_t width, int32_t height)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
|
||||
ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE, x, y, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*
|
||||
* Atomically applies state changes recently sent to the compositor.
|
||||
*
|
||||
* The commit request establishes and updates the state of the client, and
|
||||
* must be issued after any changes to apply them.
|
||||
*
|
||||
* Text input state (enabled status, content purpose, content hint,
|
||||
* surrounding text and change cause, cursor rectangle) is conceptually
|
||||
* double-buffered within the context of a text input, i.e. between a
|
||||
* committed enable request and the following committed enable or disable
|
||||
* request.
|
||||
*
|
||||
* Protocol requests modify the pending state, as opposed to the current
|
||||
* state in use by the input method. A commit request atomically applies
|
||||
* all pending state, replacing the current state. After commit, the new
|
||||
* pending state is as documented for each related request.
|
||||
*
|
||||
* Requests are applied in the order of arrival.
|
||||
*
|
||||
* Neither current nor pending state are modified unless noted otherwise.
|
||||
*
|
||||
* The compositor must count the number of commit requests coming from
|
||||
* each zwp_text_input_v3 object and use the count as the serial in done
|
||||
* events.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_v3_commit(struct zwp_text_input_v3 *zwp_text_input_v3)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
|
||||
ZWP_TEXT_INPUT_V3_COMMIT);
|
||||
}
|
||||
|
||||
#define ZWP_TEXT_INPUT_MANAGER_V3_DESTROY 0
|
||||
#define ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT 1
|
||||
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_manager_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_MANAGER_V3_DESTROY_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_manager_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT_SINCE_VERSION 1
|
||||
|
||||
/** @ingroup iface_zwp_text_input_manager_v3 */
|
||||
static inline void
|
||||
zwp_text_input_manager_v3_set_user_data(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) zwp_text_input_manager_v3, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_zwp_text_input_manager_v3 */
|
||||
static inline void *
|
||||
zwp_text_input_manager_v3_get_user_data(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) zwp_text_input_manager_v3);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
zwp_text_input_manager_v3_get_version(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) zwp_text_input_manager_v3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_manager_v3
|
||||
*
|
||||
* Destroy the wp_text_input_manager object.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_manager_v3_destroy(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_manager_v3,
|
||||
ZWP_TEXT_INPUT_MANAGER_V3_DESTROY);
|
||||
|
||||
wl_proxy_destroy((struct wl_proxy *) zwp_text_input_manager_v3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_manager_v3
|
||||
*
|
||||
* Creates a new text-input object for a given seat.
|
||||
*/
|
||||
static inline struct zwp_text_input_v3 *
|
||||
zwp_text_input_manager_v3_get_text_input(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3, struct wl_seat *seat)
|
||||
{
|
||||
struct wl_proxy *id;
|
||||
|
||||
id = wl_proxy_marshal_constructor((struct wl_proxy *) zwp_text_input_manager_v3,
|
||||
ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT, &zwp_text_input_v3_interface, NULL, seat);
|
||||
|
||||
return (struct zwp_text_input_v3 *) id;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
//go:build ((linux && !android) || freebsd) && !nowayland
|
||||
// +build linux,!android freebsd
|
||||
// +build !nowayland
|
||||
|
||||
/* Generated by wayland-scanner 1.19.0 */
|
||||
|
||||
/*
|
||||
* Copyright © 2018 Simon Ser
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include "wayland-util.h"
|
||||
|
||||
#ifndef __has_attribute
|
||||
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
|
||||
#endif
|
||||
|
||||
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
|
||||
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
|
||||
#else
|
||||
#define WL_PRIVATE
|
||||
#endif
|
||||
|
||||
extern const struct wl_interface xdg_toplevel_interface;
|
||||
extern const struct wl_interface zxdg_toplevel_decoration_v1_interface;
|
||||
|
||||
static const struct wl_interface *xdg_decoration_unstable_v1_types[] = {
|
||||
NULL,
|
||||
&zxdg_toplevel_decoration_v1_interface,
|
||||
&xdg_toplevel_interface,
|
||||
};
|
||||
|
||||
static const struct wl_message zxdg_decoration_manager_v1_requests[] = {
|
||||
{ "destroy", "", xdg_decoration_unstable_v1_types + 0 },
|
||||
{ "get_toplevel_decoration", "no", xdg_decoration_unstable_v1_types + 1 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface zxdg_decoration_manager_v1_interface = {
|
||||
"zxdg_decoration_manager_v1", 1,
|
||||
2, zxdg_decoration_manager_v1_requests,
|
||||
0, NULL,
|
||||
};
|
||||
|
||||
static const struct wl_message zxdg_toplevel_decoration_v1_requests[] = {
|
||||
{ "destroy", "", xdg_decoration_unstable_v1_types + 0 },
|
||||
{ "set_mode", "u", xdg_decoration_unstable_v1_types + 0 },
|
||||
{ "unset_mode", "", xdg_decoration_unstable_v1_types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message zxdg_toplevel_decoration_v1_events[] = {
|
||||
{ "configure", "u", xdg_decoration_unstable_v1_types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface zxdg_toplevel_decoration_v1_interface = {
|
||||
"zxdg_toplevel_decoration_v1", 1,
|
||||
3, zxdg_toplevel_decoration_v1_requests,
|
||||
1, zxdg_toplevel_decoration_v1_events,
|
||||
};
|
||||
|
||||
+382
@@ -0,0 +1,382 @@
|
||||
/* Generated by wayland-scanner 1.19.0 */
|
||||
|
||||
#ifndef XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H
|
||||
#define XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "wayland-client.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @page page_xdg_decoration_unstable_v1 The xdg_decoration_unstable_v1 protocol
|
||||
* @section page_ifaces_xdg_decoration_unstable_v1 Interfaces
|
||||
* - @subpage page_iface_zxdg_decoration_manager_v1 - window decoration manager
|
||||
* - @subpage page_iface_zxdg_toplevel_decoration_v1 - decoration object for a toplevel surface
|
||||
* @section page_copyright_xdg_decoration_unstable_v1 Copyright
|
||||
* <pre>
|
||||
*
|
||||
* Copyright © 2018 Simon Ser
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
* </pre>
|
||||
*/
|
||||
struct xdg_toplevel;
|
||||
struct zxdg_decoration_manager_v1;
|
||||
struct zxdg_toplevel_decoration_v1;
|
||||
|
||||
#ifndef ZXDG_DECORATION_MANAGER_V1_INTERFACE
|
||||
#define ZXDG_DECORATION_MANAGER_V1_INTERFACE
|
||||
/**
|
||||
* @page page_iface_zxdg_decoration_manager_v1 zxdg_decoration_manager_v1
|
||||
* @section page_iface_zxdg_decoration_manager_v1_desc Description
|
||||
*
|
||||
* This interface allows a compositor to announce support for server-side
|
||||
* decorations.
|
||||
*
|
||||
* A window decoration is a set of window controls as deemed appropriate by
|
||||
* the party managing them, such as user interface components used to move,
|
||||
* resize and change a window's state.
|
||||
*
|
||||
* A client can use this protocol to request being decorated by a supporting
|
||||
* compositor.
|
||||
*
|
||||
* If compositor and client do not negotiate the use of a server-side
|
||||
* decoration using this protocol, clients continue to self-decorate as they
|
||||
* see fit.
|
||||
*
|
||||
* Warning! The protocol described in this file is experimental and
|
||||
* backward incompatible changes may be made. Backward compatible changes
|
||||
* may be added together with the corresponding interface version bump.
|
||||
* Backward incompatible changes are done by bumping the version number in
|
||||
* the protocol and interface names and resetting the interface version.
|
||||
* Once the protocol is to be declared stable, the 'z' prefix and the
|
||||
* version number in the protocol and interface names are removed and the
|
||||
* interface version number is reset.
|
||||
* @section page_iface_zxdg_decoration_manager_v1_api API
|
||||
* See @ref iface_zxdg_decoration_manager_v1.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_zxdg_decoration_manager_v1 The zxdg_decoration_manager_v1 interface
|
||||
*
|
||||
* This interface allows a compositor to announce support for server-side
|
||||
* decorations.
|
||||
*
|
||||
* A window decoration is a set of window controls as deemed appropriate by
|
||||
* the party managing them, such as user interface components used to move,
|
||||
* resize and change a window's state.
|
||||
*
|
||||
* A client can use this protocol to request being decorated by a supporting
|
||||
* compositor.
|
||||
*
|
||||
* If compositor and client do not negotiate the use of a server-side
|
||||
* decoration using this protocol, clients continue to self-decorate as they
|
||||
* see fit.
|
||||
*
|
||||
* Warning! The protocol described in this file is experimental and
|
||||
* backward incompatible changes may be made. Backward compatible changes
|
||||
* may be added together with the corresponding interface version bump.
|
||||
* Backward incompatible changes are done by bumping the version number in
|
||||
* the protocol and interface names and resetting the interface version.
|
||||
* Once the protocol is to be declared stable, the 'z' prefix and the
|
||||
* version number in the protocol and interface names are removed and the
|
||||
* interface version number is reset.
|
||||
*/
|
||||
extern const struct wl_interface zxdg_decoration_manager_v1_interface;
|
||||
#endif
|
||||
#ifndef ZXDG_TOPLEVEL_DECORATION_V1_INTERFACE
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_INTERFACE
|
||||
/**
|
||||
* @page page_iface_zxdg_toplevel_decoration_v1 zxdg_toplevel_decoration_v1
|
||||
* @section page_iface_zxdg_toplevel_decoration_v1_desc Description
|
||||
*
|
||||
* The decoration object allows the compositor to toggle server-side window
|
||||
* decorations for a toplevel surface. The client can request to switch to
|
||||
* another mode.
|
||||
*
|
||||
* The xdg_toplevel_decoration object must be destroyed before its
|
||||
* xdg_toplevel.
|
||||
* @section page_iface_zxdg_toplevel_decoration_v1_api API
|
||||
* See @ref iface_zxdg_toplevel_decoration_v1.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_zxdg_toplevel_decoration_v1 The zxdg_toplevel_decoration_v1 interface
|
||||
*
|
||||
* The decoration object allows the compositor to toggle server-side window
|
||||
* decorations for a toplevel surface. The client can request to switch to
|
||||
* another mode.
|
||||
*
|
||||
* The xdg_toplevel_decoration object must be destroyed before its
|
||||
* xdg_toplevel.
|
||||
*/
|
||||
extern const struct wl_interface zxdg_toplevel_decoration_v1_interface;
|
||||
#endif
|
||||
|
||||
#define ZXDG_DECORATION_MANAGER_V1_DESTROY 0
|
||||
#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION 1
|
||||
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_decoration_manager_v1
|
||||
*/
|
||||
#define ZXDG_DECORATION_MANAGER_V1_DESTROY_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zxdg_decoration_manager_v1
|
||||
*/
|
||||
#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION_SINCE_VERSION 1
|
||||
|
||||
/** @ingroup iface_zxdg_decoration_manager_v1 */
|
||||
static inline void
|
||||
zxdg_decoration_manager_v1_set_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) zxdg_decoration_manager_v1, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_zxdg_decoration_manager_v1 */
|
||||
static inline void *
|
||||
zxdg_decoration_manager_v1_get_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) zxdg_decoration_manager_v1);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
zxdg_decoration_manager_v1_get_version(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_decoration_manager_v1
|
||||
*
|
||||
* Destroy the decoration manager. This doesn't destroy objects created
|
||||
* with the manager.
|
||||
*/
|
||||
static inline void
|
||||
zxdg_decoration_manager_v1_destroy(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zxdg_decoration_manager_v1,
|
||||
ZXDG_DECORATION_MANAGER_V1_DESTROY);
|
||||
|
||||
wl_proxy_destroy((struct wl_proxy *) zxdg_decoration_manager_v1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_decoration_manager_v1
|
||||
*
|
||||
* Create a new decoration object associated with the given toplevel.
|
||||
*
|
||||
* Creating an xdg_toplevel_decoration from an xdg_toplevel which has a
|
||||
* buffer attached or committed is a client error, and any attempts by a
|
||||
* client to attach or manipulate a buffer prior to the first
|
||||
* xdg_toplevel_decoration.configure event must also be treated as
|
||||
* errors.
|
||||
*/
|
||||
static inline struct zxdg_toplevel_decoration_v1 *
|
||||
zxdg_decoration_manager_v1_get_toplevel_decoration(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, struct xdg_toplevel *toplevel)
|
||||
{
|
||||
struct wl_proxy *id;
|
||||
|
||||
id = wl_proxy_marshal_constructor((struct wl_proxy *) zxdg_decoration_manager_v1,
|
||||
ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION, &zxdg_toplevel_decoration_v1_interface, NULL, toplevel);
|
||||
|
||||
return (struct zxdg_toplevel_decoration_v1 *) id;
|
||||
}
|
||||
|
||||
#ifndef ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM
|
||||
enum zxdg_toplevel_decoration_v1_error {
|
||||
/**
|
||||
* xdg_toplevel has a buffer attached before configure
|
||||
*/
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_ERROR_UNCONFIGURED_BUFFER = 0,
|
||||
/**
|
||||
* xdg_toplevel already has a decoration object
|
||||
*/
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ALREADY_CONSTRUCTED = 1,
|
||||
/**
|
||||
* xdg_toplevel destroyed before the decoration object
|
||||
*/
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ORPHANED = 2,
|
||||
};
|
||||
#endif /* ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM */
|
||||
|
||||
#ifndef ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
* window decoration modes
|
||||
*
|
||||
* These values describe window decoration modes.
|
||||
*/
|
||||
enum zxdg_toplevel_decoration_v1_mode {
|
||||
/**
|
||||
* no server-side window decoration
|
||||
*/
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE = 1,
|
||||
/**
|
||||
* server-side window decoration
|
||||
*/
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE = 2,
|
||||
};
|
||||
#endif /* ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM */
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
* @struct zxdg_toplevel_decoration_v1_listener
|
||||
*/
|
||||
struct zxdg_toplevel_decoration_v1_listener {
|
||||
/**
|
||||
* suggest a surface change
|
||||
*
|
||||
* The configure event asks the client to change its decoration
|
||||
* mode. The configured state should not be applied immediately.
|
||||
* Clients must send an ack_configure in response to this event.
|
||||
* See xdg_surface.configure and xdg_surface.ack_configure for
|
||||
* details.
|
||||
*
|
||||
* A configure event can be sent at any time. The specified mode
|
||||
* must be obeyed by the client.
|
||||
* @param mode the decoration mode
|
||||
*/
|
||||
void (*configure)(void *data,
|
||||
struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
|
||||
uint32_t mode);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
*/
|
||||
static inline int
|
||||
zxdg_toplevel_decoration_v1_add_listener(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
|
||||
const struct zxdg_toplevel_decoration_v1_listener *listener, void *data)
|
||||
{
|
||||
return wl_proxy_add_listener((struct wl_proxy *) zxdg_toplevel_decoration_v1,
|
||||
(void (**)(void)) listener, data);
|
||||
}
|
||||
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY 0
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE 1
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE 2
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
*/
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_CONFIGURE_SINCE_VERSION 1
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
*/
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
*/
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
*/
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE_SINCE_VERSION 1
|
||||
|
||||
/** @ingroup iface_zxdg_toplevel_decoration_v1 */
|
||||
static inline void
|
||||
zxdg_toplevel_decoration_v1_set_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_zxdg_toplevel_decoration_v1 */
|
||||
static inline void *
|
||||
zxdg_toplevel_decoration_v1_get_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
zxdg_toplevel_decoration_v1_get_version(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
*
|
||||
* Switch back to a mode without any server-side decorations at the next
|
||||
* commit.
|
||||
*/
|
||||
static inline void
|
||||
zxdg_toplevel_decoration_v1_destroy(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zxdg_toplevel_decoration_v1,
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_DESTROY);
|
||||
|
||||
wl_proxy_destroy((struct wl_proxy *) zxdg_toplevel_decoration_v1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
*
|
||||
* Set the toplevel surface decoration mode. This informs the compositor
|
||||
* that the client prefers the provided decoration mode.
|
||||
*
|
||||
* After requesting a decoration mode, the compositor will respond by
|
||||
* emitting an xdg_surface.configure event. The client should then update
|
||||
* its content, drawing it without decorations if the received mode is
|
||||
* server-side decorations. The client must also acknowledge the configure
|
||||
* when committing the new content (see xdg_surface.ack_configure).
|
||||
*
|
||||
* The compositor can decide not to use the client's mode and enforce a
|
||||
* different mode instead.
|
||||
*
|
||||
* Clients whose decoration mode depend on the xdg_toplevel state may send
|
||||
* a set_mode request in response to an xdg_surface.configure event and wait
|
||||
* for the next xdg_surface.configure event to prevent unwanted state.
|
||||
* Such clients are responsible for preventing configure loops and must
|
||||
* make sure not to send multiple successive set_mode requests with the
|
||||
* same decoration mode.
|
||||
*/
|
||||
static inline void
|
||||
zxdg_toplevel_decoration_v1_set_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, uint32_t mode)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zxdg_toplevel_decoration_v1,
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
*
|
||||
* Unset the toplevel surface decoration mode. This informs the compositor
|
||||
* that the client doesn't prefer a particular decoration mode.
|
||||
*
|
||||
* This request has the same semantics as set_mode.
|
||||
*/
|
||||
static inline void
|
||||
zxdg_toplevel_decoration_v1_unset_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zxdg_toplevel_decoration_v1,
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
//go:build ((linux && !android) || freebsd) && !nowayland
|
||||
// +build linux,!android freebsd
|
||||
// +build !nowayland
|
||||
|
||||
/* Generated by wayland-scanner 1.19.0 */
|
||||
|
||||
/*
|
||||
* Copyright © 2008-2013 Kristian Høgsberg
|
||||
* Copyright © 2013 Rafael Antognolli
|
||||
* Copyright © 2013 Jasper St. Pierre
|
||||
* Copyright © 2010-2013 Intel Corporation
|
||||
* Copyright © 2015-2017 Samsung Electronics Co., Ltd
|
||||
* Copyright © 2015-2017 Red Hat Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include "wayland-util.h"
|
||||
|
||||
#ifndef __has_attribute
|
||||
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
|
||||
#endif
|
||||
|
||||
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
|
||||
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
|
||||
#else
|
||||
#define WL_PRIVATE
|
||||
#endif
|
||||
|
||||
extern const struct wl_interface wl_output_interface;
|
||||
extern const struct wl_interface wl_seat_interface;
|
||||
extern const struct wl_interface wl_surface_interface;
|
||||
extern const struct wl_interface xdg_popup_interface;
|
||||
extern const struct wl_interface xdg_positioner_interface;
|
||||
extern const struct wl_interface xdg_surface_interface;
|
||||
extern const struct wl_interface xdg_toplevel_interface;
|
||||
|
||||
static const struct wl_interface *xdg_shell_types[] = {
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
&xdg_positioner_interface,
|
||||
&xdg_surface_interface,
|
||||
&wl_surface_interface,
|
||||
&xdg_toplevel_interface,
|
||||
&xdg_popup_interface,
|
||||
&xdg_surface_interface,
|
||||
&xdg_positioner_interface,
|
||||
&xdg_toplevel_interface,
|
||||
&wl_seat_interface,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
&wl_seat_interface,
|
||||
NULL,
|
||||
&wl_seat_interface,
|
||||
NULL,
|
||||
NULL,
|
||||
&wl_output_interface,
|
||||
&wl_seat_interface,
|
||||
NULL,
|
||||
&xdg_positioner_interface,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_wm_base_requests[] = {
|
||||
{ "destroy", "", xdg_shell_types + 0 },
|
||||
{ "create_positioner", "n", xdg_shell_types + 4 },
|
||||
{ "get_xdg_surface", "no", xdg_shell_types + 5 },
|
||||
{ "pong", "u", xdg_shell_types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_wm_base_events[] = {
|
||||
{ "ping", "u", xdg_shell_types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_wm_base_interface = {
|
||||
"xdg_wm_base", 3,
|
||||
4, xdg_wm_base_requests,
|
||||
1, xdg_wm_base_events,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_positioner_requests[] = {
|
||||
{ "destroy", "", xdg_shell_types + 0 },
|
||||
{ "set_size", "ii", xdg_shell_types + 0 },
|
||||
{ "set_anchor_rect", "iiii", xdg_shell_types + 0 },
|
||||
{ "set_anchor", "u", xdg_shell_types + 0 },
|
||||
{ "set_gravity", "u", xdg_shell_types + 0 },
|
||||
{ "set_constraint_adjustment", "u", xdg_shell_types + 0 },
|
||||
{ "set_offset", "ii", xdg_shell_types + 0 },
|
||||
{ "set_reactive", "3", xdg_shell_types + 0 },
|
||||
{ "set_parent_size", "3ii", xdg_shell_types + 0 },
|
||||
{ "set_parent_configure", "3u", xdg_shell_types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_positioner_interface = {
|
||||
"xdg_positioner", 3,
|
||||
10, xdg_positioner_requests,
|
||||
0, NULL,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_surface_requests[] = {
|
||||
{ "destroy", "", xdg_shell_types + 0 },
|
||||
{ "get_toplevel", "n", xdg_shell_types + 7 },
|
||||
{ "get_popup", "n?oo", xdg_shell_types + 8 },
|
||||
{ "set_window_geometry", "iiii", xdg_shell_types + 0 },
|
||||
{ "ack_configure", "u", xdg_shell_types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_surface_events[] = {
|
||||
{ "configure", "u", xdg_shell_types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_surface_interface = {
|
||||
"xdg_surface", 3,
|
||||
5, xdg_surface_requests,
|
||||
1, xdg_surface_events,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_toplevel_requests[] = {
|
||||
{ "destroy", "", xdg_shell_types + 0 },
|
||||
{ "set_parent", "?o", xdg_shell_types + 11 },
|
||||
{ "set_title", "s", xdg_shell_types + 0 },
|
||||
{ "set_app_id", "s", xdg_shell_types + 0 },
|
||||
{ "show_window_menu", "ouii", xdg_shell_types + 12 },
|
||||
{ "move", "ou", xdg_shell_types + 16 },
|
||||
{ "resize", "ouu", xdg_shell_types + 18 },
|
||||
{ "set_max_size", "ii", xdg_shell_types + 0 },
|
||||
{ "set_min_size", "ii", xdg_shell_types + 0 },
|
||||
{ "set_maximized", "", xdg_shell_types + 0 },
|
||||
{ "unset_maximized", "", xdg_shell_types + 0 },
|
||||
{ "set_fullscreen", "?o", xdg_shell_types + 21 },
|
||||
{ "unset_fullscreen", "", xdg_shell_types + 0 },
|
||||
{ "set_minimized", "", xdg_shell_types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_toplevel_events[] = {
|
||||
{ "configure", "iia", xdg_shell_types + 0 },
|
||||
{ "close", "", xdg_shell_types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_toplevel_interface = {
|
||||
"xdg_toplevel", 3,
|
||||
14, xdg_toplevel_requests,
|
||||
2, xdg_toplevel_events,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_popup_requests[] = {
|
||||
{ "destroy", "", xdg_shell_types + 0 },
|
||||
{ "grab", "ou", xdg_shell_types + 22 },
|
||||
{ "reposition", "3ou", xdg_shell_types + 24 },
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_popup_events[] = {
|
||||
{ "configure", "iiii", xdg_shell_types + 0 },
|
||||
{ "popup_done", "", xdg_shell_types + 0 },
|
||||
{ "repositioned", "3u", xdg_shell_types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_popup_interface = {
|
||||
"xdg_popup", 3,
|
||||
3, xdg_popup_requests,
|
||||
3, xdg_popup_events,
|
||||
};
|
||||
|
||||
+2003
File diff suppressed because it is too large
Load Diff
+991
@@ -0,0 +1,991 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/font/gofont"
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/debug"
|
||||
"gioui.org/internal/ops"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/input"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/text"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
)
|
||||
|
||||
// Option configures a window.
|
||||
type Option func(unit.Metric, *Config)
|
||||
|
||||
// Window represents an operating system window.
|
||||
//
|
||||
// The zero-value Window is useful; the GUI window is created and shown the first
|
||||
// time the [Event] method is called. On iOS or Android, the first Window represents
|
||||
// the window previously created by the platform.
|
||||
//
|
||||
// More than one Window is not supported on iOS, Android, WebAssembly.
|
||||
type Window struct {
|
||||
initialOpts []Option
|
||||
initialActions []system.Action
|
||||
|
||||
ctx context
|
||||
gpu gpu.GPU
|
||||
// timer tracks the delayed invalidate goroutine.
|
||||
timer struct {
|
||||
// quit is shuts down the goroutine.
|
||||
quit chan struct{}
|
||||
// update the invalidate time.
|
||||
update chan time.Time
|
||||
}
|
||||
|
||||
animating bool
|
||||
hasNextFrame bool
|
||||
nextFrame time.Time
|
||||
// viewport is the latest frame size with insets applied.
|
||||
viewport image.Rectangle
|
||||
// metric is the metric from the most recent frame.
|
||||
metric unit.Metric
|
||||
queue input.Router
|
||||
cursor pointer.Cursor
|
||||
decorations struct {
|
||||
op.Ops
|
||||
// enabled tracks the Decorated option as
|
||||
// given to the Option method. It may differ
|
||||
// from Config.Decorated depending on platform
|
||||
// capability.
|
||||
enabled bool
|
||||
Config
|
||||
height unit.Dp
|
||||
currentHeight int
|
||||
*material.Theme
|
||||
*widget.Decorations
|
||||
}
|
||||
nocontext bool
|
||||
// semantic data, lazily evaluated if requested by a backend to speed up
|
||||
// the cases where semantic data is not needed.
|
||||
semantic struct {
|
||||
// uptodate tracks whether the fields below are up to date.
|
||||
uptodate bool
|
||||
root input.SemanticID
|
||||
prevTree []input.SemanticNode
|
||||
tree []input.SemanticNode
|
||||
ids map[input.SemanticID]input.SemanticNode
|
||||
}
|
||||
imeState editorState
|
||||
driver driver
|
||||
// gpuErr tracks the GPU error that is to be reported when
|
||||
// the window is closed.
|
||||
gpuErr error
|
||||
|
||||
// invMu protects mayInvalidate.
|
||||
invMu sync.Mutex
|
||||
mayInvalidate bool
|
||||
|
||||
// coalesced tracks the most recent events waiting to be delivered
|
||||
// to the client.
|
||||
coalesced eventSummary
|
||||
// frame tracks the most recent frame event.
|
||||
lastFrame struct {
|
||||
sync bool
|
||||
size image.Point
|
||||
off image.Point
|
||||
deco op.CallOp
|
||||
}
|
||||
}
|
||||
|
||||
type eventSummary struct {
|
||||
wakeup bool
|
||||
cfg *ConfigEvent
|
||||
view *ViewEvent
|
||||
frame *frameEvent
|
||||
framePending bool
|
||||
destroy *DestroyEvent
|
||||
}
|
||||
|
||||
type callbacks struct {
|
||||
w *Window
|
||||
}
|
||||
|
||||
func decoHeightOpt(h unit.Dp) Option {
|
||||
return func(m unit.Metric, c *Config) {
|
||||
c.decoHeight = h
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops, sigChan chan<- struct{}) error {
|
||||
signal := func() {
|
||||
if sigChan != nil {
|
||||
// We're done with frame, let the client continue.
|
||||
sigChan <- struct{}{}
|
||||
// Signal at most once.
|
||||
sigChan = nil
|
||||
}
|
||||
}
|
||||
defer signal()
|
||||
for {
|
||||
if w.gpu == nil && !w.nocontext {
|
||||
var err error
|
||||
if w.ctx == nil {
|
||||
w.ctx, err = w.driver.NewContext()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sync = true
|
||||
}
|
||||
}
|
||||
if sync && w.ctx != nil {
|
||||
if err := w.ctx.Refresh(); err != nil {
|
||||
if errors.Is(err, errOutOfDate) {
|
||||
// Surface couldn't be created for transient reasons. Skip
|
||||
// this frame and wait for the next.
|
||||
return nil
|
||||
}
|
||||
w.destroyGPU()
|
||||
if errors.Is(err, gpu.ErrDeviceLost) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
if w.ctx != nil {
|
||||
if err := w.ctx.Lock(); err != nil {
|
||||
w.destroyGPU()
|
||||
return err
|
||||
}
|
||||
}
|
||||
if w.gpu == nil && !w.nocontext {
|
||||
gpu, err := gpu.New(w.ctx.API())
|
||||
if err != nil {
|
||||
w.ctx.Unlock()
|
||||
w.destroyGPU()
|
||||
return err
|
||||
}
|
||||
w.gpu = gpu
|
||||
}
|
||||
if w.gpu != nil {
|
||||
if err := w.frame(frame, size); err != nil {
|
||||
w.ctx.Unlock()
|
||||
if errors.Is(err, errOutOfDate) {
|
||||
// GPU surface needs refreshing.
|
||||
sync = true
|
||||
continue
|
||||
}
|
||||
w.destroyGPU()
|
||||
if errors.Is(err, gpu.ErrDeviceLost) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.queue.Frame(frame)
|
||||
// Let the client continue as soon as possible, in particular before
|
||||
// a potentially blocking Present.
|
||||
signal()
|
||||
var err error
|
||||
if w.gpu != nil {
|
||||
err = w.ctx.Present()
|
||||
w.ctx.Unlock()
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) frame(frame *op.Ops, viewport image.Point) error {
|
||||
if runtime.GOOS == "js" {
|
||||
// Use transparent black when Gio is embedded, to allow mixing of Gio and
|
||||
// foreign content below.
|
||||
w.gpu.Clear(color.NRGBA{A: 0x00, R: 0x00, G: 0x00, B: 0x00})
|
||||
} else {
|
||||
w.gpu.Clear(color.NRGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff})
|
||||
}
|
||||
target, err := w.ctx.RenderTarget()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.gpu.Frame(frame, target, viewport)
|
||||
}
|
||||
|
||||
func (w *Window) processFrame(frame *op.Ops, ack chan<- struct{}) {
|
||||
w.coalesced.framePending = false
|
||||
wrapper := &w.decorations.Ops
|
||||
off := op.Offset(w.lastFrame.off).Push(wrapper)
|
||||
ops.AddCall(&wrapper.Internal, &frame.Internal, ops.PC{}, ops.PCFor(&frame.Internal))
|
||||
off.Pop()
|
||||
w.lastFrame.deco.Add(wrapper)
|
||||
if err := w.validateAndProcess(w.lastFrame.size, w.lastFrame.sync, wrapper, ack); err != nil {
|
||||
w.destroyGPU()
|
||||
w.gpuErr = err
|
||||
w.driver.Perform(system.ActionClose)
|
||||
return
|
||||
}
|
||||
w.updateState()
|
||||
w.updateCursor()
|
||||
}
|
||||
|
||||
func (w *Window) updateState() {
|
||||
for k := range w.semantic.ids {
|
||||
delete(w.semantic.ids, k)
|
||||
}
|
||||
w.semantic.uptodate = false
|
||||
q := &w.queue
|
||||
switch q.TextInputState() {
|
||||
case input.TextInputOpen:
|
||||
w.driver.ShowTextInput(true)
|
||||
case input.TextInputClose:
|
||||
w.driver.ShowTextInput(false)
|
||||
}
|
||||
if hint, ok := q.TextInputHint(); ok {
|
||||
w.driver.SetInputHint(hint)
|
||||
}
|
||||
if mime, txt, ok := q.WriteClipboard(); ok {
|
||||
w.driver.WriteClipboard(mime, txt)
|
||||
}
|
||||
if q.ClipboardRequested() {
|
||||
w.driver.ReadClipboard()
|
||||
}
|
||||
oldState := w.imeState
|
||||
newState := oldState
|
||||
newState.EditorState = q.EditorState()
|
||||
if newState != oldState {
|
||||
w.imeState = newState
|
||||
w.driver.EditorStateChanged(oldState, newState)
|
||||
}
|
||||
if t, ok := q.WakeupTime(); ok {
|
||||
w.setNextFrame(t)
|
||||
}
|
||||
w.updateAnimation()
|
||||
}
|
||||
|
||||
// Invalidate the window such that a [FrameEvent] will be generated immediately.
|
||||
// If the window is inactive, an unspecified event is sent instead.
|
||||
//
|
||||
// Note that Invalidate is intended for externally triggered updates, such as a
|
||||
// response from a network request. The [op.InvalidateCmd] command is more efficient
|
||||
// for animation.
|
||||
//
|
||||
// Invalidate is safe for concurrent use.
|
||||
func (w *Window) Invalidate() {
|
||||
w.invMu.Lock()
|
||||
defer w.invMu.Unlock()
|
||||
if w.mayInvalidate {
|
||||
w.mayInvalidate = false
|
||||
w.driver.Invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
// Option applies the options to the window. The options are hints; the platform is
|
||||
// free to ignore or adjust them.
|
||||
func (w *Window) Option(opts ...Option) {
|
||||
if len(opts) == 0 {
|
||||
return
|
||||
}
|
||||
if w.driver == nil {
|
||||
w.initialOpts = append(w.initialOpts, opts...)
|
||||
return
|
||||
}
|
||||
w.Run(func() {
|
||||
cnf := Config{Decorated: w.decorations.enabled}
|
||||
for _, opt := range opts {
|
||||
opt(w.metric, &cnf)
|
||||
}
|
||||
w.decorations.enabled = cnf.Decorated
|
||||
decoHeight := w.decorations.height
|
||||
if !w.decorations.enabled {
|
||||
decoHeight = 0
|
||||
}
|
||||
opts = append(opts, decoHeightOpt(decoHeight))
|
||||
w.driver.Configure(opts)
|
||||
w.setNextFrame(time.Time{})
|
||||
w.updateAnimation()
|
||||
})
|
||||
}
|
||||
|
||||
// Run f in the same thread as the native window event loop, and wait for f to
|
||||
// return or the window to close. If the window has not yet been created,
|
||||
// Run calls f directly.
|
||||
//
|
||||
// Note that most programs should not call Run; configuring a Window with
|
||||
// [CustomRenderer] is a notable exception.
|
||||
func (w *Window) Run(f func()) {
|
||||
if w.driver == nil {
|
||||
f()
|
||||
return
|
||||
}
|
||||
done := make(chan struct{})
|
||||
w.driver.Run(func() {
|
||||
defer close(done)
|
||||
f()
|
||||
})
|
||||
<-done
|
||||
}
|
||||
|
||||
func (w *Window) updateAnimation() {
|
||||
if w.driver == nil {
|
||||
return
|
||||
}
|
||||
animate := false
|
||||
if w.hasNextFrame {
|
||||
if dt := time.Until(w.nextFrame); dt <= 0 {
|
||||
animate = true
|
||||
} else {
|
||||
// Schedule redraw.
|
||||
w.scheduleInvalidate(w.nextFrame)
|
||||
}
|
||||
}
|
||||
if animate != w.animating {
|
||||
w.animating = animate
|
||||
w.driver.SetAnimating(animate)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) scheduleInvalidate(t time.Time) {
|
||||
if w.timer.quit == nil {
|
||||
w.timer.quit = make(chan struct{})
|
||||
w.timer.update = make(chan time.Time)
|
||||
go func() {
|
||||
var timer *time.Timer
|
||||
for {
|
||||
var timeC <-chan time.Time
|
||||
if timer != nil {
|
||||
timeC = timer.C
|
||||
}
|
||||
select {
|
||||
case <-w.timer.quit:
|
||||
w.timer.quit <- struct{}{}
|
||||
return
|
||||
case t := <-w.timer.update:
|
||||
if timer != nil {
|
||||
timer.Stop()
|
||||
}
|
||||
timer = time.NewTimer(time.Until(t))
|
||||
case <-timeC:
|
||||
w.Invalidate()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
w.timer.update <- t
|
||||
}
|
||||
|
||||
func (w *Window) setNextFrame(at time.Time) {
|
||||
if !w.hasNextFrame || at.Before(w.nextFrame) {
|
||||
w.hasNextFrame = true
|
||||
w.nextFrame = at
|
||||
}
|
||||
}
|
||||
|
||||
func (c *callbacks) SetDriver(d driver) {
|
||||
if d == nil {
|
||||
panic("nil driver")
|
||||
}
|
||||
c.w.invMu.Lock()
|
||||
defer c.w.invMu.Unlock()
|
||||
c.w.driver = d
|
||||
}
|
||||
|
||||
func (c *callbacks) ProcessFrame(frame *op.Ops, ack chan<- struct{}) {
|
||||
c.w.processFrame(frame, ack)
|
||||
}
|
||||
|
||||
func (c *callbacks) ProcessEvent(e event.Event) bool {
|
||||
return c.w.processEvent(e)
|
||||
}
|
||||
|
||||
// SemanticRoot returns the ID of the semantic root.
|
||||
func (c *callbacks) SemanticRoot() input.SemanticID {
|
||||
c.w.updateSemantics()
|
||||
return c.w.semantic.root
|
||||
}
|
||||
|
||||
// LookupSemantic looks up a semantic node from an ID. The zero ID denotes the root.
|
||||
func (c *callbacks) LookupSemantic(semID input.SemanticID) (input.SemanticNode, bool) {
|
||||
c.w.updateSemantics()
|
||||
n, found := c.w.semantic.ids[semID]
|
||||
return n, found
|
||||
}
|
||||
|
||||
func (c *callbacks) AppendSemanticDiffs(diffs []input.SemanticID) []input.SemanticID {
|
||||
c.w.updateSemantics()
|
||||
if tree := c.w.semantic.prevTree; len(tree) > 0 {
|
||||
c.w.collectSemanticDiffs(&diffs, c.w.semantic.prevTree[0])
|
||||
}
|
||||
return diffs
|
||||
}
|
||||
|
||||
func (c *callbacks) SemanticAt(pos f32.Point) (input.SemanticID, bool) {
|
||||
c.w.updateSemantics()
|
||||
return c.w.queue.SemanticAt(pos)
|
||||
}
|
||||
|
||||
func (c *callbacks) EditorState() editorState {
|
||||
return c.w.imeState
|
||||
}
|
||||
|
||||
func (c *callbacks) SetComposingRegion(r key.Range) {
|
||||
c.w.imeState.compose = r
|
||||
c.w.driver.ProcessEvent(key.CompositionEvent(r))
|
||||
}
|
||||
|
||||
func (c *callbacks) EditorInsert(text string) {
|
||||
sel := c.w.imeState.Selection.Range
|
||||
c.EditorReplace(sel, text)
|
||||
start := min(sel.End, sel.Start)
|
||||
sel.Start = start + utf8.RuneCountInString(text)
|
||||
sel.End = sel.Start
|
||||
c.SetEditorSelection(sel)
|
||||
}
|
||||
|
||||
func (c *callbacks) EditorReplace(r key.Range, text string) {
|
||||
c.w.imeState.Replace(r, text)
|
||||
c.w.driver.ProcessEvent(key.EditEvent{Range: r, Text: text})
|
||||
c.w.driver.ProcessEvent(key.SnippetEvent(c.w.imeState.Snippet.Range))
|
||||
}
|
||||
|
||||
func (c *callbacks) SetEditorSelection(r key.Range) {
|
||||
c.w.imeState.Selection.Range = r
|
||||
c.w.driver.ProcessEvent(key.SelectionEvent(r))
|
||||
}
|
||||
|
||||
func (c *callbacks) SetEditorSnippet(r key.Range) {
|
||||
if sn := c.EditorState().Snippet.Range; sn == r {
|
||||
// No need to expand.
|
||||
return
|
||||
}
|
||||
c.w.driver.ProcessEvent(key.SnippetEvent(r))
|
||||
}
|
||||
|
||||
func (w *Window) moveFocus(dir key.FocusDirection) {
|
||||
w.queue.MoveFocus(dir)
|
||||
if _, handled := w.queue.WakeupTime(); handled {
|
||||
w.queue.RevealFocus(w.viewport)
|
||||
} else {
|
||||
var v image.Point
|
||||
switch dir {
|
||||
case key.FocusRight:
|
||||
v = image.Pt(+1, 0)
|
||||
case key.FocusLeft:
|
||||
v = image.Pt(-1, 0)
|
||||
case key.FocusDown:
|
||||
v = image.Pt(0, +1)
|
||||
case key.FocusUp:
|
||||
v = image.Pt(0, -1)
|
||||
default:
|
||||
return
|
||||
}
|
||||
const scrollABit = unit.Dp(50)
|
||||
dist := v.Mul(int(w.metric.Dp(scrollABit)))
|
||||
w.queue.ScrollFocus(dist)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *callbacks) ClickFocus() {
|
||||
c.w.queue.ClickFocus()
|
||||
c.w.setNextFrame(time.Time{})
|
||||
c.w.updateAnimation()
|
||||
}
|
||||
|
||||
func (c *callbacks) ActionAt(p f32.Point) (system.Action, bool) {
|
||||
return c.w.queue.ActionAt(p)
|
||||
}
|
||||
|
||||
func (w *Window) destroyGPU() {
|
||||
if w.gpu != nil {
|
||||
w.ctx.Lock()
|
||||
w.gpu.Release()
|
||||
w.ctx.Unlock()
|
||||
w.gpu = nil
|
||||
}
|
||||
if w.ctx != nil {
|
||||
w.ctx.Release()
|
||||
w.ctx = nil
|
||||
}
|
||||
}
|
||||
|
||||
// updateSemantics refreshes the semantics tree, the id to node map and the ids of
|
||||
// updated nodes.
|
||||
func (w *Window) updateSemantics() {
|
||||
if w.semantic.uptodate {
|
||||
return
|
||||
}
|
||||
w.semantic.uptodate = true
|
||||
w.semantic.prevTree, w.semantic.tree = w.semantic.tree, w.semantic.prevTree
|
||||
w.semantic.tree = w.queue.AppendSemantics(w.semantic.tree[:0])
|
||||
w.semantic.root = w.semantic.tree[0].ID
|
||||
for _, n := range w.semantic.tree {
|
||||
w.semantic.ids[n.ID] = n
|
||||
}
|
||||
}
|
||||
|
||||
// collectSemanticDiffs traverses the previous semantic tree, noting changed nodes.
|
||||
func (w *Window) collectSemanticDiffs(diffs *[]input.SemanticID, n input.SemanticNode) {
|
||||
newNode, exists := w.semantic.ids[n.ID]
|
||||
// Ignore deleted nodes, as their disappearance will be reported through an
|
||||
// ancestor node.
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
diff := newNode.Desc != n.Desc || len(n.Children) != len(newNode.Children)
|
||||
for i, ch := range n.Children {
|
||||
if !diff {
|
||||
newCh := newNode.Children[i]
|
||||
diff = ch.ID != newCh.ID
|
||||
}
|
||||
w.collectSemanticDiffs(diffs, ch)
|
||||
}
|
||||
if diff {
|
||||
*diffs = append(*diffs, n.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *callbacks) Invalidate() {
|
||||
c.w.setNextFrame(time.Time{})
|
||||
c.w.updateAnimation()
|
||||
// Guarantee a wakeup, even when not animating.
|
||||
c.w.processEvent(wakeupEvent{})
|
||||
}
|
||||
|
||||
func (c *callbacks) nextEvent() (event.Event, bool) {
|
||||
return c.w.nextEvent()
|
||||
}
|
||||
|
||||
func (w *Window) nextEvent() (event.Event, bool) {
|
||||
s := &w.coalesced
|
||||
defer func() {
|
||||
// Every event counts as a wakeup.
|
||||
s.wakeup = false
|
||||
}()
|
||||
switch {
|
||||
case s.framePending:
|
||||
// If the user didn't call FrameEvent.Event, process
|
||||
// an empty frame.
|
||||
w.processFrame(new(op.Ops), nil)
|
||||
case s.view != nil:
|
||||
e := *s.view
|
||||
s.view = nil
|
||||
return e, true
|
||||
case s.destroy != nil:
|
||||
e := *s.destroy
|
||||
// Clear pending events after DestroyEvent is delivered.
|
||||
*s = eventSummary{}
|
||||
return e, true
|
||||
case s.cfg != nil:
|
||||
e := *s.cfg
|
||||
s.cfg = nil
|
||||
return e, true
|
||||
case s.frame != nil:
|
||||
e := *s.frame
|
||||
s.frame = nil
|
||||
s.framePending = true
|
||||
return e.FrameEvent, true
|
||||
case s.wakeup:
|
||||
return wakeupEvent{}, true
|
||||
}
|
||||
w.invMu.Lock()
|
||||
defer w.invMu.Unlock()
|
||||
w.mayInvalidate = w.driver != nil
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (w *Window) processEvent(e event.Event) bool {
|
||||
switch e2 := e.(type) {
|
||||
case wakeupEvent:
|
||||
w.coalesced.wakeup = true
|
||||
case frameEvent:
|
||||
if e2.Size == (image.Point{}) {
|
||||
panic(errors.New("internal error: zero-sized Draw"))
|
||||
}
|
||||
w.metric = e2.Metric
|
||||
w.hasNextFrame = false
|
||||
e2.Frame = w.driver.Frame
|
||||
e2.Source = w.queue.Source()
|
||||
// Prepare the decorations and update the frame insets.
|
||||
viewport := image.Rectangle{
|
||||
Min: image.Point{
|
||||
X: e2.Metric.Dp(e2.Insets.Left),
|
||||
Y: e2.Metric.Dp(e2.Insets.Top),
|
||||
},
|
||||
Max: image.Point{
|
||||
X: e2.Size.X - e2.Metric.Dp(e2.Insets.Right),
|
||||
Y: e2.Size.Y - e2.Metric.Dp(e2.Insets.Bottom),
|
||||
},
|
||||
}
|
||||
// Scroll to focus if viewport is shrinking in any dimension.
|
||||
if old, new := w.viewport.Size(), viewport.Size(); new.X < old.X || new.Y < old.Y {
|
||||
w.queue.RevealFocus(viewport)
|
||||
}
|
||||
w.viewport = viewport
|
||||
wrapper := &w.decorations.Ops
|
||||
wrapper.Reset()
|
||||
m := op.Record(wrapper)
|
||||
offset := w.decorate(e2.FrameEvent, wrapper)
|
||||
w.lastFrame.deco = m.Stop()
|
||||
w.lastFrame.size = e2.Size
|
||||
w.lastFrame.sync = e2.Sync
|
||||
w.lastFrame.off = offset
|
||||
e2.Size = e2.Size.Sub(offset)
|
||||
w.coalesced.frame = &e2
|
||||
case DestroyEvent:
|
||||
if w.gpuErr != nil {
|
||||
e2.Err = w.gpuErr
|
||||
}
|
||||
w.destroyGPU()
|
||||
w.invMu.Lock()
|
||||
w.mayInvalidate = false
|
||||
w.driver = nil
|
||||
w.invMu.Unlock()
|
||||
if q := w.timer.quit; q != nil {
|
||||
q <- struct{}{}
|
||||
<-q
|
||||
}
|
||||
w.coalesced.destroy = &e2
|
||||
case ViewEvent:
|
||||
if !e2.Valid() && w.gpu != nil {
|
||||
w.ctx.Lock()
|
||||
w.gpu.Release()
|
||||
w.gpu = nil
|
||||
w.ctx.Unlock()
|
||||
}
|
||||
w.coalesced.view = &e2
|
||||
case ConfigEvent:
|
||||
w.decorations.Decorations.Maximized = e2.Config.Mode == Maximized
|
||||
wasFocused := w.decorations.Config.Focused
|
||||
w.decorations.Config = e2.Config
|
||||
e2.Config = w.effectiveConfig()
|
||||
w.coalesced.cfg = &e2
|
||||
if f := w.decorations.Config.Focused; f != wasFocused {
|
||||
w.queue.Queue(key.FocusEvent{Focus: f})
|
||||
}
|
||||
t, handled := w.queue.WakeupTime()
|
||||
if handled {
|
||||
w.setNextFrame(t)
|
||||
w.updateAnimation()
|
||||
}
|
||||
return handled
|
||||
case event.Event:
|
||||
focusDir := key.FocusDirection(-1)
|
||||
if e, ok := e2.(key.Event); ok && e.State == key.Press {
|
||||
isMobile := runtime.GOOS == "ios" || runtime.GOOS == "android"
|
||||
switch {
|
||||
case e.Name == key.NameTab && e.Modifiers == 0:
|
||||
focusDir = key.FocusForward
|
||||
case e.Name == key.NameTab && e.Modifiers == key.ModShift:
|
||||
focusDir = key.FocusBackward
|
||||
case e.Name == key.NameUpArrow && e.Modifiers == 0 && isMobile:
|
||||
focusDir = key.FocusUp
|
||||
case e.Name == key.NameDownArrow && e.Modifiers == 0 && isMobile:
|
||||
focusDir = key.FocusDown
|
||||
case e.Name == key.NameLeftArrow && e.Modifiers == 0 && isMobile:
|
||||
focusDir = key.FocusLeft
|
||||
case e.Name == key.NameRightArrow && e.Modifiers == 0 && isMobile:
|
||||
focusDir = key.FocusRight
|
||||
}
|
||||
}
|
||||
e := e2
|
||||
if focusDir != -1 {
|
||||
e = input.SystemEvent{Event: e}
|
||||
}
|
||||
w.queue.Queue(e)
|
||||
t, handled := w.queue.WakeupTime()
|
||||
if focusDir != -1 && !handled {
|
||||
w.moveFocus(focusDir)
|
||||
t, handled = w.queue.WakeupTime()
|
||||
}
|
||||
w.updateCursor()
|
||||
if handled {
|
||||
w.setNextFrame(t)
|
||||
w.updateAnimation()
|
||||
}
|
||||
return handled
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Event blocks until an event is received from the window, such as
|
||||
// [FrameEvent], or until [Invalidate] is called. The window is created
|
||||
// and shown the first time Event is called.
|
||||
func (w *Window) Event() event.Event {
|
||||
if w.driver == nil {
|
||||
w.init()
|
||||
}
|
||||
if w.driver == nil {
|
||||
e, ok := w.nextEvent()
|
||||
if !ok {
|
||||
panic("window initialization failed without a DestroyEvent")
|
||||
}
|
||||
return e
|
||||
}
|
||||
return w.driver.Event()
|
||||
}
|
||||
|
||||
func (w *Window) init() {
|
||||
debug.Parse()
|
||||
// Measure decoration height.
|
||||
deco := new(widget.Decorations)
|
||||
theme := material.NewTheme()
|
||||
theme.Shaper = text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Regular()))
|
||||
decoStyle := material.Decorations(theme, deco, 0, "")
|
||||
gtx := layout.Context{
|
||||
Ops: new(op.Ops),
|
||||
// Measure in Dp.
|
||||
Metric: unit.Metric{},
|
||||
}
|
||||
// Allow plenty of space.
|
||||
gtx.Constraints.Max.Y = 200
|
||||
dims := decoStyle.Layout(gtx)
|
||||
decoHeight := unit.Dp(dims.Size.Y)
|
||||
defaultOptions := []Option{
|
||||
Size(800, 600),
|
||||
Title("Gio"),
|
||||
Decorated(true),
|
||||
decoHeightOpt(decoHeight),
|
||||
}
|
||||
options := append(defaultOptions, w.initialOpts...)
|
||||
w.initialOpts = nil
|
||||
var cnf Config
|
||||
cnf.apply(unit.Metric{}, options)
|
||||
|
||||
w.nocontext = cnf.CustomRenderer
|
||||
w.decorations.Theme = theme
|
||||
w.decorations.Decorations = deco
|
||||
w.decorations.enabled = cnf.Decorated
|
||||
w.decorations.height = decoHeight
|
||||
w.imeState.compose = key.Range{Start: -1, End: -1}
|
||||
w.semantic.ids = make(map[input.SemanticID]input.SemanticNode)
|
||||
newWindow(&callbacks{w}, options)
|
||||
for _, acts := range w.initialActions {
|
||||
w.Perform(acts)
|
||||
}
|
||||
w.initialActions = nil
|
||||
}
|
||||
|
||||
func (w *Window) updateCursor() {
|
||||
if c := w.queue.Cursor(); c != w.cursor {
|
||||
w.cursor = c
|
||||
w.driver.SetCursor(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) fallbackDecorate() bool {
|
||||
cnf := w.decorations.Config
|
||||
return w.decorations.enabled && !cnf.Decorated && cnf.Mode != Fullscreen && !w.nocontext
|
||||
}
|
||||
|
||||
// decorate the window if enabled and returns the corresponding Insets.
|
||||
func (w *Window) decorate(e FrameEvent, o *op.Ops) image.Point {
|
||||
if !w.fallbackDecorate() {
|
||||
return image.Pt(0, 0)
|
||||
}
|
||||
deco := w.decorations.Decorations
|
||||
allActions := system.ActionMinimize | system.ActionMaximize | system.ActionUnmaximize |
|
||||
system.ActionClose | system.ActionMove
|
||||
style := material.Decorations(w.decorations.Theme, deco, allActions, w.decorations.Config.Title)
|
||||
// Update the decorations based on the current window mode.
|
||||
var actions system.Action
|
||||
switch m := w.decorations.Config.Mode; m {
|
||||
case Windowed:
|
||||
actions |= system.ActionUnmaximize
|
||||
case Minimized:
|
||||
actions |= system.ActionMinimize
|
||||
case Maximized:
|
||||
actions |= system.ActionMaximize
|
||||
case Fullscreen:
|
||||
actions |= system.ActionFullscreen
|
||||
default:
|
||||
panic(fmt.Errorf("unknown WindowMode %v", m))
|
||||
}
|
||||
gtx := layout.Context{
|
||||
Ops: o,
|
||||
Now: e.Now,
|
||||
Source: e.Source,
|
||||
Metric: e.Metric,
|
||||
Constraints: layout.Exact(e.Size),
|
||||
}
|
||||
// Update the window based on the actions on the decorations.
|
||||
opts, acts := splitActions(deco.Update(gtx))
|
||||
if len(opts) > 0 {
|
||||
w.driver.Configure(opts)
|
||||
}
|
||||
if acts != 0 {
|
||||
w.driver.Perform(acts)
|
||||
}
|
||||
style.Layout(gtx)
|
||||
// Offset to place the frame content below the decorations.
|
||||
decoHeight := gtx.Dp(w.decorations.Config.decoHeight)
|
||||
if w.decorations.currentHeight != decoHeight {
|
||||
w.decorations.currentHeight = decoHeight
|
||||
w.coalesced.cfg = &ConfigEvent{Config: w.effectiveConfig()}
|
||||
}
|
||||
return image.Pt(0, decoHeight)
|
||||
}
|
||||
|
||||
func (w *Window) effectiveConfig() Config {
|
||||
cnf := w.decorations.Config
|
||||
cnf.Size.Y -= w.decorations.currentHeight
|
||||
cnf.Decorated = w.decorations.enabled || cnf.Decorated
|
||||
return cnf
|
||||
}
|
||||
|
||||
// splitActions splits options from actions and return them and the remaining
|
||||
// actions.
|
||||
func splitActions(actions system.Action) ([]Option, system.Action) {
|
||||
var opts []Option
|
||||
walkActions(actions, func(action system.Action) {
|
||||
switch action {
|
||||
case system.ActionMinimize:
|
||||
opts = append(opts, Minimized.Option())
|
||||
case system.ActionMaximize:
|
||||
opts = append(opts, Maximized.Option())
|
||||
case system.ActionUnmaximize:
|
||||
opts = append(opts, Windowed.Option())
|
||||
case system.ActionFullscreen:
|
||||
opts = append(opts, Fullscreen.Option())
|
||||
default:
|
||||
return
|
||||
}
|
||||
actions &^= action
|
||||
})
|
||||
return opts, actions
|
||||
}
|
||||
|
||||
// Perform the actions on the window.
|
||||
func (w *Window) Perform(actions system.Action) {
|
||||
opts, acts := splitActions(actions)
|
||||
w.Option(opts...)
|
||||
if acts == 0 {
|
||||
return
|
||||
}
|
||||
if w.driver == nil {
|
||||
w.initialActions = append(w.initialActions, acts)
|
||||
return
|
||||
}
|
||||
w.Run(func() {
|
||||
w.driver.Perform(actions)
|
||||
})
|
||||
}
|
||||
|
||||
// Title sets the title of the window.
|
||||
func Title(t string) Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.Title = t
|
||||
}
|
||||
}
|
||||
|
||||
// Size sets the size of the window. The mode will be changed to Windowed.
|
||||
func Size(w, h unit.Dp) Option {
|
||||
if w <= 0 {
|
||||
panic("width must be larger than or equal to 0")
|
||||
}
|
||||
if h <= 0 {
|
||||
panic("height must be larger than or equal to 0")
|
||||
}
|
||||
return func(m unit.Metric, cnf *Config) {
|
||||
cnf.Mode = Windowed
|
||||
cnf.Size = image.Point{
|
||||
X: m.Dp(w),
|
||||
Y: m.Dp(h),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MaxSize sets the maximum size of the window.
|
||||
func MaxSize(w, h unit.Dp) Option {
|
||||
if w <= 0 {
|
||||
panic("width must be larger than or equal to 0")
|
||||
}
|
||||
if h <= 0 {
|
||||
panic("height must be larger than or equal to 0")
|
||||
}
|
||||
return func(m unit.Metric, cnf *Config) {
|
||||
cnf.MaxSize = image.Point{
|
||||
X: m.Dp(w),
|
||||
Y: m.Dp(h),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MinSize sets the minimum size of the window.
|
||||
func MinSize(w, h unit.Dp) Option {
|
||||
if w <= 0 {
|
||||
panic("width must be larger than or equal to 0")
|
||||
}
|
||||
if h <= 0 {
|
||||
panic("height must be larger than or equal to 0")
|
||||
}
|
||||
return func(m unit.Metric, cnf *Config) {
|
||||
cnf.MinSize = image.Point{
|
||||
X: m.Dp(w),
|
||||
Y: m.Dp(h),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StatusColor sets the color of the Android status bar.
|
||||
func StatusColor(color color.NRGBA) Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.StatusColor = color
|
||||
}
|
||||
}
|
||||
|
||||
// NavigationColor sets the color of the navigation bar on Android, or the address bar in browsers.
|
||||
func NavigationColor(color color.NRGBA) Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.NavigationColor = color
|
||||
}
|
||||
}
|
||||
|
||||
// CustomRenderer controls whether the window contents is
|
||||
// rendered by the client. If true, no GPU context is created.
|
||||
//
|
||||
// Caller must assume responsibility for rendering which includes
|
||||
// initializing the render backend, swapping the framebuffer and
|
||||
// handling frame pacing.
|
||||
func CustomRenderer(custom bool) Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.CustomRenderer = custom
|
||||
}
|
||||
}
|
||||
|
||||
// Decorated controls whether Gio and/or the platform are responsible
|
||||
// for drawing window decorations. Providing false indicates that
|
||||
// the application will either be undecorated or will draw its own decorations.
|
||||
func Decorated(enabled bool) Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.Decorated = enabled
|
||||
}
|
||||
}
|
||||
|
||||
// TopMost windows will be rendered above all other non-top-most windows.
|
||||
//
|
||||
// TopMost windows are supported on macOS, Windows.
|
||||
func TopMost(enabled bool) Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.TopMost = enabled
|
||||
}
|
||||
}
|
||||
|
||||
// flushEvent is sent to detect when the user program
|
||||
// has completed processing of all prior events. Its an
|
||||
// [io/event.Event] but only for internal use.
|
||||
type flushEvent struct{}
|
||||
|
||||
func (t flushEvent) ImplementsEvent() {}
|
||||
|
||||
// theFlushEvent avoids allocating garbage when sending
|
||||
// flushEvents.
|
||||
var theFlushEvent flushEvent
|
||||
Reference in New Issue
Block a user