mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-03 16:35:36 +00:00
app: merge app/internal/wm into package app
The app and app/internal/wm packages are tightly coupled, requiring quite a bit of forwarding types, values and constants from the internal package to export it. Further, no other package imports package wm. This change merges the two packages. While here, drop the pre-Go 1.14 SIGPIPE workaround. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
@@ -1,68 +0,0 @@
|
||||
// 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();
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package org.gioui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.content.res.Configuration;
|
||||
|
||||
public final class GioActivity extends Activity {
|
||||
private GioView view;
|
||||
|
||||
@Override public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
this.view = new GioView(this);
|
||||
|
||||
setContentView(view);
|
||||
}
|
||||
|
||||
@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 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();
|
||||
}
|
||||
}
|
||||
@@ -1,365 +0,0 @@
|
||||
// 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.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.text.Editable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Choreographer;
|
||||
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.BaseInputConnection;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.text.InputType;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public final class GioView extends SurfaceView implements Choreographer.FrameCallback {
|
||||
private static boolean jniLoaded;
|
||||
|
||||
private final SurfaceHolder.Callback surfCallbacks;
|
||||
private final View.OnFocusChangeListener focusCallback;
|
||||
private final InputMethodManager imm;
|
||||
private final float scrollXScale;
|
||||
private final float scrollYScale;
|
||||
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;
|
||||
}
|
||||
|
||||
nhandle = onCreateView(this);
|
||||
imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
focusCallback = new View.OnFocusChangeListener() {
|
||||
@Override public void onFocusChange(View v, boolean focus) {
|
||||
GioView.this.onFocusChange(nhandle, focus);
|
||||
}
|
||||
};
|
||||
setOnFocusChangeListener(focusCallback);
|
||||
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) {
|
||||
onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), 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 dispatchMotionEvent(MotionEvent event) {
|
||||
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) {
|
||||
editor.inputType = this.keyboardHint;
|
||||
editor.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
|
||||
return new InputConnection(this);
|
||||
}
|
||||
|
||||
void setInputHint(int hint) {
|
||||
if (hint == this.keyboardHint) {
|
||||
return;
|
||||
}
|
||||
this.keyboardHint = hint;
|
||||
imm.restartInput(this);
|
||||
}
|
||||
|
||||
void showTextInput() {
|
||||
GioView.this.requestFocus();
|
||||
imm.showSoftInput(GioView.this, 0);
|
||||
}
|
||||
|
||||
void hideTextInput() {
|
||||
imm.hideSoftInputFromWindow(getWindowToken(), 0);
|
||||
}
|
||||
|
||||
@Override protected boolean fitSystemWindows(Rect insets) {
|
||||
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) {
|
||||
onFrameCallback(nhandle, nanos);
|
||||
}
|
||||
|
||||
int getDensity() {
|
||||
return getResources().getDisplayMetrics().densityDpi;
|
||||
}
|
||||
|
||||
float getFontScale() {
|
||||
return getResources().getConfiguration().fontScale;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
onStartView(nhandle);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
onStopView(nhandle);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
setOnFocusChangeListener(null);
|
||||
getHolder().removeCallback(surfCallbacks);
|
||||
onDestroyView(nhandle);
|
||||
nhandle = 0;
|
||||
}
|
||||
|
||||
public void configurationChanged() {
|
||||
onConfigurationChanged(nhandle);
|
||||
}
|
||||
|
||||
public boolean backPressed() {
|
||||
return onBack(nhandle);
|
||||
}
|
||||
|
||||
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, long time);
|
||||
static private native void onFrameCallback(long handle, long nanos);
|
||||
static private native boolean onBack(long handle);
|
||||
static private native void onFocusChange(long handle, boolean focus);
|
||||
|
||||
private static class InputConnection extends BaseInputConnection {
|
||||
private final Editable editable;
|
||||
|
||||
InputConnection(View view) {
|
||||
// Passing false enables "dummy mode", where the BaseInputConnection
|
||||
// attempts to convert IME operations to key events.
|
||||
super(view, false);
|
||||
editable = Editable.Factory.getInstance().newEditable("");
|
||||
}
|
||||
|
||||
@Override public Editable getEditable() {
|
||||
return editable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package wm
|
||||
|
||||
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 debug = false
|
||||
|
||||
func init() {
|
||||
drivers = append(drivers, gpuAPI{
|
||||
priority: 1,
|
||||
initializer: func(w *window) (Context, error) {
|
||||
hwnd, _, _ := w.HWND()
|
||||
var flags uint32
|
||||
if debug {
|
||||
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 {
|
||||
return gpu.Direct3D11RenderTarget{
|
||||
RenderTarget: unsafe.Pointer(c.renderTarget),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *d3d11Context) Present() error {
|
||||
err := c.swchain.Present(1, 0)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
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 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 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 debug {
|
||||
d3d11.ReportLiveObjects()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *d3d11Context) releaseFBO() {
|
||||
if c.renderTarget != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(c.renderTarget), c.renderTarget.Vtbl.Release)
|
||||
c.renderTarget = nil
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package wm
|
||||
|
||||
/*
|
||||
#include <android/native_window_jni.h>
|
||||
#include <EGL/egl.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/internal/egl"
|
||||
)
|
||||
|
||||
type context struct {
|
||||
win *window
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
ctx, err := egl.NewContext(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &context{win: w, Context: ctx}, nil
|
||||
}
|
||||
|
||||
func (c *context) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) Refresh() error {
|
||||
c.Context.ReleaseSurface()
|
||||
var (
|
||||
win *C.ANativeWindow
|
||||
width, height int
|
||||
)
|
||||
win, width, height = c.win.nativeWindow(c.Context.VisualID())
|
||||
if win == nil {
|
||||
return nil
|
||||
}
|
||||
eglSurf := egl.NativeWindowType(unsafe.Pointer(win))
|
||||
return c.Context.CreateSurface(eglSurf, width, height)
|
||||
}
|
||||
|
||||
func (c *context) Lock() error {
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *context) Unlock() {
|
||||
c.Context.ReleaseCurrent()
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build (linux && !android && !nowayland) || freebsd
|
||||
// +build linux,!android,!nowayland freebsd
|
||||
|
||||
package wm
|
||||
|
||||
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 context struct {
|
||||
win *window
|
||||
*egl.Context
|
||||
eglWin *C.struct_wl_egl_window
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
disp := egl.NativeDisplayType(unsafe.Pointer(w.display()))
|
||||
ctx, err := egl.NewContext(disp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &context{Context: ctx, win: w}, nil
|
||||
}
|
||||
|
||||
func (c *context) 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 *context) Refresh() error {
|
||||
c.Context.ReleaseSurface()
|
||||
if c.eglWin != nil {
|
||||
C.wl_egl_window_destroy(c.eglWin)
|
||||
c.eglWin = nil
|
||||
}
|
||||
surf, width, height := c.win.surface()
|
||||
if surf == nil {
|
||||
return errors.New("wayland: no surface")
|
||||
}
|
||||
eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
|
||||
if eglWin == nil {
|
||||
return errors.New("wayland: wl_egl_window_create failed")
|
||||
}
|
||||
c.eglWin = eglWin
|
||||
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
||||
return c.Context.CreateSurface(eglSurf, width, height)
|
||||
}
|
||||
|
||||
func (c *context) Lock() error {
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *context) Unlock() {
|
||||
c.Context.ReleaseCurrent()
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package wm
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"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
|
||||
}
|
||||
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 {
|
||||
c.Context.ReleaseSurface()
|
||||
var (
|
||||
win windows.Handle
|
||||
width, height int
|
||||
)
|
||||
win, width, height = c.win.HWND()
|
||||
eglSurf := egl.NativeWindowType(win)
|
||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Context.MakeCurrent(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Context.EnableVSync(true)
|
||||
c.Context.ReleaseCurrent()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Lock() error {
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *glContext) Unlock() {
|
||||
c.Context.ReleaseCurrent()
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build (linux && !android && !nox11) || freebsd || openbsd
|
||||
// +build linux,!android,!nox11 freebsd openbsd
|
||||
|
||||
package wm
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/internal/egl"
|
||||
)
|
||||
|
||||
type x11Context struct {
|
||||
win *x11Window
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func (w *x11Window) NewContext() (Context, error) {
|
||||
disp := egl.NativeDisplayType(unsafe.Pointer(w.display()))
|
||||
ctx, err := egl.NewContext(disp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
c.Context.ReleaseSurface()
|
||||
win, width, height := c.win.window()
|
||||
eglSurf := egl.NativeWindowType(uintptr(win))
|
||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Context.MakeCurrent(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Context.EnableVSync(true)
|
||||
c.Context.ReleaseCurrent()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *x11Context) Lock() error {
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *x11Context) Unlock() {
|
||||
c.Context.ReleaseCurrent()
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
#include <UIKit/UIKit.h>
|
||||
|
||||
@interface GioViewController : UIViewController
|
||||
@end
|
||||
@@ -1,144 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && ios && nometal
|
||||
// +build darwin,ios,nometal
|
||||
|
||||
package wm
|
||||
|
||||
/*
|
||||
@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"
|
||||
|
||||
"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 {
|
||||
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)
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package wm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall/js"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/gl"
|
||||
)
|
||||
|
||||
type context struct {
|
||||
ctx js.Value
|
||||
cnv js.Value
|
||||
}
|
||||
|
||||
func newContext(w *window) (*context, 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 := &context{
|
||||
ctx: ctx,
|
||||
cnv: w.cnv,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *context) RenderTarget() gpu.RenderTarget {
|
||||
return gpu.OpenGLRenderTarget{}
|
||||
}
|
||||
|
||||
func (c *context) API() gpu.API {
|
||||
return gpu.OpenGL{Context: gl.Context(c.ctx)}
|
||||
}
|
||||
|
||||
func (c *context) Release() {
|
||||
}
|
||||
|
||||
func (c *context) Present() error {
|
||||
if c.ctx.Call("isContextLost").Bool() {
|
||||
return errors.New("context lost")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Lock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Unlock() {}
|
||||
|
||||
func (c *context) Refresh() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
return newContext(w)
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && !ios && nometal
|
||||
// +build darwin,!ios,nometal
|
||||
|
||||
package wm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/internal/gl"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <AppKit/AppKit.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);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type context struct {
|
||||
c *gl.Functions
|
||||
ctx C.CFTypeRef
|
||||
view C.CFTypeRef
|
||||
}
|
||||
|
||||
func newContext(w *window) (*context, error) {
|
||||
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 := &context{
|
||||
ctx: ctx,
|
||||
view: view,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *context) RenderTarget() gpu.RenderTarget {
|
||||
return gpu.OpenGLRenderTarget{}
|
||||
}
|
||||
|
||||
func (c *context) API() gpu.API {
|
||||
return gpu.OpenGL{}
|
||||
}
|
||||
|
||||
func (c *context) Release() {
|
||||
if c.ctx != 0 {
|
||||
C.gio_clearCurrentContext()
|
||||
C.CFRelease(c.ctx)
|
||||
c.ctx = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) Present() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Lock() error {
|
||||
C.gio_lockContext(c.ctx)
|
||||
C.gio_makeCurrentContext(c.ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Unlock() {
|
||||
C.gio_clearCurrentContext()
|
||||
C.gio_unlockContext(c.ctx)
|
||||
}
|
||||
|
||||
func (c *context) Refresh() error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
C.gio_updateContext(c.ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
return newContext(w)
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,!ios,nometal
|
||||
|
||||
@import AppKit;
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#include "_cgo_export.h"
|
||||
|
||||
CALayer *gio_layerFactory(void) {
|
||||
@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]);
|
||||
}
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !nometal
|
||||
// +build !nometal
|
||||
|
||||
package wm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -xobjective-c -fmodules -fobjc-arc
|
||||
|
||||
@import Metal;
|
||||
@import QuartzCore.CAMetalLayer;
|
||||
|
||||
#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 presentDrawable:drawable];
|
||||
[cmdBuffer commit];
|
||||
[cmdBuffer waitUntilCompleted];
|
||||
}
|
||||
}
|
||||
|
||||
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 layer == 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 {
|
||||
if c.drawable != 0 || c.texture != 0 {
|
||||
panic("a previous RenderTarget wasn't Presented")
|
||||
}
|
||||
c.drawable = C.nextDrawable(c.layer)
|
||||
if c.drawable == 0 {
|
||||
panic("metal: [CAMetalLayer nextDrawable] failed")
|
||||
}
|
||||
c.texture = C.drawableTexture(c.drawable)
|
||||
if c.texture == 0 {
|
||||
panic("metal: CADrawable.texture is nil")
|
||||
}
|
||||
return gpu.MetalRenderTarget{
|
||||
Texture: unsafe.Pointer(c.texture),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *mtlContext) API() gpu.API {
|
||||
return gpu.Metal{
|
||||
Device: unsafe.Pointer(c.dev),
|
||||
Queue: unsafe.Pointer(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)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !nometal
|
||||
// +build !nometal
|
||||
|
||||
package wm
|
||||
|
||||
/*
|
||||
#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;
|
||||
return CFBridgingRetain(view.layer);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && !ios && !nometal
|
||||
// +build darwin,!ios,!nometal
|
||||
|
||||
package wm
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -xobjective-c -fmodules -fobjc-arc
|
||||
|
||||
@import AppKit;
|
||||
|
||||
@import QuartzCore.CAMetalLayer;
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
CALayer *gio_layerFactory(void) {
|
||||
@autoreleasepool {
|
||||
return [CAMetalLayer layer];
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -1,871 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package wm
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror
|
||||
#cgo LDFLAGS: -landroid
|
||||
|
||||
#include <android/native_window_jni.h>
|
||||
#include <android/configuration.h>
|
||||
#include <android/keycodes.h>
|
||||
#include <android/input.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static jint jni_GetEnv(JavaVM *vm, JNIEnv **env, jint version) {
|
||||
return (*vm)->GetEnv(vm, (void **)env, version);
|
||||
}
|
||||
|
||||
static jint jni_GetJavaVM(JNIEnv *env, JavaVM **jvm) {
|
||||
return (*env)->GetJavaVM(env, jvm);
|
||||
}
|
||||
|
||||
static jint jni_AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args) {
|
||||
return (*vm)->AttachCurrentThread(vm, p_env, thr_args);
|
||||
}
|
||||
|
||||
static jint jni_DetachCurrentThread(JavaVM *vm) {
|
||||
return (*vm)->DetachCurrentThread(vm);
|
||||
}
|
||||
|
||||
static jobject jni_NewGlobalRef(JNIEnv *env, jobject obj) {
|
||||
return (*env)->NewGlobalRef(env, obj);
|
||||
}
|
||||
|
||||
static void jni_DeleteGlobalRef(JNIEnv *env, jobject obj) {
|
||||
(*env)->DeleteGlobalRef(env, obj);
|
||||
}
|
||||
|
||||
static jclass jni_GetObjectClass(JNIEnv *env, jobject obj) {
|
||||
return (*env)->GetObjectClass(env, obj);
|
||||
}
|
||||
|
||||
static jmethodID jni_GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
|
||||
return (*env)->GetMethodID(env, clazz, name, sig);
|
||||
}
|
||||
|
||||
static jmethodID jni_GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
|
||||
return (*env)->GetStaticMethodID(env, clazz, name, sig);
|
||||
}
|
||||
|
||||
static jfloat jni_CallFloatMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
|
||||
return (*env)->CallFloatMethod(env, obj, methodID);
|
||||
}
|
||||
|
||||
static jint jni_CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
|
||||
return (*env)->CallIntMethod(env, obj, methodID);
|
||||
}
|
||||
|
||||
static void jni_CallStaticVoidMethodA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args) {
|
||||
(*env)->CallStaticVoidMethodA(env, cls, methodID, args);
|
||||
}
|
||||
|
||||
static void jni_CallVoidMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args) {
|
||||
(*env)->CallVoidMethodA(env, obj, methodID, args);
|
||||
}
|
||||
|
||||
static jbyte *jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr) {
|
||||
return (*env)->GetByteArrayElements(env, arr, NULL);
|
||||
}
|
||||
|
||||
static void jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes) {
|
||||
(*env)->ReleaseByteArrayElements(env, arr, bytes, JNI_ABORT);
|
||||
}
|
||||
|
||||
static jsize jni_GetArrayLength(JNIEnv *env, jbyteArray arr) {
|
||||
return (*env)->GetArrayLength(env, arr);
|
||||
}
|
||||
|
||||
static jstring jni_NewString(JNIEnv *env, const jchar *unicodeChars, jsize len) {
|
||||
return (*env)->NewString(env, unicodeChars, len);
|
||||
}
|
||||
|
||||
static jsize jni_GetStringLength(JNIEnv *env, jstring str) {
|
||||
return (*env)->GetStringLength(env, str);
|
||||
}
|
||||
|
||||
static const jchar *jni_GetStringChars(JNIEnv *env, jstring str) {
|
||||
return (*env)->GetStringChars(env, str, NULL);
|
||||
}
|
||||
|
||||
static jthrowable jni_ExceptionOccurred(JNIEnv *env) {
|
||||
return (*env)->ExceptionOccurred(env);
|
||||
}
|
||||
|
||||
static void jni_ExceptionClear(JNIEnv *env) {
|
||||
(*env)->ExceptionClear(env);
|
||||
}
|
||||
|
||||
static jobject jni_CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args) {
|
||||
return (*env)->CallObjectMethodA(env, obj, method, args);
|
||||
}
|
||||
|
||||
static jobject jni_CallStaticObjectMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args) {
|
||||
return (*env)->CallStaticObjectMethodA(env, cls, method, args);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/internal/f32color"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type window struct {
|
||||
callbacks Callbacks
|
||||
|
||||
view C.jobject
|
||||
|
||||
dpi int
|
||||
fontScale float32
|
||||
insets system.Insets
|
||||
|
||||
stage system.Stage
|
||||
started bool
|
||||
animating bool
|
||||
|
||||
win *C.ANativeWindow
|
||||
}
|
||||
|
||||
// gioView hold cached JNI methods for GioView.
|
||||
var gioView struct {
|
||||
once sync.Once
|
||||
getDensity C.jmethodID
|
||||
getFontScale C.jmethodID
|
||||
showTextInput C.jmethodID
|
||||
hideTextInput C.jmethodID
|
||||
setInputHint C.jmethodID
|
||||
postFrameCallback C.jmethodID
|
||||
setCursor C.jmethodID
|
||||
setOrientation C.jmethodID
|
||||
setNavigationColor C.jmethodID
|
||||
setStatusColor C.jmethodID
|
||||
setFullscreen C.jmethodID
|
||||
}
|
||||
|
||||
// ViewEvent is sent whenever the Window's underlying Android view
|
||||
// changes.
|
||||
type ViewEvent struct {
|
||||
// View is a JNI global reference to the android.view.View
|
||||
// instance backing the Window. The reference is valid until
|
||||
// the next ViewEvent is received.
|
||||
// A zero View means that there is currently no view attached.
|
||||
View uintptr
|
||||
}
|
||||
|
||||
type jvalue uint64 // The largest JNI type fits in 64 bits.
|
||||
|
||||
var dataDirChan = make(chan string, 1)
|
||||
|
||||
var android struct {
|
||||
// mu protects all fields of this structure. However, once a
|
||||
// non-nil jvm is returned from javaVM, all the other fields may
|
||||
// be accessed unlocked.
|
||||
mu sync.Mutex
|
||||
jvm *C.JavaVM
|
||||
|
||||
// appCtx is the global Android App context.
|
||||
appCtx C.jobject
|
||||
// gioCls is the class of the Gio class.
|
||||
gioCls C.jclass
|
||||
|
||||
mwriteClipboard C.jmethodID
|
||||
mreadClipboard C.jmethodID
|
||||
mwakeupMainThread C.jmethodID
|
||||
}
|
||||
|
||||
// view maps from GioView JNI refenreces to windows.
|
||||
var views = make(map[C.jlong]*window)
|
||||
|
||||
// windows maps from Callbacks to windows
|
||||
var windows = make(map[Callbacks]*window)
|
||||
|
||||
var mainWindow = newWindowRendezvous()
|
||||
|
||||
var mainFuncs = make(chan func(env *C.JNIEnv), 1)
|
||||
|
||||
func getMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
|
||||
m := C.CString(method)
|
||||
defer C.free(unsafe.Pointer(m))
|
||||
s := C.CString(sig)
|
||||
defer C.free(unsafe.Pointer(s))
|
||||
jm := C.jni_GetMethodID(env, class, m, s)
|
||||
if err := exception(env); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return jm
|
||||
}
|
||||
|
||||
func getStaticMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
|
||||
m := C.CString(method)
|
||||
defer C.free(unsafe.Pointer(m))
|
||||
s := C.CString(sig)
|
||||
defer C.free(unsafe.Pointer(s))
|
||||
jm := C.jni_GetStaticMethodID(env, class, m, s)
|
||||
if err := exception(env); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return jm
|
||||
}
|
||||
|
||||
//export Java_org_gioui_Gio_runGoMain
|
||||
func Java_org_gioui_Gio_runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyteArray, context C.jobject) {
|
||||
initJVM(env, class, context)
|
||||
dirBytes := C.jni_GetByteArrayElements(env, jdataDir)
|
||||
if dirBytes == nil {
|
||||
panic("runGoMain: GetByteArrayElements failed")
|
||||
}
|
||||
n := C.jni_GetArrayLength(env, jdataDir)
|
||||
dataDir := C.GoStringN((*C.char)(unsafe.Pointer(dirBytes)), n)
|
||||
dataDirChan <- dataDir
|
||||
C.jni_ReleaseByteArrayElements(env, jdataDir, dirBytes)
|
||||
|
||||
runMain()
|
||||
}
|
||||
|
||||
func initJVM(env *C.JNIEnv, gio C.jclass, ctx C.jobject) {
|
||||
android.mu.Lock()
|
||||
defer android.mu.Unlock()
|
||||
if res := C.jni_GetJavaVM(env, &android.jvm); res != 0 {
|
||||
panic("gio: GetJavaVM failed")
|
||||
}
|
||||
android.appCtx = C.jni_NewGlobalRef(env, ctx)
|
||||
android.gioCls = C.jclass(C.jni_NewGlobalRef(env, C.jobject(gio)))
|
||||
android.mwriteClipboard = getStaticMethodID(env, gio, "writeClipboard", "(Landroid/content/Context;Ljava/lang/String;)V")
|
||||
android.mreadClipboard = getStaticMethodID(env, gio, "readClipboard", "(Landroid/content/Context;)Ljava/lang/String;")
|
||||
android.mwakeupMainThread = getStaticMethodID(env, gio, "wakeupMainThread", "()V")
|
||||
}
|
||||
|
||||
func JavaVM() uintptr {
|
||||
jvm := javaVM()
|
||||
return uintptr(unsafe.Pointer(jvm))
|
||||
}
|
||||
|
||||
func javaVM() *C.JavaVM {
|
||||
android.mu.Lock()
|
||||
defer android.mu.Unlock()
|
||||
return android.jvm
|
||||
}
|
||||
|
||||
func AppContext() uintptr {
|
||||
android.mu.Lock()
|
||||
defer android.mu.Unlock()
|
||||
return uintptr(android.appCtx)
|
||||
}
|
||||
|
||||
func GetDataDir() string {
|
||||
return <-dataDirChan
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onCreateView
|
||||
func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong {
|
||||
gioView.once.Do(func() {
|
||||
m := &gioView
|
||||
m.getDensity = getMethodID(env, class, "getDensity", "()I")
|
||||
m.getFontScale = getMethodID(env, class, "getFontScale", "()F")
|
||||
m.showTextInput = getMethodID(env, class, "showTextInput", "()V")
|
||||
m.hideTextInput = getMethodID(env, class, "hideTextInput", "()V")
|
||||
m.setInputHint = getMethodID(env, class, "setInputHint", "(I)V")
|
||||
m.postFrameCallback = getMethodID(env, class, "postFrameCallback", "()V")
|
||||
m.setCursor = getMethodID(env, class, "setCursor", "(I)V")
|
||||
m.setOrientation = getMethodID(env, class, "setOrientation", "(II)V")
|
||||
m.setNavigationColor = getMethodID(env, class, "setNavigationColor", "(II)V")
|
||||
m.setStatusColor = getMethodID(env, class, "setStatusColor", "(II)V")
|
||||
m.setFullscreen = getMethodID(env, class, "setFullscreen", "(Z)V")
|
||||
})
|
||||
view = C.jni_NewGlobalRef(env, view)
|
||||
wopts := <-mainWindow.out
|
||||
w, ok := windows[wopts.window]
|
||||
if !ok {
|
||||
w = &window{
|
||||
callbacks: wopts.window,
|
||||
}
|
||||
windows[wopts.window] = w
|
||||
}
|
||||
w.view = view
|
||||
w.callbacks.SetDriver(w)
|
||||
handle := C.jlong(view)
|
||||
views[handle] = w
|
||||
w.loadConfig(env, class)
|
||||
w.Option(wopts.opts)
|
||||
w.setStage(system.StagePaused)
|
||||
w.callbacks.Event(ViewEvent{View: uintptr(view)})
|
||||
return handle
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onDestroyView
|
||||
func Java_org_gioui_GioView_onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := views[handle]
|
||||
w.callbacks.Event(ViewEvent{View: 0})
|
||||
w.callbacks.SetDriver(nil)
|
||||
delete(views, handle)
|
||||
C.jni_DeleteGlobalRef(env, w.view)
|
||||
w.view = 0
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onStopView
|
||||
func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := views[handle]
|
||||
w.started = false
|
||||
w.setStage(system.StagePaused)
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onStartView
|
||||
func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := views[handle]
|
||||
w.started = true
|
||||
if w.win != nil {
|
||||
w.setVisible()
|
||||
}
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onSurfaceDestroyed
|
||||
func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := views[handle]
|
||||
w.win = nil
|
||||
w.setStage(system.StagePaused)
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onSurfaceChanged
|
||||
func Java_org_gioui_GioView_onSurfaceChanged(env *C.JNIEnv, class C.jclass, handle C.jlong, surf C.jobject) {
|
||||
w := views[handle]
|
||||
w.win = C.ANativeWindow_fromSurface(env, surf)
|
||||
if w.started {
|
||||
w.setVisible()
|
||||
}
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onLowMemory
|
||||
func Java_org_gioui_GioView_onLowMemory(env *C.JNIEnv, class C.jclass) {
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onConfigurationChanged
|
||||
func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
|
||||
w := views[view]
|
||||
w.loadConfig(env, class)
|
||||
if w.stage >= system.StageRunning {
|
||||
w.draw(true)
|
||||
}
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onFrameCallback
|
||||
func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view C.jlong, nanos C.jlong) {
|
||||
w, exist := views[view]
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
if w.stage < system.StageRunning {
|
||||
return
|
||||
}
|
||||
anim := w.animating
|
||||
if anim {
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
||||
})
|
||||
w.draw(false)
|
||||
}
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onBack
|
||||
func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
|
||||
w := views[view]
|
||||
ev := &system.CommandEvent{Type: system.CommandBack}
|
||||
w.callbacks.Event(ev)
|
||||
if ev.Cancel {
|
||||
return C.JNI_TRUE
|
||||
}
|
||||
return C.JNI_FALSE
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onFocusChange
|
||||
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
||||
w := views[view]
|
||||
go w.callbacks.Event(key.FocusEvent{Focus: focus == C.JNI_TRUE})
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onWindowInsets
|
||||
func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C.jlong, top, right, bottom, left C.jint) {
|
||||
w := views[view]
|
||||
w.insets = system.Insets{
|
||||
Top: unit.Px(float32(top)),
|
||||
Right: unit.Px(float32(right)),
|
||||
Bottom: unit.Px(float32(bottom)),
|
||||
Left: unit.Px(float32(left)),
|
||||
}
|
||||
if w.stage >= system.StageRunning {
|
||||
w.draw(true)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) setVisible() {
|
||||
width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win)
|
||||
if width == 0 || height == 0 {
|
||||
return
|
||||
}
|
||||
w.setStage(system.StageRunning)
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
func (w *window) setStage(stage system.Stage) {
|
||||
if stage == w.stage {
|
||||
return
|
||||
}
|
||||
w.stage = stage
|
||||
w.callbacks.Event(system.StageEvent{stage})
|
||||
}
|
||||
|
||||
func (w *window) nativeWindow(visID int) (*C.ANativeWindow, int, int) {
|
||||
var width, height int
|
||||
if w.win != nil {
|
||||
if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
|
||||
panic(errors.New("ANativeWindow_setBuffersGeometry failed"))
|
||||
}
|
||||
w, h := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win)
|
||||
width, height = int(w), int(h)
|
||||
}
|
||||
return w.win, width, height
|
||||
}
|
||||
|
||||
func (w *window) loadConfig(env *C.JNIEnv, class C.jclass) {
|
||||
dpi := int(C.jni_CallIntMethod(env, w.view, gioView.getDensity))
|
||||
w.fontScale = float32(C.jni_CallFloatMethod(env, w.view, gioView.getFontScale))
|
||||
switch dpi {
|
||||
case C.ACONFIGURATION_DENSITY_NONE,
|
||||
C.ACONFIGURATION_DENSITY_DEFAULT,
|
||||
C.ACONFIGURATION_DENSITY_ANY:
|
||||
// Assume standard density.
|
||||
w.dpi = C.ACONFIGURATION_DENSITY_MEDIUM
|
||||
default:
|
||||
w.dpi = int(dpi)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
w.animating = anim
|
||||
if anim {
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win)
|
||||
if width == 0 || height == 0 {
|
||||
return
|
||||
}
|
||||
const inchPrDp = 1.0 / 160
|
||||
ppdp := float32(w.dpi) * inchPrDp
|
||||
w.callbacks.Event(FrameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: int(width),
|
||||
Y: int(height),
|
||||
},
|
||||
Insets: w.insets,
|
||||
Metric: unit.Metric{
|
||||
PxPerDp: ppdp,
|
||||
PxPerSp: w.fontScale * ppdp,
|
||||
},
|
||||
},
|
||||
Sync: sync,
|
||||
})
|
||||
}
|
||||
|
||||
type keyMapper func(devId, keyCode C.int32_t) rune
|
||||
|
||||
func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) {
|
||||
if jvm == nil {
|
||||
panic("nil JVM")
|
||||
}
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
var env *C.JNIEnv
|
||||
if res := C.jni_GetEnv(jvm, &env, C.JNI_VERSION_1_6); res != C.JNI_OK {
|
||||
if res != C.JNI_EDETACHED {
|
||||
panic(fmt.Errorf("JNI GetEnv failed with error %d", res))
|
||||
}
|
||||
if C.jni_AttachCurrentThread(jvm, &env, nil) != C.JNI_OK {
|
||||
panic(errors.New("runInJVM: AttachCurrentThread failed"))
|
||||
}
|
||||
defer C.jni_DetachCurrentThread(jvm)
|
||||
}
|
||||
|
||||
f(env)
|
||||
}
|
||||
|
||||
func convertKeyCode(code C.jint) (string, bool) {
|
||||
var n string
|
||||
switch code {
|
||||
case C.AKEYCODE_DPAD_UP:
|
||||
n = key.NameUpArrow
|
||||
case C.AKEYCODE_DPAD_DOWN:
|
||||
n = key.NameDownArrow
|
||||
case C.AKEYCODE_DPAD_LEFT:
|
||||
n = key.NameLeftArrow
|
||||
case C.AKEYCODE_DPAD_RIGHT:
|
||||
n = key.NameRightArrow
|
||||
case C.AKEYCODE_FORWARD_DEL:
|
||||
n = key.NameDeleteForward
|
||||
case C.AKEYCODE_DEL:
|
||||
n = key.NameDeleteBackward
|
||||
case C.AKEYCODE_NUMPAD_ENTER:
|
||||
n = key.NameEnter
|
||||
case C.AKEYCODE_ENTER:
|
||||
n = key.NameEnter
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onKeyEvent
|
||||
func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, keyCode, r C.jint, t C.jlong) {
|
||||
w := views[handle]
|
||||
if n, ok := convertKeyCode(keyCode); ok {
|
||||
w.callbacks.Event(key.Event{Name: n})
|
||||
}
|
||||
if r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224).
|
||||
w.callbacks.Event(key.EditEvent{Text: string(rune(r))})
|
||||
}
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onTouchEvent
|
||||
func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, action, pointerID, tool C.jint, x, y, scrollX, scrollY C.jfloat, jbtns C.jint, t C.jlong) {
|
||||
w := views[handle]
|
||||
var typ pointer.Type
|
||||
switch action {
|
||||
case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN:
|
||||
typ = pointer.Press
|
||||
case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP:
|
||||
typ = pointer.Release
|
||||
case C.AMOTION_EVENT_ACTION_CANCEL:
|
||||
typ = pointer.Cancel
|
||||
case C.AMOTION_EVENT_ACTION_MOVE:
|
||||
typ = pointer.Move
|
||||
case C.AMOTION_EVENT_ACTION_SCROLL:
|
||||
typ = pointer.Scroll
|
||||
default:
|
||||
return
|
||||
}
|
||||
var src pointer.Source
|
||||
var btns pointer.Buttons
|
||||
if jbtns&C.AMOTION_EVENT_BUTTON_PRIMARY != 0 {
|
||||
btns |= pointer.ButtonPrimary
|
||||
}
|
||||
if jbtns&C.AMOTION_EVENT_BUTTON_SECONDARY != 0 {
|
||||
btns |= pointer.ButtonSecondary
|
||||
}
|
||||
if jbtns&C.AMOTION_EVENT_BUTTON_TERTIARY != 0 {
|
||||
btns |= pointer.ButtonTertiary
|
||||
}
|
||||
switch tool {
|
||||
case C.AMOTION_EVENT_TOOL_TYPE_FINGER:
|
||||
src = pointer.Touch
|
||||
case C.AMOTION_EVENT_TOOL_TYPE_MOUSE:
|
||||
src = pointer.Mouse
|
||||
case C.AMOTION_EVENT_TOOL_TYPE_UNKNOWN:
|
||||
// For example, triggered via 'adb shell input tap'.
|
||||
// Instead of discarding it, treat it as a touch event.
|
||||
src = pointer.Touch
|
||||
default:
|
||||
return
|
||||
}
|
||||
w.callbacks.Event(pointer.Event{
|
||||
Type: typ,
|
||||
Source: src,
|
||||
Buttons: btns,
|
||||
PointerID: pointer.ID(pointerID),
|
||||
Time: time.Duration(t) * time.Millisecond,
|
||||
Position: f32.Point{X: float32(x), Y: float32(y)},
|
||||
Scroll: f32.Pt(float32(scrollX), float32(scrollY)),
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
if show {
|
||||
callVoidMethod(env, w.view, gioView.showTextInput)
|
||||
} else {
|
||||
callVoidMethod(env, w.view, gioView.hideTextInput)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) SetInputHint(mode key.InputHint) {
|
||||
// Constants defined at https://developer.android.com/reference/android/text/InputType.
|
||||
const (
|
||||
TYPE_NULL = 0
|
||||
TYPE_CLASS_NUMBER = 2
|
||||
TYPE_NUMBER_FLAG_DECIMAL = 8192
|
||||
TYPE_NUMBER_FLAG_SIGNED = 4096
|
||||
TYPE_TEXT_FLAG_NO_SUGGESTIONS = 524288
|
||||
TYPE_TEXT_VARIATION_VISIBLE_PASSWORD = 144
|
||||
)
|
||||
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
var m jvalue
|
||||
switch mode {
|
||||
case key.HintNumeric:
|
||||
m = TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_DECIMAL | TYPE_NUMBER_FLAG_SIGNED
|
||||
default:
|
||||
// TYPE_NULL, since TYPE_CLASS_TEXT isn't currently supported.
|
||||
m = TYPE_NULL
|
||||
}
|
||||
|
||||
// The TYPE_TEXT_FLAG_NO_SUGGESTIONS and TYPE_TEXT_VARIATION_VISIBLE_PASSWORD are used to fix the
|
||||
// Samsung keyboard compatibility, forcing to disable the suggests/auto-complete. gio#116.
|
||||
m = m | TYPE_TEXT_FLAG_NO_SUGGESTIONS | TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
|
||||
callVoidMethod(env, w.view, gioView.setInputHint, m)
|
||||
})
|
||||
}
|
||||
|
||||
func javaString(env *C.JNIEnv, str string) C.jstring {
|
||||
if str == "" {
|
||||
return 0
|
||||
}
|
||||
utf16Chars := utf16.Encode([]rune(str))
|
||||
return C.jni_NewString(env, (*C.jchar)(unsafe.Pointer(&utf16Chars[0])), C.int(len(utf16Chars)))
|
||||
}
|
||||
|
||||
func varArgs(args []jvalue) *C.jvalue {
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
return (*C.jvalue)(unsafe.Pointer(&args[0]))
|
||||
}
|
||||
|
||||
func callStaticVoidMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) error {
|
||||
C.jni_CallStaticVoidMethodA(env, cls, method, varArgs(args))
|
||||
return exception(env)
|
||||
}
|
||||
|
||||
func callStaticObjectMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) (C.jobject, error) {
|
||||
res := C.jni_CallStaticObjectMethodA(env, cls, method, varArgs(args))
|
||||
return res, exception(env)
|
||||
}
|
||||
|
||||
func callVoidMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) error {
|
||||
C.jni_CallVoidMethodA(env, obj, method, varArgs(args))
|
||||
return exception(env)
|
||||
}
|
||||
|
||||
func callObjectMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) (C.jobject, error) {
|
||||
res := C.jni_CallObjectMethodA(env, obj, method, varArgs(args))
|
||||
return res, exception(env)
|
||||
}
|
||||
|
||||
// exception returns an error corresponding to the pending
|
||||
// exception, or nil if no exception is pending. The pending
|
||||
// exception is cleared.
|
||||
func exception(env *C.JNIEnv) error {
|
||||
thr := C.jni_ExceptionOccurred(env)
|
||||
if thr == 0 {
|
||||
return nil
|
||||
}
|
||||
C.jni_ExceptionClear(env)
|
||||
cls := getObjectClass(env, C.jobject(thr))
|
||||
toString := getMethodID(env, cls, "toString", "()Ljava/lang/String;")
|
||||
msg, err := callObjectMethod(env, C.jobject(thr), toString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New(goString(env, C.jstring(msg)))
|
||||
}
|
||||
|
||||
func getObjectClass(env *C.JNIEnv, obj C.jobject) C.jclass {
|
||||
if obj == 0 {
|
||||
panic("null object")
|
||||
}
|
||||
cls := C.jni_GetObjectClass(env, C.jobject(obj))
|
||||
if err := exception(env); err != nil {
|
||||
// GetObjectClass should never fail.
|
||||
panic(err)
|
||||
}
|
||||
return cls
|
||||
}
|
||||
|
||||
// goString converts the JVM jstring to a Go string.
|
||||
func goString(env *C.JNIEnv, str C.jstring) string {
|
||||
if str == 0 {
|
||||
return ""
|
||||
}
|
||||
strlen := C.jni_GetStringLength(env, C.jstring(str))
|
||||
chars := C.jni_GetStringChars(env, C.jstring(str))
|
||||
var utf16Chars []uint16
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&utf16Chars))
|
||||
hdr.Data = uintptr(unsafe.Pointer(chars))
|
||||
hdr.Cap = int(strlen)
|
||||
hdr.Len = int(strlen)
|
||||
utf8 := utf16.Decode(utf16Chars)
|
||||
return string(utf8)
|
||||
}
|
||||
|
||||
func Main() {
|
||||
}
|
||||
|
||||
func NewWindow(window Callbacks, opts *Options) error {
|
||||
mainWindow.in <- windowAndOptions{window, opts}
|
||||
return <-mainWindow.errs
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
jstr := javaString(env, s)
|
||||
callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
|
||||
jvalue(android.appCtx), jvalue(jstr))
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
c, err := callStaticObjectMethod(env, android.gioCls, android.mreadClipboard,
|
||||
jvalue(android.appCtx))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
content := goString(env, C.jstring(c))
|
||||
go w.callbacks.Event(clipboard.Event{Text: content})
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) Option(opts *Options) {
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
if o := opts.Orientation; o != nil {
|
||||
setOrientation(env, w.view, *o)
|
||||
}
|
||||
if o := opts.NavigationColor; o != nil {
|
||||
setNavigationColor(env, w.view, *o)
|
||||
}
|
||||
if o := opts.StatusColor; o != nil {
|
||||
setStatusColor(env, w.view, *o)
|
||||
}
|
||||
if o := opts.WindowMode; o != nil {
|
||||
setWindowMode(env, w.view, *o)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) SetCursor(name pointer.CursorName) {
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
setCursor(env, w.view, name)
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
runOnMain(func(env *C.JNIEnv) {
|
||||
w.callbacks.Event(WakeupEvent{})
|
||||
})
|
||||
}
|
||||
|
||||
func setCursor(env *C.JNIEnv, view C.jobject, name pointer.CursorName) {
|
||||
var curID int
|
||||
switch name {
|
||||
default:
|
||||
fallthrough
|
||||
case pointer.CursorDefault:
|
||||
curID = 1000 // TYPE_ARROW
|
||||
case pointer.CursorText:
|
||||
curID = 1008 // TYPE_TEXT
|
||||
case pointer.CursorPointer:
|
||||
curID = 1002 // TYPE_HAND
|
||||
case pointer.CursorCrossHair:
|
||||
curID = 1007 // TYPE_CROSSHAIR
|
||||
case pointer.CursorColResize:
|
||||
curID = 1014 // TYPE_HORIZONTAL_DOUBLE_ARROW
|
||||
case pointer.CursorRowResize:
|
||||
curID = 1015 // TYPE_VERTICAL_DOUBLE_ARROW
|
||||
case pointer.CursorNone:
|
||||
curID = 0 // TYPE_NULL
|
||||
}
|
||||
callVoidMethod(env, view, gioView.setCursor, jvalue(curID))
|
||||
}
|
||||
|
||||
func setOrientation(env *C.JNIEnv, view C.jobject, mode Orientation) {
|
||||
var (
|
||||
id int
|
||||
idFallback int // Used only for SDK 17 or older.
|
||||
)
|
||||
// Constants defined at https://developer.android.com/reference/android/content/pm/ActivityInfo.
|
||||
switch mode {
|
||||
case AnyOrientation:
|
||||
id, idFallback = 2, 2 // SCREEN_ORIENTATION_USER
|
||||
case LandscapeOrientation:
|
||||
id, idFallback = 11, 0 // SCREEN_ORIENTATION_USER_LANDSCAPE (or SCREEN_ORIENTATION_LANDSCAPE)
|
||||
case PortraitOrientation:
|
||||
id, idFallback = 12, 1 // SCREEN_ORIENTATION_USER_PORTRAIT (or SCREEN_ORIENTATION_PORTRAIT)
|
||||
}
|
||||
callVoidMethod(env, view, gioView.setOrientation, jvalue(id), jvalue(idFallback))
|
||||
}
|
||||
|
||||
func setStatusColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) {
|
||||
callVoidMethod(env, view, gioView.setStatusColor,
|
||||
jvalue(uint32(color.A)<<24|uint32(color.R)<<16|uint32(color.G)<<8|uint32(color.B)),
|
||||
jvalue(int(f32color.LinearFromSRGB(color).Luminance()*255)),
|
||||
)
|
||||
}
|
||||
|
||||
func setNavigationColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) {
|
||||
callVoidMethod(env, view, gioView.setNavigationColor,
|
||||
jvalue(uint32(color.A)<<24|uint32(color.R)<<16|uint32(color.G)<<8|uint32(color.B)),
|
||||
jvalue(int(f32color.LinearFromSRGB(color).Luminance()*255)),
|
||||
)
|
||||
}
|
||||
|
||||
func setWindowMode(env *C.JNIEnv, view C.jobject, mode WindowMode) {
|
||||
switch mode {
|
||||
case Fullscreen:
|
||||
callVoidMethod(env, view, gioView.setFullscreen, C.JNI_TRUE)
|
||||
default:
|
||||
callVoidMethod(env, view, gioView.setFullscreen, C.JNI_FALSE)
|
||||
}
|
||||
}
|
||||
|
||||
// Close the window. Not implemented for Android.
|
||||
func (w *window) Close() {}
|
||||
|
||||
// runOnMain runs a function on the Java main thread.
|
||||
func runOnMain(f func(env *C.JNIEnv)) {
|
||||
go func() {
|
||||
mainFuncs <- f
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
callStaticVoidMethod(env, android.gioCls, android.mwakeupMainThread)
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
//export Java_org_gioui_Gio_scheduleMainFuncs
|
||||
func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
|
||||
for {
|
||||
select {
|
||||
case f := <-mainFuncs:
|
||||
f(env)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
@@ -1,237 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package wm
|
||||
|
||||
/*
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_wakeupMainThread(void);
|
||||
__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)];
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"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
|
||||
|
||||
var mainFuncs = make(chan func(), 1)
|
||||
|
||||
// runOnMain runs the function on the main thread.
|
||||
func runOnMain(f func()) {
|
||||
if C.isMainThread() {
|
||||
f()
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
mainFuncs <- f
|
||||
C.gio_wakeupMainThread()
|
||||
}()
|
||||
}
|
||||
|
||||
//export gio_dispatchMainFuncs
|
||||
func gio_dispatchMainFuncs() {
|
||||
for {
|
||||
select {
|
||||
case f := <-mainFuncs:
|
||||
f()
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nsstringToString converts a NSString to a Go string, and
|
||||
// releases the original string.
|
||||
func nsstringToString(str C.CFTypeRef) string {
|
||||
if str == 0 {
|
||||
return ""
|
||||
}
|
||||
defer C.CFRelease(str)
|
||||
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)
|
||||
}
|
||||
|
||||
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(dl C.CFTypeRef) {
|
||||
if d, exists := displayLinks.Load(dl); exists {
|
||||
d := d.(*displayLink)
|
||||
if atomic.LoadUint32(&d.running) != 0 {
|
||||
d.callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// windowSetCursor updates the cursor from the current one to a new one
|
||||
// and returns the new one.
|
||||
func windowSetCursor(from, to pointer.CursorName) pointer.CursorName {
|
||||
if from == to {
|
||||
return to
|
||||
}
|
||||
var curID int
|
||||
switch to {
|
||||
default:
|
||||
to = pointer.CursorDefault
|
||||
fallthrough
|
||||
case pointer.CursorDefault:
|
||||
curID = 1
|
||||
case pointer.CursorText:
|
||||
curID = 2
|
||||
case pointer.CursorPointer:
|
||||
curID = 3
|
||||
case pointer.CursorCrossHair:
|
||||
curID = 4
|
||||
case pointer.CursorColResize:
|
||||
curID = 5
|
||||
case pointer.CursorRowResize:
|
||||
curID = 6
|
||||
case pointer.CursorGrab:
|
||||
curID = 7
|
||||
case pointer.CursorNone:
|
||||
C.gio_hideCursor()
|
||||
return to
|
||||
}
|
||||
if from == pointer.CursorNone {
|
||||
C.gio_showCursor()
|
||||
}
|
||||
C.gio_setCursor(C.NSUInteger(curID))
|
||||
return to
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
runOnMain(func() {
|
||||
w.w.Event(WakeupEvent{})
|
||||
})
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
@import Dispatch;
|
||||
@import Foundation;
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
void gio_wakeupMainThread(void) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
gio_dispatchMainFuncs();
|
||||
});
|
||||
}
|
||||
@@ -1,345 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && ios
|
||||
// +build darwin,ios
|
||||
|
||||
package wm
|
||||
|
||||
/*
|
||||
#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>
|
||||
|
||||
struct drawParams {
|
||||
CGFloat dpi, sdpi;
|
||||
CGFloat width, height;
|
||||
CGFloat top, right, bottom, left;
|
||||
};
|
||||
|
||||
static void writeClipboard(unichar *chars, NSUInteger length) {
|
||||
@autoreleasepool {
|
||||
NSString *s = [NSString string];
|
||||
if (length > 0) {
|
||||
s = [NSString stringWithCharacters:chars length:length];
|
||||
}
|
||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||
p.string = s;
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef readClipboard(void) {
|
||||
@autoreleasepool {
|
||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||
return (__bridge_retained CFTypeRef)p.string;
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type ViewEvent struct{}
|
||||
|
||||
type window struct {
|
||||
view C.CFTypeRef
|
||||
w Callbacks
|
||||
displayLink *displayLink
|
||||
|
||||
visible bool
|
||||
cursor pointer.CursorName
|
||||
|
||||
pointerMap []C.CFTypeRef
|
||||
}
|
||||
|
||||
var mainWindow = newWindowRendezvous()
|
||||
|
||||
var views = make(map[C.CFTypeRef]*window)
|
||||
|
||||
func init() {
|
||||
// Darwin requires UI operations happen on the main thread only.
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
//export onCreate
|
||||
func onCreate(view C.CFTypeRef) {
|
||||
w := &window{
|
||||
view: view,
|
||||
}
|
||||
dl, err := NewDisplayLink(func() {
|
||||
w.draw(false)
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w.displayLink = dl
|
||||
wopts := <-mainWindow.out
|
||||
w.w = wopts.window
|
||||
w.w.SetDriver(w)
|
||||
views[view] = w
|
||||
w.w.Event(system.StageEvent{Stage: system.StagePaused})
|
||||
}
|
||||
|
||||
//export gio_onDraw
|
||||
func gio_onDraw(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
params := C.viewDrawParams(w.view)
|
||||
if params.width == 0 || params.height == 0 {
|
||||
return
|
||||
}
|
||||
wasVisible := w.visible
|
||||
w.visible = true
|
||||
if !wasVisible {
|
||||
w.w.Event(system.StageEvent{Stage: system.StageRunning})
|
||||
}
|
||||
const inchPrDp = 1.0 / 163
|
||||
w.w.Event(FrameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: int(params.width + .5),
|
||||
Y: int(params.height + .5),
|
||||
},
|
||||
Insets: system.Insets{
|
||||
Top: unit.Px(float32(params.top)),
|
||||
Right: unit.Px(float32(params.right)),
|
||||
Bottom: unit.Px(float32(params.bottom)),
|
||||
Left: unit.Px(float32(params.left)),
|
||||
},
|
||||
Metric: unit.Metric{
|
||||
PxPerDp: float32(params.dpi) * inchPrDp,
|
||||
PxPerSp: float32(params.sdpi) * inchPrDp,
|
||||
},
|
||||
},
|
||||
Sync: sync,
|
||||
})
|
||||
}
|
||||
|
||||
//export onStop
|
||||
func onStop(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
w.visible = false
|
||||
w.w.Event(system.StageEvent{Stage: system.StagePaused})
|
||||
}
|
||||
|
||||
//export onDestroy
|
||||
func onDestroy(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
delete(views, view)
|
||||
w.w.Event(system.DestroyEvent{})
|
||||
w.displayLink.Close()
|
||||
w.view = 0
|
||||
}
|
||||
|
||||
//export onFocus
|
||||
func onFocus(view C.CFTypeRef, focus int) {
|
||||
w := views[view]
|
||||
w.w.Event(key.FocusEvent{Focus: focus != 0})
|
||||
}
|
||||
|
||||
//export onLowMemory
|
||||
func onLowMemory() {
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
}
|
||||
|
||||
//export onUpArrow
|
||||
func onUpArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameUpArrow)
|
||||
}
|
||||
|
||||
//export onDownArrow
|
||||
func onDownArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameDownArrow)
|
||||
}
|
||||
|
||||
//export onLeftArrow
|
||||
func onLeftArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameLeftArrow)
|
||||
}
|
||||
|
||||
//export onRightArrow
|
||||
func onRightArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameRightArrow)
|
||||
}
|
||||
|
||||
//export onDeleteBackward
|
||||
func onDeleteBackward(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameDeleteBackward)
|
||||
}
|
||||
|
||||
//export onText
|
||||
func onText(view C.CFTypeRef, str *C.char) {
|
||||
w := views[view]
|
||||
w.w.Event(key.EditEvent{
|
||||
Text: C.GoString(str),
|
||||
})
|
||||
}
|
||||
|
||||
//export onTouch
|
||||
func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
|
||||
var typ pointer.Type
|
||||
switch phase {
|
||||
case C.UITouchPhaseBegan:
|
||||
typ = pointer.Press
|
||||
case C.UITouchPhaseMoved:
|
||||
typ = pointer.Move
|
||||
case C.UITouchPhaseEnded:
|
||||
typ = pointer.Release
|
||||
case C.UITouchPhaseCancelled:
|
||||
typ = pointer.Cancel
|
||||
default:
|
||||
return
|
||||
}
|
||||
w := views[view]
|
||||
t := time.Duration(float64(ti) * float64(time.Second))
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.w.Event(pointer.Event{
|
||||
Type: typ,
|
||||
Source: pointer.Touch,
|
||||
PointerID: w.lookupTouch(last != 0, touchRef),
|
||||
Position: p,
|
||||
Time: t,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
content := nsstringToString(C.readClipboard())
|
||||
go w.w.Event(clipboard.Event{Text: content})
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
u16 := utf16.Encode([]rune(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) Option(opts *Options) {}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
v := w.view
|
||||
if v == 0 {
|
||||
return
|
||||
}
|
||||
if anim {
|
||||
w.displayLink.Start()
|
||||
} else {
|
||||
w.displayLink.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetCursor(name pointer.CursorName) {
|
||||
w.cursor = windowSetCursor(w.cursor, name)
|
||||
}
|
||||
|
||||
func (w *window) onKeyCommand(name string) {
|
||||
w.w.Event(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) {}
|
||||
|
||||
// Close the window. Not implemented for iOS.
|
||||
func (w *window) Close() {}
|
||||
|
||||
func NewWindow(win Callbacks, opts *Options) error {
|
||||
mainWindow.in <- windowAndOptions{win, opts}
|
||||
return <-mainWindow.errs
|
||||
}
|
||||
|
||||
func Main() {
|
||||
}
|
||||
|
||||
//export gio_runMain
|
||||
func gio_runMain() {
|
||||
runMain()
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
@@ -1,274 +0,0 @@
|
||||
// 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>
|
||||
@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];
|
||||
#ifndef TARGET_OS_TV
|
||||
drawView.multipleTouchEnabled = YES;
|
||||
#endif
|
||||
drawView.preservesSuperviewLayoutMargins = YES;
|
||||
drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
onCreate((__bridge CFTypeRef)drawView);
|
||||
[[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];
|
||||
[[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 {
|
||||
UIView *drawView = self.view.subviews[0];
|
||||
if (drawView != nil) {
|
||||
gio_onDraw((__bridge CFTypeRef)drawView);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
UIView *drawView = self.view.subviews[0];
|
||||
if (drawView != nil) {
|
||||
onStop((__bridge CFTypeRef)drawView);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
[super viewDidDisappear:animated];
|
||||
CFTypeRef viewRef = (__bridge CFTypeRef)self.view.subviews[0];
|
||||
onDestroy(viewRef);
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
UIView *view = 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((__bridge CFTypeRef)view);
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
onLowMemory();
|
||||
[super didReceiveMemoryWarning];
|
||||
}
|
||||
|
||||
- (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];
|
||||
}
|
||||
@end
|
||||
|
||||
static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIEvent *event) {
|
||||
CGFloat scale = view.contentScaleFactor;
|
||||
NSUInteger i = 0;
|
||||
NSUInteger n = [touches count];
|
||||
CFTypeRef viewRef = (__bridge CFTypeRef)view;
|
||||
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(lastTouch, viewRef, 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 {
|
||||
if (self.window != nil) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UIWindowDidBecomeKeyNotification
|
||||
object:self.window];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UIWindowDidResignKeyNotification
|
||||
object:self.window];
|
||||
}
|
||||
self.contentScaleFactor = newWindow.screen.nativeScale;
|
||||
[[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((__bridge CFTypeRef)self, YES);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onWindowDidResignKey:(NSNotification *)note {
|
||||
if (self.isFirstResponder) {
|
||||
onFocus((__bridge CFTypeRef)self, 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((__bridge CFTypeRef)self, (char *)text.UTF8String);
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)hasText {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)deleteBackward {
|
||||
onDeleteBackward((__bridge CFTypeRef)self);
|
||||
}
|
||||
|
||||
- (void)onUpArrow {
|
||||
onUpArrow((__bridge CFTypeRef)self);
|
||||
}
|
||||
|
||||
- (void)onDownArrow {
|
||||
onDownArrow((__bridge CFTypeRef)self);
|
||||
}
|
||||
|
||||
- (void)onLeftArrow {
|
||||
onLeftArrow((__bridge CFTypeRef)self);
|
||||
}
|
||||
|
||||
- (void)onRightArrow {
|
||||
onRightArrow((__bridge CFTypeRef)self);
|
||||
}
|
||||
|
||||
- (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.
|
||||
}
|
||||
@@ -1,701 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package wm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"strings"
|
||||
"syscall/js"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"gioui.org/internal/f32color"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type ViewEvent struct{}
|
||||
|
||||
type window struct {
|
||||
window js.Value
|
||||
document js.Value
|
||||
head js.Value
|
||||
clipboard js.Value
|
||||
cnv js.Value
|
||||
tarea js.Value
|
||||
w Callbacks
|
||||
redraw js.Func
|
||||
clipboardCallback js.Func
|
||||
requestAnimationFrame js.Value
|
||||
browserHistory js.Value
|
||||
visualViewport js.Value
|
||||
screenOrientation js.Value
|
||||
cleanfuncs []func()
|
||||
touches []js.Value
|
||||
composing bool
|
||||
requestFocus bool
|
||||
|
||||
chanAnimation chan struct{}
|
||||
chanRedraw chan struct{}
|
||||
|
||||
size f32.Point
|
||||
inset f32.Point
|
||||
scale float32
|
||||
animating bool
|
||||
// animRequested tracks whether a requestAnimationFrame callback
|
||||
// is pending.
|
||||
animRequested bool
|
||||
wakeups chan struct{}
|
||||
}
|
||||
|
||||
func NewWindow(win Callbacks, opts *Options) error {
|
||||
doc := js.Global().Get("document")
|
||||
cont := getContainer(doc)
|
||||
cnv := createCanvas(doc)
|
||||
cont.Call("appendChild", cnv)
|
||||
tarea := createTextArea(doc)
|
||||
cont.Call("appendChild", tarea)
|
||||
w := &window{
|
||||
cnv: cnv,
|
||||
document: doc,
|
||||
tarea: tarea,
|
||||
window: js.Global().Get("window"),
|
||||
head: doc.Get("head"),
|
||||
clipboard: js.Global().Get("navigator").Get("clipboard"),
|
||||
wakeups: make(chan struct{}, 1),
|
||||
}
|
||||
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
|
||||
w.browserHistory = w.window.Get("history")
|
||||
w.visualViewport = w.window.Get("visualViewport")
|
||||
if w.visualViewport.IsUndefined() {
|
||||
w.visualViewport = w.window
|
||||
}
|
||||
if screen := w.window.Get("screen"); screen.Truthy() {
|
||||
w.screenOrientation = screen.Get("orientation")
|
||||
}
|
||||
w.chanAnimation = make(chan struct{}, 1)
|
||||
w.chanRedraw = make(chan struct{}, 1)
|
||||
w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||
w.chanAnimation <- struct{}{}
|
||||
return nil
|
||||
})
|
||||
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||
content := args[0].String()
|
||||
go win.Event(clipboard.Event{Text: content})
|
||||
return nil
|
||||
})
|
||||
w.addEventListeners()
|
||||
w.addHistory()
|
||||
w.Option(opts)
|
||||
w.w = win
|
||||
|
||||
go func() {
|
||||
defer w.cleanup()
|
||||
w.w.SetDriver(w)
|
||||
w.blur()
|
||||
w.w.Event(system.StageEvent{Stage: system.StageRunning})
|
||||
w.resize()
|
||||
w.draw(true)
|
||||
for {
|
||||
select {
|
||||
case <-w.wakeups:
|
||||
w.w.Event(WakeupEvent{})
|
||||
case <-w.chanAnimation:
|
||||
w.animCallback()
|
||||
case <-w.chanRedraw:
|
||||
w.draw(true)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainer(doc js.Value) js.Value {
|
||||
cont := doc.Call("getElementById", "giowindow")
|
||||
if !cont.IsNull() {
|
||||
return cont
|
||||
}
|
||||
cont = doc.Call("createElement", "DIV")
|
||||
doc.Get("body").Call("appendChild", cont)
|
||||
return cont
|
||||
}
|
||||
|
||||
func createTextArea(doc js.Value) js.Value {
|
||||
tarea := doc.Call("createElement", "input")
|
||||
style := tarea.Get("style")
|
||||
style.Set("width", "1px")
|
||||
style.Set("height", "1px")
|
||||
style.Set("opacity", "0")
|
||||
style.Set("border", "0")
|
||||
style.Set("padding", "0")
|
||||
tarea.Set("autocomplete", "off")
|
||||
tarea.Set("autocorrect", "off")
|
||||
tarea.Set("autocapitalize", "off")
|
||||
tarea.Set("spellcheck", false)
|
||||
return tarea
|
||||
}
|
||||
|
||||
func createCanvas(doc js.Value) js.Value {
|
||||
cnv := doc.Call("createElement", "canvas")
|
||||
style := cnv.Get("style")
|
||||
style.Set("position", "fixed")
|
||||
style.Set("width", "100%")
|
||||
style.Set("height", "100%")
|
||||
return cnv
|
||||
}
|
||||
|
||||
func (w *window) cleanup() {
|
||||
// Cleanup in the opposite order of
|
||||
// construction.
|
||||
for i := len(w.cleanfuncs) - 1; i >= 0; i-- {
|
||||
w.cleanfuncs[i]()
|
||||
}
|
||||
w.cleanfuncs = nil
|
||||
}
|
||||
|
||||
func (w *window) addEventListeners() {
|
||||
w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
|
||||
w.resize()
|
||||
w.chanRedraw <- struct{}{}
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
|
||||
args[0].Call("preventDefault")
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.window, "popstate", func(this js.Value, args []js.Value) interface{} {
|
||||
ev := &system.CommandEvent{Type: system.CommandBack}
|
||||
w.w.Event(ev)
|
||||
if ev.Cancel {
|
||||
return w.browserHistory.Call("forward")
|
||||
}
|
||||
|
||||
return w.browserHistory.Call("back")
|
||||
})
|
||||
w.addEventListener(w.document, "visibilitychange", func(this js.Value, args []js.Value) interface{} {
|
||||
ev := system.StageEvent{}
|
||||
switch w.document.Get("visibilityState").String() {
|
||||
case "hidden", "prerender", "unloaded":
|
||||
ev.Stage = system.StagePaused
|
||||
default:
|
||||
ev.Stage = system.StageRunning
|
||||
}
|
||||
w.w.Event(ev)
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "mousemove", func(this js.Value, args []js.Value) interface{} {
|
||||
w.pointerEvent(pointer.Move, 0, 0, args[0])
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "mousedown", func(this js.Value, args []js.Value) interface{} {
|
||||
w.pointerEvent(pointer.Press, 0, 0, args[0])
|
||||
if w.requestFocus {
|
||||
w.focus()
|
||||
w.requestFocus = false
|
||||
}
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "mouseup", func(this js.Value, args []js.Value) interface{} {
|
||||
w.pointerEvent(pointer.Release, 0, 0, args[0])
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "wheel", func(this js.Value, args []js.Value) interface{} {
|
||||
e := args[0]
|
||||
dx, dy := e.Get("deltaX").Float(), e.Get("deltaY").Float()
|
||||
mode := e.Get("deltaMode").Int()
|
||||
switch mode {
|
||||
case 0x01: // DOM_DELTA_LINE
|
||||
dx *= 10
|
||||
dy *= 10
|
||||
case 0x02: // DOM_DELTA_PAGE
|
||||
dx *= 120
|
||||
dy *= 120
|
||||
}
|
||||
w.pointerEvent(pointer.Scroll, float32(dx), float32(dy), e)
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "touchstart", func(this js.Value, args []js.Value) interface{} {
|
||||
w.touchEvent(pointer.Press, args[0])
|
||||
if w.requestFocus {
|
||||
w.focus() // iOS can only focus inside a Touch event.
|
||||
w.requestFocus = false
|
||||
}
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "touchend", func(this js.Value, args []js.Value) interface{} {
|
||||
w.touchEvent(pointer.Release, args[0])
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "touchmove", func(this js.Value, args []js.Value) interface{} {
|
||||
w.touchEvent(pointer.Move, args[0])
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "touchcancel", func(this js.Value, args []js.Value) interface{} {
|
||||
// Cancel all touches even if only one touch was cancelled.
|
||||
for i := range w.touches {
|
||||
w.touches[i] = js.Null()
|
||||
}
|
||||
w.touches = w.touches[:0]
|
||||
w.w.Event(pointer.Event{
|
||||
Type: pointer.Cancel,
|
||||
Source: pointer.Touch,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
|
||||
w.w.Event(key.FocusEvent{Focus: true})
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
w.blur()
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "keydown", func(this js.Value, args []js.Value) interface{} {
|
||||
w.keyEvent(args[0], key.Press)
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "keyup", func(this js.Value, args []js.Value) interface{} {
|
||||
w.keyEvent(args[0], key.Release)
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "compositionstart", func(this js.Value, args []js.Value) interface{} {
|
||||
w.composing = true
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "compositionend", func(this js.Value, args []js.Value) interface{} {
|
||||
w.composing = false
|
||||
w.flushInput()
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "input", func(this js.Value, args []js.Value) interface{} {
|
||||
if w.composing {
|
||||
return nil
|
||||
}
|
||||
w.flushInput()
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "paste", func(this js.Value, args []js.Value) interface{} {
|
||||
if w.clipboard.IsUndefined() {
|
||||
return nil
|
||||
}
|
||||
// Prevents duplicated-paste, since "paste" is already handled through Clipboard API.
|
||||
args[0].Call("preventDefault")
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) addHistory() {
|
||||
w.browserHistory.Call("pushState", nil, nil, w.window.Get("location").Get("href"))
|
||||
}
|
||||
|
||||
func (w *window) flushInput() {
|
||||
val := w.tarea.Get("value").String()
|
||||
w.tarea.Set("value", "")
|
||||
w.w.Event(key.EditEvent{Text: string(val)})
|
||||
}
|
||||
|
||||
func (w *window) blur() {
|
||||
w.tarea.Call("blur")
|
||||
w.requestFocus = false
|
||||
}
|
||||
|
||||
func (w *window) focus() {
|
||||
w.tarea.Call("focus")
|
||||
w.requestFocus = true
|
||||
}
|
||||
|
||||
func (w *window) keyboard(hint key.InputHint) {
|
||||
var m string
|
||||
switch hint {
|
||||
case key.HintAny:
|
||||
m = "text"
|
||||
case key.HintText:
|
||||
m = "text"
|
||||
case key.HintNumeric:
|
||||
m = "decimal"
|
||||
case key.HintEmail:
|
||||
m = "email"
|
||||
case key.HintURL:
|
||||
m = "url"
|
||||
case key.HintTelephone:
|
||||
m = "tel"
|
||||
default:
|
||||
m = "text"
|
||||
}
|
||||
w.tarea.Set("inputMode", m)
|
||||
}
|
||||
|
||||
func (w *window) keyEvent(e js.Value, ks key.State) {
|
||||
k := e.Get("key").String()
|
||||
if n, ok := translateKey(k); ok {
|
||||
cmd := key.Event{
|
||||
Name: n,
|
||||
Modifiers: modifiersFor(e),
|
||||
State: ks,
|
||||
}
|
||||
w.w.Event(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
// modifiersFor returns the modifier set for a DOM MouseEvent or
|
||||
// KeyEvent.
|
||||
func modifiersFor(e js.Value) key.Modifiers {
|
||||
var mods key.Modifiers
|
||||
if e.Get("getModifierState").IsUndefined() {
|
||||
// Some browsers doesn't support getModifierState.
|
||||
return mods
|
||||
}
|
||||
if e.Call("getModifierState", "Alt").Bool() {
|
||||
mods |= key.ModAlt
|
||||
}
|
||||
if e.Call("getModifierState", "Control").Bool() {
|
||||
mods |= key.ModCtrl
|
||||
}
|
||||
if e.Call("getModifierState", "Shift").Bool() {
|
||||
mods |= key.ModShift
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
func (w *window) touchEvent(typ pointer.Type, e js.Value) {
|
||||
e.Call("preventDefault")
|
||||
t := time.Duration(e.Get("timeStamp").Int()) * time.Millisecond
|
||||
changedTouches := e.Get("changedTouches")
|
||||
n := changedTouches.Length()
|
||||
rect := w.cnv.Call("getBoundingClientRect")
|
||||
scale := w.scale
|
||||
var mods key.Modifiers
|
||||
if e.Get("shiftKey").Bool() {
|
||||
mods |= key.ModShift
|
||||
}
|
||||
if e.Get("altKey").Bool() {
|
||||
mods |= key.ModAlt
|
||||
}
|
||||
if e.Get("ctrlKey").Bool() {
|
||||
mods |= key.ModCtrl
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
touch := changedTouches.Index(i)
|
||||
pid := w.touchIDFor(touch)
|
||||
x, y := touch.Get("clientX").Float(), touch.Get("clientY").Float()
|
||||
x -= rect.Get("left").Float()
|
||||
y -= rect.Get("top").Float()
|
||||
pos := f32.Point{
|
||||
X: float32(x) * scale,
|
||||
Y: float32(y) * scale,
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
Type: typ,
|
||||
Source: pointer.Touch,
|
||||
Position: pos,
|
||||
PointerID: pid,
|
||||
Time: t,
|
||||
Modifiers: mods,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) touchIDFor(touch js.Value) pointer.ID {
|
||||
id := touch.Get("identifier")
|
||||
for i, id2 := range w.touches {
|
||||
if id2.Equal(id) {
|
||||
return pointer.ID(i)
|
||||
}
|
||||
}
|
||||
pid := pointer.ID(len(w.touches))
|
||||
w.touches = append(w.touches, id)
|
||||
return pid
|
||||
}
|
||||
|
||||
func (w *window) pointerEvent(typ pointer.Type, dx, dy float32, e js.Value) {
|
||||
e.Call("preventDefault")
|
||||
x, y := e.Get("clientX").Float(), e.Get("clientY").Float()
|
||||
rect := w.cnv.Call("getBoundingClientRect")
|
||||
x -= rect.Get("left").Float()
|
||||
y -= rect.Get("top").Float()
|
||||
scale := w.scale
|
||||
pos := f32.Point{
|
||||
X: float32(x) * scale,
|
||||
Y: float32(y) * scale,
|
||||
}
|
||||
scroll := f32.Point{
|
||||
X: dx * scale,
|
||||
Y: dy * scale,
|
||||
}
|
||||
t := time.Duration(e.Get("timeStamp").Int()) * time.Millisecond
|
||||
jbtns := e.Get("buttons").Int()
|
||||
var btns pointer.Buttons
|
||||
if jbtns&1 != 0 {
|
||||
btns |= pointer.ButtonPrimary
|
||||
}
|
||||
if jbtns&2 != 0 {
|
||||
btns |= pointer.ButtonSecondary
|
||||
}
|
||||
if jbtns&4 != 0 {
|
||||
btns |= pointer.ButtonTertiary
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
Type: typ,
|
||||
Source: pointer.Mouse,
|
||||
Buttons: btns,
|
||||
Position: pos,
|
||||
Scroll: scroll,
|
||||
Time: t,
|
||||
Modifiers: modifiersFor(e),
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) addEventListener(this js.Value, event string, f func(this js.Value, args []js.Value) interface{}) {
|
||||
jsf := w.funcOf(f)
|
||||
this.Call("addEventListener", event, jsf)
|
||||
w.cleanfuncs = append(w.cleanfuncs, func() {
|
||||
this.Call("removeEventListener", event, jsf)
|
||||
})
|
||||
}
|
||||
|
||||
// funcOf is like js.FuncOf but adds the js.Func to a list of
|
||||
// functions to be released during cleanup.
|
||||
func (w *window) funcOf(f func(this js.Value, args []js.Value) interface{}) js.Func {
|
||||
jsf := js.FuncOf(f)
|
||||
w.cleanfuncs = append(w.cleanfuncs, jsf.Release)
|
||||
return jsf
|
||||
}
|
||||
|
||||
func (w *window) animCallback() {
|
||||
anim := w.animating
|
||||
w.animRequested = anim
|
||||
if anim {
|
||||
w.requestAnimationFrame.Invoke(w.redraw)
|
||||
}
|
||||
if anim {
|
||||
w.draw(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
w.animating = anim
|
||||
if anim && !w.animRequested {
|
||||
w.animRequested = true
|
||||
w.requestAnimationFrame.Invoke(w.redraw)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
if w.clipboard.IsUndefined() {
|
||||
return
|
||||
}
|
||||
if w.clipboard.Get("readText").IsUndefined() {
|
||||
return
|
||||
}
|
||||
w.clipboard.Call("readText", w.clipboard).Call("then", w.clipboardCallback)
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
if w.clipboard.IsUndefined() {
|
||||
return
|
||||
}
|
||||
if w.clipboard.Get("writeText").IsUndefined() {
|
||||
return
|
||||
}
|
||||
w.clipboard.Call("writeText", s)
|
||||
}
|
||||
|
||||
func (w *window) Option(opts *Options) {
|
||||
if o := opts.Title; o != nil {
|
||||
w.document.Set("title", *o)
|
||||
}
|
||||
if o := opts.WindowMode; o != nil {
|
||||
w.windowMode(*o)
|
||||
}
|
||||
if o := opts.NavigationColor; o != nil {
|
||||
w.navigationColor(*o)
|
||||
}
|
||||
if o := opts.Orientation; o != nil {
|
||||
w.orientation(*o)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetCursor(name pointer.CursorName) {
|
||||
style := w.cnv.Get("style")
|
||||
style.Set("cursor", string(name))
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {
|
||||
// Run in a goroutine to avoid a deadlock if the
|
||||
// focus change result in an event.
|
||||
go func() {
|
||||
if show {
|
||||
w.focus()
|
||||
} else {
|
||||
w.blur()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (w *window) SetInputHint(mode key.InputHint) {
|
||||
w.keyboard(mode)
|
||||
}
|
||||
|
||||
// Close the window. Not implemented for js.
|
||||
func (w *window) Close() {}
|
||||
|
||||
func (w *window) resize() {
|
||||
w.scale = float32(w.window.Get("devicePixelRatio").Float())
|
||||
|
||||
rect := w.cnv.Call("getBoundingClientRect")
|
||||
w.size.X = float32(rect.Get("width").Float()) * w.scale
|
||||
w.size.Y = float32(rect.Get("height").Float()) * w.scale
|
||||
|
||||
if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
|
||||
w.inset.X = w.size.X - float32(vx.Float())*w.scale
|
||||
w.inset.Y = w.size.Y - float32(vy.Float())*w.scale
|
||||
}
|
||||
|
||||
if w.size.X == 0 || w.size.Y == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
w.cnv.Set("width", int(w.size.X+.5))
|
||||
w.cnv.Set("height", int(w.size.Y+.5))
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
width, height, insets, metric := w.config()
|
||||
if metric == (unit.Metric{}) || width == 0 || height == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
w.w.Event(FrameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: width,
|
||||
Y: height,
|
||||
},
|
||||
Insets: insets,
|
||||
Metric: metric,
|
||||
},
|
||||
Sync: sync,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) config() (int, int, system.Insets, unit.Metric) {
|
||||
return int(w.size.X + .5), int(w.size.Y + .5), system.Insets{
|
||||
Bottom: unit.Px(w.inset.Y),
|
||||
Right: unit.Px(w.inset.X),
|
||||
}, unit.Metric{
|
||||
PxPerDp: w.scale,
|
||||
PxPerSp: w.scale,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) windowMode(mode WindowMode) {
|
||||
switch mode {
|
||||
case Windowed:
|
||||
if !w.document.Get("fullscreenElement").Truthy() {
|
||||
return // Browser is already Windowed.
|
||||
}
|
||||
if !w.document.Get("exitFullscreen").Truthy() {
|
||||
return // Browser doesn't support such feature.
|
||||
}
|
||||
w.document.Call("exitFullscreen")
|
||||
case Fullscreen:
|
||||
elem := w.document.Get("documentElement")
|
||||
if !elem.Get("requestFullscreen").Truthy() {
|
||||
return // Browser doesn't support such feature.
|
||||
}
|
||||
elem.Call("requestFullscreen")
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) orientation(mode Orientation) {
|
||||
if j := w.screenOrientation; !j.Truthy() || !j.Get("unlock").Truthy() || !j.Get("lock").Truthy() {
|
||||
return // Browser don't support Screen Orientation API.
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case AnyOrientation:
|
||||
w.screenOrientation.Call("unlock")
|
||||
case LandscapeOrientation:
|
||||
w.screenOrientation.Call("lock", "landscape").Call("then", w.redraw)
|
||||
case PortraitOrientation:
|
||||
w.screenOrientation.Call("lock", "portrait").Call("then", w.redraw)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) navigationColor(c color.NRGBA) {
|
||||
theme := w.head.Call("querySelector", `meta[name="theme-color"]`)
|
||||
if !theme.Truthy() {
|
||||
theme = w.document.Call("createElement", "meta")
|
||||
theme.Set("name", "theme-color")
|
||||
w.head.Call("appendChild", theme)
|
||||
}
|
||||
rgba := f32color.NRGBAToRGBA(c)
|
||||
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
|
||||
}
|
||||
|
||||
func Main() {
|
||||
select {}
|
||||
}
|
||||
|
||||
func translateKey(k string) (string, bool) {
|
||||
var n string
|
||||
switch k {
|
||||
case "ArrowUp":
|
||||
n = key.NameUpArrow
|
||||
case "ArrowDown":
|
||||
n = key.NameDownArrow
|
||||
case "ArrowLeft":
|
||||
n = key.NameLeftArrow
|
||||
case "ArrowRight":
|
||||
n = key.NameRightArrow
|
||||
case "Escape":
|
||||
n = key.NameEscape
|
||||
case "Enter":
|
||||
n = key.NameReturn
|
||||
case "Backspace":
|
||||
n = key.NameDeleteBackward
|
||||
case "Delete":
|
||||
n = key.NameDeleteForward
|
||||
case "Home":
|
||||
n = key.NameHome
|
||||
case "End":
|
||||
n = key.NameEnd
|
||||
case "PageUp":
|
||||
n = key.NamePageUp
|
||||
case "PageDown":
|
||||
n = key.NamePageDown
|
||||
case "Tab":
|
||||
n = key.NameTab
|
||||
case " ":
|
||||
n = key.NameSpace
|
||||
case "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12":
|
||||
n = k
|
||||
default:
|
||||
r, s := utf8.DecodeRuneInString(k)
|
||||
// If there is exactly one printable character, return that.
|
||||
if s == len(k) && unicode.IsPrint(r) {
|
||||
return strings.ToUpper(k), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
@@ -1,603 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && !ios
|
||||
// +build darwin,!ios
|
||||
|
||||
package wm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"runtime"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/unit"
|
||||
|
||||
_ "gioui.org/internal/cocoainit"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION -Werror -Wno-deprecated-declarations -fmodules -fobjc-arc -x objective-c
|
||||
|
||||
#include <AppKit/AppKit.h>
|
||||
|
||||
#define MOUSE_MOVE 1
|
||||
#define MOUSE_UP 2
|
||||
#define MOUSE_DOWN 3
|
||||
#define MOUSE_SCROLL 4
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_main(void);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(void);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
|
||||
|
||||
static void writeClipboard(unichar *chars, NSUInteger length) {
|
||||
@autoreleasepool {
|
||||
NSString *s = [NSString string];
|
||||
if (length > 0) {
|
||||
s = [NSString stringWithCharacters:chars length:length];
|
||||
}
|
||||
NSPasteboard *p = NSPasteboard.generalPasteboard;
|
||||
[p declareTypes:@[NSPasteboardTypeString] owner:nil];
|
||||
[p setString:s forType:NSPasteboardTypeString];
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef readClipboard(void) {
|
||||
@autoreleasepool {
|
||||
NSPasteboard *p = NSPasteboard.generalPasteboard;
|
||||
NSString *content = [p stringForType:NSPasteboardTypeString];
|
||||
return (__bridge_retained CFTypeRef)content;
|
||||
}
|
||||
}
|
||||
|
||||
static CGFloat viewHeight(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view bounds].size.height;
|
||||
}
|
||||
|
||||
static CGFloat viewWidth(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view bounds].size.width;
|
||||
}
|
||||
|
||||
static CGFloat getScreenBackingScale(void) {
|
||||
return [NSScreen.mainScreen backingScaleFactor];
|
||||
}
|
||||
|
||||
static CGFloat getViewBackingScale(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view.window backingScaleFactor];
|
||||
}
|
||||
|
||||
static void setNeedsDisplay(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
[view setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
static NSPoint cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
return [window cascadeTopLeftFromPoint:topLeft];
|
||||
}
|
||||
|
||||
static void makeKeyAndOrderFront(CFTypeRef windowRef) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window makeKeyAndOrderFront:nil];
|
||||
}
|
||||
|
||||
static void toggleFullScreen(CFTypeRef windowRef) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window toggleFullScreen:nil];
|
||||
}
|
||||
|
||||
static void closeWindow(CFTypeRef windowRef) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
[window performClose:nil];
|
||||
}
|
||||
|
||||
static void setSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
NSSize size = NSMakeSize(width, height);
|
||||
[window setContentSize:size];
|
||||
}
|
||||
|
||||
static void setMinSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
window.contentMinSize = NSMakeSize(width, height);
|
||||
}
|
||||
|
||||
static void setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
window.contentMaxSize = NSMakeSize(width, height);
|
||||
}
|
||||
|
||||
static void setTitle(CFTypeRef windowRef, const char *title) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
window.title = [NSString stringWithUTF8String: title];
|
||||
}
|
||||
|
||||
static CFTypeRef layerForView(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return (__bridge CFTypeRef)view.layer;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func init() {
|
||||
// Darwin requires that UI operations happen on the main thread only.
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
// ViewEvent notified the client of changes to the window AppKit handles.
|
||||
// The handles are retained until another ViewEvent is sent.
|
||||
type ViewEvent struct {
|
||||
// View is a CFTypeRef for the NSView for the window.
|
||||
View uintptr
|
||||
// Layer is a CFTypeRef of the CALayer of View.
|
||||
Layer uintptr
|
||||
}
|
||||
|
||||
type window struct {
|
||||
view C.CFTypeRef
|
||||
window C.CFTypeRef
|
||||
w Callbacks
|
||||
stage system.Stage
|
||||
displayLink *displayLink
|
||||
cursor pointer.CursorName
|
||||
|
||||
scale float32
|
||||
mode WindowMode
|
||||
}
|
||||
|
||||
// viewMap is the mapping from Cocoa NSViews to Go windows.
|
||||
var viewMap = make(map[C.CFTypeRef]*window)
|
||||
|
||||
// launched is closed when applicationDidFinishLaunching is called.
|
||||
var launched = make(chan struct{})
|
||||
|
||||
// nextTopLeft is the offset to use for the next window's call to
|
||||
// cascadeTopLeftFromPoint.
|
||||
var nextTopLeft C.NSPoint
|
||||
|
||||
// mustView is like lookupView, except that it panics
|
||||
// if the view isn't mapped.
|
||||
func mustView(view C.CFTypeRef) *window {
|
||||
w, ok := lookupView(view)
|
||||
if !ok {
|
||||
panic("no window for view")
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func lookupView(view C.CFTypeRef) (*window, bool) {
|
||||
w, exists := viewMap[view]
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
return w, true
|
||||
}
|
||||
|
||||
func deleteView(view C.CFTypeRef) {
|
||||
delete(viewMap, view)
|
||||
}
|
||||
|
||||
func insertView(view C.CFTypeRef, w *window) {
|
||||
viewMap[view] = w
|
||||
}
|
||||
|
||||
func (w *window) contextView() C.CFTypeRef {
|
||||
return w.view
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
content := nsstringToString(C.readClipboard())
|
||||
go w.w.Event(clipboard.Event{Text: content})
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
u16 := utf16.Encode([]rune(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) Option(opts *Options) {
|
||||
screenScale := float32(C.getScreenBackingScale())
|
||||
cfg := configFor(screenScale)
|
||||
val := func(v unit.Value) float32 {
|
||||
return float32(cfg.Px(v)) / screenScale
|
||||
}
|
||||
if o := opts.Size; o != nil {
|
||||
width := val(o.Width)
|
||||
height := val(o.Height)
|
||||
if width > 0 || height > 0 {
|
||||
C.setSize(w.window, C.CGFloat(width), C.CGFloat(height))
|
||||
}
|
||||
}
|
||||
if o := opts.MinSize; o != nil {
|
||||
width := val(o.Width)
|
||||
height := val(o.Height)
|
||||
if width > 0 || height > 0 {
|
||||
C.setMinSize(w.window, C.CGFloat(width), C.CGFloat(height))
|
||||
}
|
||||
}
|
||||
if o := opts.MaxSize; o != nil {
|
||||
width := val(o.Width)
|
||||
height := val(o.Height)
|
||||
if width > 0 || height > 0 {
|
||||
C.setMaxSize(w.window, C.CGFloat(width), C.CGFloat(height))
|
||||
}
|
||||
}
|
||||
if o := opts.Title; o != nil {
|
||||
title := C.CString(*o)
|
||||
defer C.free(unsafe.Pointer(title))
|
||||
C.setTitle(w.window, title)
|
||||
}
|
||||
if o := opts.WindowMode; o != nil {
|
||||
w.SetWindowMode(*o)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetWindowMode(mode WindowMode) {
|
||||
switch mode {
|
||||
case w.mode:
|
||||
case Windowed, Fullscreen:
|
||||
C.toggleFullScreen(w.window)
|
||||
w.mode = mode
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetCursor(name pointer.CursorName) {
|
||||
w.cursor = windowSetCursor(w.cursor, name)
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {}
|
||||
|
||||
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
if anim {
|
||||
w.displayLink.Start()
|
||||
} else {
|
||||
w.displayLink.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) runOnMain(f func()) {
|
||||
runOnMain(func() {
|
||||
// Make sure the view is still valid. The window might've been closed
|
||||
// during the switch to the main thread.
|
||||
if w.view != 0 {
|
||||
f()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) Close() {
|
||||
// close immediately calls gio_onClose which sends events
|
||||
// causing a deadlock because Close is called during an event.
|
||||
// Break the deadlock by deferring the close, making Close more
|
||||
// akin to a message like the other platforms.
|
||||
go w.runOnMain(func() {
|
||||
C.closeWindow(w.window)
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) setStage(stage system.Stage) {
|
||||
if stage == w.stage {
|
||||
return
|
||||
}
|
||||
w.stage = stage
|
||||
w.w.Event(system.StageEvent{Stage: stage})
|
||||
}
|
||||
|
||||
//export gio_onKeys
|
||||
func gio_onKeys(view C.CFTypeRef, cstr *C.char, ti C.double, mods C.NSUInteger, keyDown C.bool) {
|
||||
str := C.GoString(cstr)
|
||||
kmods := convertMods(mods)
|
||||
ks := key.Release
|
||||
if keyDown {
|
||||
ks = key.Press
|
||||
}
|
||||
w := mustView(view)
|
||||
for _, k := range str {
|
||||
if n, ok := convertKey(k); ok {
|
||||
w.w.Event(key.Event{
|
||||
Name: n,
|
||||
Modifiers: kmods,
|
||||
State: ks,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_onText
|
||||
func gio_onText(view C.CFTypeRef, cstr *C.char) {
|
||||
str := C.GoString(cstr)
|
||||
w := mustView(view)
|
||||
w.w.Event(key.EditEvent{Text: str})
|
||||
}
|
||||
|
||||
//export gio_onMouse
|
||||
func gio_onMouse(view C.CFTypeRef, cdir C.int, cbtns C.NSUInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) {
|
||||
var typ pointer.Type
|
||||
switch cdir {
|
||||
case C.MOUSE_MOVE:
|
||||
typ = pointer.Move
|
||||
case C.MOUSE_UP:
|
||||
typ = pointer.Release
|
||||
case C.MOUSE_DOWN:
|
||||
typ = pointer.Press
|
||||
case C.MOUSE_SCROLL:
|
||||
typ = pointer.Scroll
|
||||
default:
|
||||
panic("invalid direction")
|
||||
}
|
||||
var btns pointer.Buttons
|
||||
if cbtns&(1<<0) != 0 {
|
||||
btns |= pointer.ButtonPrimary
|
||||
}
|
||||
if cbtns&(1<<1) != 0 {
|
||||
btns |= pointer.ButtonSecondary
|
||||
}
|
||||
if cbtns&(1<<2) != 0 {
|
||||
btns |= pointer.ButtonTertiary
|
||||
}
|
||||
t := time.Duration(float64(ti)*float64(time.Second) + .5)
|
||||
w := mustView(view)
|
||||
xf, yf := float32(x)*w.scale, float32(y)*w.scale
|
||||
dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale
|
||||
w.w.Event(pointer.Event{
|
||||
Type: typ,
|
||||
Source: pointer.Mouse,
|
||||
Time: t,
|
||||
Buttons: btns,
|
||||
Position: f32.Point{X: xf, Y: yf},
|
||||
Scroll: f32.Point{X: dxf, Y: dyf},
|
||||
Modifiers: convertMods(mods),
|
||||
})
|
||||
}
|
||||
|
||||
//export gio_onDraw
|
||||
func gio_onDraw(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.draw()
|
||||
}
|
||||
|
||||
//export gio_onFocus
|
||||
func gio_onFocus(view C.CFTypeRef, focus C.int) {
|
||||
w := mustView(view)
|
||||
w.w.Event(key.FocusEvent{Focus: focus == 1})
|
||||
w.SetCursor(w.cursor)
|
||||
}
|
||||
|
||||
//export gio_onChangeScreen
|
||||
func gio_onChangeScreen(view C.CFTypeRef, did uint64) {
|
||||
w := mustView(view)
|
||||
w.displayLink.SetDisplayID(did)
|
||||
}
|
||||
|
||||
func (w *window) draw() {
|
||||
w.scale = float32(C.getViewBackingScale(w.view))
|
||||
wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view))
|
||||
if wf == 0 || hf == 0 {
|
||||
return
|
||||
}
|
||||
width := int(wf*w.scale + .5)
|
||||
height := int(hf*w.scale + .5)
|
||||
cfg := configFor(w.scale)
|
||||
w.setStage(system.StageRunning)
|
||||
w.w.Event(FrameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: width,
|
||||
Y: height,
|
||||
},
|
||||
Metric: cfg,
|
||||
},
|
||||
Sync: true,
|
||||
})
|
||||
}
|
||||
|
||||
func configFor(scale float32) unit.Metric {
|
||||
return unit.Metric{
|
||||
PxPerDp: scale,
|
||||
PxPerSp: scale,
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_onClose
|
||||
func gio_onClose(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.w.Event(ViewEvent{})
|
||||
deleteView(view)
|
||||
w.w.Event(system.DestroyEvent{})
|
||||
w.displayLink.Close()
|
||||
C.CFRelease(w.view)
|
||||
C.CFRelease(w.window)
|
||||
w.view = 0
|
||||
w.window = 0
|
||||
w.displayLink = nil
|
||||
}
|
||||
|
||||
//export gio_onHide
|
||||
func gio_onHide(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.setStage(system.StagePaused)
|
||||
}
|
||||
|
||||
//export gio_onShow
|
||||
func gio_onShow(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.setStage(system.StageRunning)
|
||||
}
|
||||
|
||||
//export gio_onAppHide
|
||||
func gio_onAppHide() {
|
||||
for _, w := range viewMap {
|
||||
w.setStage(system.StagePaused)
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_onAppShow
|
||||
func gio_onAppShow() {
|
||||
for _, w := range viewMap {
|
||||
w.setStage(system.StageRunning)
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_onFinishLaunching
|
||||
func gio_onFinishLaunching() {
|
||||
close(launched)
|
||||
}
|
||||
|
||||
func NewWindow(win Callbacks, opts *Options) error {
|
||||
<-launched
|
||||
errch := make(chan error)
|
||||
runOnMain(func() {
|
||||
w, err := newWindow(opts)
|
||||
if err != nil {
|
||||
errch <- err
|
||||
return
|
||||
}
|
||||
errch <- nil
|
||||
w.w = win
|
||||
w.window = C.gio_createWindow(w.view, nil, 0, 0, 0, 0, 0, 0)
|
||||
win.SetDriver(w)
|
||||
w.Option(opts)
|
||||
if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
|
||||
// cascadeTopLeftFromPoint treats (0, 0) as a no-op,
|
||||
// and just returns the offset we need for the first window.
|
||||
nextTopLeft = C.cascadeTopLeftFromPoint(w.window, nextTopLeft)
|
||||
}
|
||||
nextTopLeft = C.cascadeTopLeftFromPoint(w.window, nextTopLeft)
|
||||
C.makeKeyAndOrderFront(w.window)
|
||||
layer := C.layerForView(w.view)
|
||||
w.w.Event(ViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
|
||||
})
|
||||
return <-errch
|
||||
}
|
||||
|
||||
func newWindow(opts *Options) (*window, error) {
|
||||
view := C.gio_createView()
|
||||
if view == 0 {
|
||||
return nil, errors.New("CreateWindow: failed to create view")
|
||||
}
|
||||
scale := float32(C.getViewBackingScale(view))
|
||||
w := &window{
|
||||
view: view,
|
||||
scale: scale,
|
||||
}
|
||||
dl, err := NewDisplayLink(func() {
|
||||
w.runOnMain(func() {
|
||||
C.setNeedsDisplay(w.view)
|
||||
})
|
||||
})
|
||||
w.displayLink = dl
|
||||
if err != nil {
|
||||
C.CFRelease(view)
|
||||
return nil, err
|
||||
}
|
||||
insertView(view, w)
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func Main() {
|
||||
C.gio_main()
|
||||
}
|
||||
|
||||
func convertKey(k rune) (string, bool) {
|
||||
var n string
|
||||
switch k {
|
||||
case 0x1b:
|
||||
n = key.NameEscape
|
||||
case C.NSLeftArrowFunctionKey:
|
||||
n = key.NameLeftArrow
|
||||
case C.NSRightArrowFunctionKey:
|
||||
n = key.NameRightArrow
|
||||
case C.NSUpArrowFunctionKey:
|
||||
n = key.NameUpArrow
|
||||
case C.NSDownArrowFunctionKey:
|
||||
n = key.NameDownArrow
|
||||
case 0xd:
|
||||
n = key.NameReturn
|
||||
case 0x3:
|
||||
n = key.NameEnter
|
||||
case C.NSHomeFunctionKey:
|
||||
n = key.NameHome
|
||||
case C.NSEndFunctionKey:
|
||||
n = key.NameEnd
|
||||
case 0x7f:
|
||||
n = key.NameDeleteBackward
|
||||
case C.NSDeleteFunctionKey:
|
||||
n = key.NameDeleteForward
|
||||
case C.NSPageUpFunctionKey:
|
||||
n = key.NamePageUp
|
||||
case C.NSPageDownFunctionKey:
|
||||
n = key.NamePageDown
|
||||
case C.NSF1FunctionKey:
|
||||
n = "F1"
|
||||
case C.NSF2FunctionKey:
|
||||
n = "F2"
|
||||
case C.NSF3FunctionKey:
|
||||
n = "F3"
|
||||
case C.NSF4FunctionKey:
|
||||
n = "F4"
|
||||
case C.NSF5FunctionKey:
|
||||
n = "F5"
|
||||
case C.NSF6FunctionKey:
|
||||
n = "F6"
|
||||
case C.NSF7FunctionKey:
|
||||
n = "F7"
|
||||
case C.NSF8FunctionKey:
|
||||
n = "F8"
|
||||
case C.NSF9FunctionKey:
|
||||
n = "F9"
|
||||
case C.NSF10FunctionKey:
|
||||
n = "F10"
|
||||
case C.NSF11FunctionKey:
|
||||
n = "F11"
|
||||
case C.NSF12FunctionKey:
|
||||
n = "F12"
|
||||
case 0x09, 0x19:
|
||||
n = key.NameTab
|
||||
case 0x20:
|
||||
n = key.NameSpace
|
||||
default:
|
||||
k = unicode.ToUpper(k)
|
||||
if !unicode.IsPrint(k) {
|
||||
return "", false
|
||||
}
|
||||
n = string(k)
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
func convertMods(mods C.NSUInteger) key.Modifiers {
|
||||
var kmods key.Modifiers
|
||||
if mods&C.NSAlternateKeyMask != 0 {
|
||||
kmods |= key.ModAlt
|
||||
}
|
||||
if mods&C.NSControlKeyMask != 0 {
|
||||
kmods |= key.ModCtrl
|
||||
}
|
||||
if mods&C.NSCommandKeyMask != 0 {
|
||||
kmods |= key.ModCommand
|
||||
}
|
||||
if mods&C.NSShiftKeyMask != 0 {
|
||||
kmods |= key.ModShift
|
||||
}
|
||||
return kmods
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
@@ -1,282 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,!ios
|
||||
|
||||
@import AppKit;
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
|
||||
|
||||
@interface GioAppDelegate : NSObject<NSApplicationDelegate>
|
||||
@end
|
||||
|
||||
@interface GioWindowDelegate : NSObject<NSWindowDelegate>
|
||||
@end
|
||||
|
||||
@implementation GioWindowDelegate
|
||||
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onHide((__bridge CFTypeRef)window.contentView);
|
||||
}
|
||||
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onShow((__bridge CFTypeRef)window.contentView);
|
||||
}
|
||||
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
||||
CFTypeRef view = (__bridge CFTypeRef)window.contentView;
|
||||
gio_onChangeScreen(view, dispID);
|
||||
}
|
||||
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onFocus((__bridge CFTypeRef)window.contentView, 1);
|
||||
}
|
||||
- (void)windowDidResignKey:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onFocus((__bridge CFTypeRef)window.contentView, 0);
|
||||
}
|
||||
- (void)windowWillClose:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
window.delegate = nil;
|
||||
gio_onClose((__bridge CFTypeRef)window.contentView);
|
||||
}
|
||||
@end
|
||||
|
||||
static void handleMouse(NSView *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((__bridge CFTypeRef)view, typ, [NSEvent pressedMouseButtons], p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
||||
}
|
||||
|
||||
@interface GioView : NSView <CALayerDelegate>
|
||||
@end
|
||||
|
||||
@implementation GioView
|
||||
// drawRect is called when OpenGL is used, displayLayer otherwise.
|
||||
// Don't know why.
|
||||
- (void)drawRect:(NSRect)r {
|
||||
gio_onDraw((__bridge CFTypeRef)self);
|
||||
}
|
||||
- (void)displayLayer:(CALayer *)layer {
|
||||
layer.contentsScale = self.window.backingScaleFactor;
|
||||
gio_onDraw((__bridge CFTypeRef)self);
|
||||
}
|
||||
- (CALayer *)makeBackingLayer {
|
||||
CALayer *layer = gio_layerFactory();
|
||||
layer.delegate = self;
|
||||
return layer;
|
||||
}
|
||||
- (void)mouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||
}
|
||||
- (void)mouseUp:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_UP, 0, 0);
|
||||
}
|
||||
- (void)middleMouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||
}
|
||||
- (void)middletMouseUp:(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)mouseMoved:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
||||
}
|
||||
- (void)mouseDragged:(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((__bridge CFTypeRef)self, (char *)[keys UTF8String], [event timestamp], [event modifierFlags], true);
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
}
|
||||
- (void)keyUp:(NSEvent *)event {
|
||||
NSString *keys = [event charactersIgnoringModifiers];
|
||||
gio_onKeys((__bridge CFTypeRef)self, (char *)[keys UTF8String], [event timestamp], [event modifierFlags], false);
|
||||
}
|
||||
- (void)insertText:(id)string {
|
||||
const char *utf8 = [string UTF8String];
|
||||
gio_onText((__bridge CFTypeRef)self, (char *)utf8);
|
||||
}
|
||||
- (void)doCommandBySelector:(SEL)sel {
|
||||
// Don't pass commands up the responder chain.
|
||||
// They will end up in a beep.
|
||||
}
|
||||
@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 *displayLinkContext) {
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
void gio_setCursor(NSUInteger curID) {
|
||||
@autoreleasepool {
|
||||
switch (curID) {
|
||||
case 1:
|
||||
[NSCursor.arrowCursor set];
|
||||
break;
|
||||
case 2:
|
||||
[NSCursor.IBeamCursor set];
|
||||
break;
|
||||
case 3:
|
||||
[NSCursor.pointingHandCursor set];
|
||||
break;
|
||||
case 4:
|
||||
[NSCursor.crosshairCursor set];
|
||||
break;
|
||||
case 5:
|
||||
[NSCursor.resizeLeftRightCursor set];
|
||||
break;
|
||||
case 6:
|
||||
[NSCursor.resizeUpDownCursor set];
|
||||
break;
|
||||
case 7:
|
||||
[NSCursor.openHandCursor set];
|
||||
break;
|
||||
default:
|
||||
[NSCursor.arrowCursor set];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight) {
|
||||
@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];
|
||||
if (minWidth > 0 || minHeight > 0) {
|
||||
window.contentMinSize = NSMakeSize(minWidth, minHeight);
|
||||
}
|
||||
if (maxWidth > 0 || maxHeight > 0) {
|
||||
window.contentMaxSize = NSMakeSize(maxWidth, maxHeight);
|
||||
}
|
||||
[window setAcceptsMouseMovedEvents:YES];
|
||||
if (title != nil) {
|
||||
window.title = [NSString stringWithUTF8String: title];
|
||||
}
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
[window setContentView:view];
|
||||
[window makeFirstResponder:view];
|
||||
window.releasedWhenClosed = NO;
|
||||
window.delegate = globalWindowDel;
|
||||
return (__bridge_retained CFTypeRef)window;
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_createView(void) {
|
||||
@autoreleasepool {
|
||||
NSRect frame = NSMakeRect(0, 0, 0, 0);
|
||||
GioView* view = [[GioView alloc] initWithFrame:frame];
|
||||
[view setWantsLayer:YES];
|
||||
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
|
||||
return CFBridgingRetain(view);
|
||||
}
|
||||
}
|
||||
|
||||
@implementation GioAppDelegate
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
gio_onFinishLaunching();
|
||||
}
|
||||
- (void)applicationDidHide:(NSNotification *)aNotification {
|
||||
gio_onAppHide();
|
||||
}
|
||||
- (void)applicationWillUnhide:(NSNotification *)notification {
|
||||
gio_onAppShow();
|
||||
}
|
||||
@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];
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build (linux && !android) || freebsd || openbsd
|
||||
// +build linux,!android freebsd openbsd
|
||||
|
||||
package wm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type ViewEvent 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 Main() {
|
||||
select {}
|
||||
}
|
||||
|
||||
type windowDriver func(Callbacks, *Options) 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, opts *Options) error {
|
||||
var errFirst error
|
||||
for _, d := range []windowDriver{x11Driver, wlDriver} {
|
||||
if d == nil {
|
||||
continue
|
||||
}
|
||||
err := d(window, opts)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if errFirst == nil {
|
||||
errFirst = err
|
||||
}
|
||||
}
|
||||
if errFirst != nil {
|
||||
return errFirst
|
||||
}
|
||||
return errors.New("app: no window driver available")
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
@@ -1,117 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build linux,!android,!nowayland freebsd
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include "wayland_xdg_shell.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,
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,777 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package wm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/windows"
|
||||
|
||||
"gioui.org/app/internal/windows"
|
||||
"gioui.org/unit"
|
||||
gowindows "golang.org/x/sys/windows"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
)
|
||||
|
||||
type ViewEvent struct {
|
||||
HWND uintptr
|
||||
}
|
||||
|
||||
type winConstraints struct {
|
||||
minWidth, minHeight int32
|
||||
maxWidth, maxHeight int32
|
||||
}
|
||||
|
||||
type winDeltas struct {
|
||||
width int32
|
||||
height int32
|
||||
}
|
||||
|
||||
type window struct {
|
||||
hwnd syscall.Handle
|
||||
hdc syscall.Handle
|
||||
w Callbacks
|
||||
width int
|
||||
height int
|
||||
stage system.Stage
|
||||
pointerBtns pointer.Buttons
|
||||
|
||||
// cursorIn tracks whether the cursor was inside the window according
|
||||
// to the most recent WM_SETCURSOR.
|
||||
cursorIn bool
|
||||
cursor syscall.Handle
|
||||
|
||||
// placement saves the previous window position when in full screen mode.
|
||||
placement *windows.WindowPlacement
|
||||
|
||||
animating bool
|
||||
|
||||
minmax winConstraints
|
||||
deltas winDeltas
|
||||
opts *Options
|
||||
}
|
||||
|
||||
const _WM_WAKEUP = windows.WM_USER + iota
|
||||
|
||||
type gpuAPI struct {
|
||||
priority int
|
||||
initializer func(w *window) (Context, error)
|
||||
}
|
||||
|
||||
// drivers is the list of potential Context implementations.
|
||||
var drivers []gpuAPI
|
||||
|
||||
// winMap maps win32 HWNDs to *windows.
|
||||
var winMap sync.Map
|
||||
|
||||
// iconID is the ID of the icon in the resource file.
|
||||
const iconID = 1
|
||||
|
||||
var resources struct {
|
||||
once sync.Once
|
||||
// handle is the module handle from GetModuleHandle.
|
||||
handle syscall.Handle
|
||||
// class is the Gio window class from RegisterClassEx.
|
||||
class uint16
|
||||
// cursor is the arrow cursor resource.
|
||||
cursor syscall.Handle
|
||||
}
|
||||
|
||||
func Main() {
|
||||
select {}
|
||||
}
|
||||
|
||||
func NewWindow(window Callbacks, opts *Options) error {
|
||||
cerr := make(chan error)
|
||||
go func() {
|
||||
// GetMessage and PeekMessage can filter on a window HWND, but
|
||||
// then thread-specific messages such as WM_QUIT are ignored.
|
||||
// Instead lock the thread so window messages arrive through
|
||||
// unfiltered GetMessage calls.
|
||||
runtime.LockOSThread()
|
||||
w, err := createNativeWindow(opts)
|
||||
if err != nil {
|
||||
cerr <- err
|
||||
return
|
||||
}
|
||||
cerr <- nil
|
||||
winMap.Store(w.hwnd, w)
|
||||
defer winMap.Delete(w.hwnd)
|
||||
w.w = window
|
||||
w.w.SetDriver(w)
|
||||
w.w.Event(ViewEvent{HWND: uintptr(w.hwnd)})
|
||||
w.Option(opts)
|
||||
windows.ShowWindow(w.hwnd, windows.SW_SHOWDEFAULT)
|
||||
windows.SetForegroundWindow(w.hwnd)
|
||||
windows.SetFocus(w.hwnd)
|
||||
// Since the window class for the cursor is null,
|
||||
// set it here to show the cursor.
|
||||
w.SetCursor(pointer.CursorDefault)
|
||||
if err := w.loop(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
return <-cerr
|
||||
}
|
||||
|
||||
// initResources initializes the resources global.
|
||||
func initResources() error {
|
||||
windows.SetProcessDPIAware()
|
||||
hInst, err := windows.GetModuleHandle()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resources.handle = hInst
|
||||
c, err := windows.LoadCursor(windows.IDC_ARROW)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resources.cursor = c
|
||||
icon, _ := windows.LoadImage(hInst, iconID, windows.IMAGE_ICON, 0, 0, windows.LR_DEFAULTSIZE|windows.LR_SHARED)
|
||||
wcls := windows.WndClassEx{
|
||||
CbSize: uint32(unsafe.Sizeof(windows.WndClassEx{})),
|
||||
Style: windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC,
|
||||
LpfnWndProc: syscall.NewCallback(windowProc),
|
||||
HInstance: hInst,
|
||||
HIcon: icon,
|
||||
LpszClassName: syscall.StringToUTF16Ptr("GioWindow"),
|
||||
}
|
||||
cls, err := windows.RegisterClassEx(&wcls)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resources.class = cls
|
||||
return nil
|
||||
}
|
||||
|
||||
func getWindowConstraints(cfg unit.Metric, opts *Options) winConstraints {
|
||||
var minmax winConstraints
|
||||
if o := opts.MinSize; o != nil {
|
||||
minmax.minWidth = int32(cfg.Px(o.Width))
|
||||
minmax.minHeight = int32(cfg.Px(o.Height))
|
||||
}
|
||||
if o := opts.MaxSize; o != nil {
|
||||
minmax.maxWidth = int32(cfg.Px(o.Width))
|
||||
minmax.maxHeight = int32(cfg.Px(o.Height))
|
||||
}
|
||||
return minmax
|
||||
}
|
||||
|
||||
func createNativeWindow(opts *Options) (*window, error) {
|
||||
var resErr error
|
||||
resources.once.Do(func() {
|
||||
resErr = initResources()
|
||||
})
|
||||
if resErr != nil {
|
||||
return nil, resErr
|
||||
}
|
||||
dpi := windows.GetSystemDPI()
|
||||
cfg := configForDPI(dpi)
|
||||
dwStyle := uint32(windows.WS_OVERLAPPEDWINDOW)
|
||||
dwExStyle := uint32(windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE)
|
||||
|
||||
hwnd, err := windows.CreateWindowEx(dwExStyle,
|
||||
resources.class,
|
||||
"",
|
||||
dwStyle|windows.WS_CLIPSIBLINGS|windows.WS_CLIPCHILDREN,
|
||||
windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
|
||||
windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
|
||||
0,
|
||||
0,
|
||||
resources.handle,
|
||||
0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w := &window{
|
||||
hwnd: hwnd,
|
||||
minmax: getWindowConstraints(cfg, opts),
|
||||
opts: opts,
|
||||
}
|
||||
w.hdc, err = windows.GetDC(hwnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
|
||||
win, exists := winMap.Load(hwnd)
|
||||
if !exists {
|
||||
return windows.DefWindowProc(hwnd, msg, wParam, lParam)
|
||||
}
|
||||
|
||||
w := win.(*window)
|
||||
|
||||
switch msg {
|
||||
case windows.WM_UNICHAR:
|
||||
if wParam == windows.UNICODE_NOCHAR {
|
||||
// Tell the system that we accept WM_UNICHAR messages.
|
||||
return windows.TRUE
|
||||
}
|
||||
fallthrough
|
||||
case windows.WM_CHAR:
|
||||
if r := rune(wParam); unicode.IsPrint(r) {
|
||||
w.w.Event(key.EditEvent{Text: string(r)})
|
||||
}
|
||||
// The message is processed.
|
||||
return windows.TRUE
|
||||
case windows.WM_DPICHANGED:
|
||||
// Let Windows know we're prepared for runtime DPI changes.
|
||||
return windows.TRUE
|
||||
case windows.WM_ERASEBKGND:
|
||||
// Avoid flickering between GPU content and background color.
|
||||
return windows.TRUE
|
||||
case windows.WM_KEYDOWN, windows.WM_KEYUP, windows.WM_SYSKEYDOWN, windows.WM_SYSKEYUP:
|
||||
if n, ok := convertKeyCode(wParam); ok {
|
||||
e := key.Event{
|
||||
Name: n,
|
||||
Modifiers: getModifiers(),
|
||||
State: key.Press,
|
||||
}
|
||||
if msg == windows.WM_KEYUP || msg == windows.WM_SYSKEYUP {
|
||||
e.State = key.Release
|
||||
}
|
||||
|
||||
w.w.Event(e)
|
||||
|
||||
if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
|
||||
// Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
|
||||
// such as cmd.exe and graphical debuggers also reserve F10.
|
||||
return 0
|
||||
}
|
||||
}
|
||||
case windows.WM_LBUTTONDOWN:
|
||||
w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers())
|
||||
case windows.WM_LBUTTONUP:
|
||||
w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers())
|
||||
case windows.WM_RBUTTONDOWN:
|
||||
w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers())
|
||||
case windows.WM_RBUTTONUP:
|
||||
w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers())
|
||||
case windows.WM_MBUTTONDOWN:
|
||||
w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers())
|
||||
case windows.WM_MBUTTONUP:
|
||||
w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
|
||||
case windows.WM_CANCELMODE:
|
||||
w.w.Event(pointer.Event{
|
||||
Type: pointer.Cancel,
|
||||
})
|
||||
case windows.WM_SETFOCUS:
|
||||
w.w.Event(key.FocusEvent{Focus: true})
|
||||
case windows.WM_KILLFOCUS:
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
case windows.WM_MOUSEMOVE:
|
||||
x, y := coordsFromlParam(lParam)
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.w.Event(pointer.Event{
|
||||
Type: pointer.Move,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
Buttons: w.pointerBtns,
|
||||
Time: windows.GetMessageTime(),
|
||||
})
|
||||
case windows.WM_MOUSEWHEEL:
|
||||
w.scrollEvent(wParam, lParam, false)
|
||||
case windows.WM_MOUSEHWHEEL:
|
||||
w.scrollEvent(wParam, lParam, true)
|
||||
case windows.WM_DESTROY:
|
||||
w.w.Event(ViewEvent{})
|
||||
w.w.Event(system.DestroyEvent{})
|
||||
if w.hdc != 0 {
|
||||
windows.ReleaseDC(w.hdc)
|
||||
w.hdc = 0
|
||||
}
|
||||
// The system destroys the HWND for us.
|
||||
w.hwnd = 0
|
||||
windows.PostQuitMessage(0)
|
||||
case windows.WM_PAINT:
|
||||
w.draw(true)
|
||||
case windows.WM_SIZE:
|
||||
switch wParam {
|
||||
case windows.SIZE_MINIMIZED:
|
||||
w.setStage(system.StagePaused)
|
||||
case windows.SIZE_MAXIMIZED, windows.SIZE_RESTORED:
|
||||
w.setStage(system.StageRunning)
|
||||
}
|
||||
case windows.WM_GETMINMAXINFO:
|
||||
mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam)))
|
||||
if w.minmax.minWidth > 0 || w.minmax.minHeight > 0 {
|
||||
mm.PtMinTrackSize = windows.Point{
|
||||
X: w.minmax.minWidth + w.deltas.width,
|
||||
Y: w.minmax.minHeight + w.deltas.height,
|
||||
}
|
||||
}
|
||||
if w.minmax.maxWidth > 0 || w.minmax.maxHeight > 0 {
|
||||
mm.PtMaxTrackSize = windows.Point{
|
||||
X: w.minmax.maxWidth + w.deltas.width,
|
||||
Y: w.minmax.maxHeight + w.deltas.height,
|
||||
}
|
||||
}
|
||||
case windows.WM_SETCURSOR:
|
||||
w.cursorIn = (lParam & 0xffff) == windows.HTCLIENT
|
||||
if w.cursorIn {
|
||||
windows.SetCursor(w.cursor)
|
||||
return windows.TRUE
|
||||
}
|
||||
case _WM_WAKEUP:
|
||||
w.w.Event(WakeupEvent{})
|
||||
}
|
||||
|
||||
return windows.DefWindowProc(hwnd, msg, wParam, lParam)
|
||||
}
|
||||
|
||||
func getModifiers() key.Modifiers {
|
||||
var kmods key.Modifiers
|
||||
if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 || windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 {
|
||||
kmods |= key.ModSuper
|
||||
}
|
||||
if windows.GetKeyState(windows.VK_MENU)&0x1000 != 0 {
|
||||
kmods |= key.ModAlt
|
||||
}
|
||||
if windows.GetKeyState(windows.VK_CONTROL)&0x1000 != 0 {
|
||||
kmods |= key.ModCtrl
|
||||
}
|
||||
if windows.GetKeyState(windows.VK_SHIFT)&0x1000 != 0 {
|
||||
kmods |= key.ModShift
|
||||
}
|
||||
return kmods
|
||||
}
|
||||
|
||||
func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
|
||||
var typ pointer.Type
|
||||
if press {
|
||||
typ = pointer.Press
|
||||
if w.pointerBtns == 0 {
|
||||
windows.SetCapture(w.hwnd)
|
||||
}
|
||||
w.pointerBtns |= btn
|
||||
} else {
|
||||
typ = pointer.Release
|
||||
w.pointerBtns &^= btn
|
||||
if w.pointerBtns == 0 {
|
||||
windows.ReleaseCapture()
|
||||
}
|
||||
}
|
||||
x, y := coordsFromlParam(lParam)
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.w.Event(pointer.Event{
|
||||
Type: typ,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
Buttons: w.pointerBtns,
|
||||
Time: windows.GetMessageTime(),
|
||||
Modifiers: kmods,
|
||||
})
|
||||
}
|
||||
|
||||
func coordsFromlParam(lParam uintptr) (int, int) {
|
||||
x := int(int16(lParam & 0xffff))
|
||||
y := int(int16((lParam >> 16) & 0xffff))
|
||||
return x, y
|
||||
}
|
||||
|
||||
func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool) {
|
||||
x, y := coordsFromlParam(lParam)
|
||||
// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
|
||||
// to other mouse events.
|
||||
np := windows.Point{X: int32(x), Y: int32(y)}
|
||||
windows.ScreenToClient(w.hwnd, &np)
|
||||
p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
|
||||
dist := float32(int16(wParam >> 16))
|
||||
var sp f32.Point
|
||||
if horizontal {
|
||||
sp.X = dist
|
||||
} else {
|
||||
sp.Y = -dist
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
Type: pointer.Scroll,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
Buttons: w.pointerBtns,
|
||||
Scroll: sp,
|
||||
Time: windows.GetMessageTime(),
|
||||
})
|
||||
}
|
||||
|
||||
// Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
|
||||
func (w *window) loop() error {
|
||||
msg := new(windows.Msg)
|
||||
loop:
|
||||
for {
|
||||
anim := w.animating
|
||||
if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
|
||||
w.draw(false)
|
||||
continue
|
||||
}
|
||||
switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
|
||||
case -1:
|
||||
return errors.New("GetMessage failed")
|
||||
case 0:
|
||||
// WM_QUIT received.
|
||||
break loop
|
||||
}
|
||||
windows.TranslateMessage(msg)
|
||||
windows.DispatchMessage(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
w.animating = anim
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) setStage(s system.Stage) {
|
||||
w.stage = s
|
||||
w.w.Event(system.StageEvent{Stage: s})
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
var r windows.Rect
|
||||
windows.GetClientRect(w.hwnd, &r)
|
||||
w.width = int(r.Right - r.Left)
|
||||
w.height = int(r.Bottom - r.Top)
|
||||
if w.width == 0 || w.height == 0 {
|
||||
return
|
||||
}
|
||||
dpi := windows.GetWindowDPI(w.hwnd)
|
||||
cfg := configForDPI(dpi)
|
||||
w.minmax = getWindowConstraints(cfg, w.opts)
|
||||
w.w.Event(FrameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: w.width,
|
||||
Y: w.height,
|
||||
},
|
||||
Metric: cfg,
|
||||
},
|
||||
Sync: sync,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
sort.Slice(drivers, func(i, j int) bool {
|
||||
return drivers[i].priority < drivers[j].priority
|
||||
})
|
||||
var errs []string
|
||||
for _, b := range drivers {
|
||||
ctx, err := b.initializer(w)
|
||||
if err == nil {
|
||||
return ctx, nil
|
||||
}
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return nil, fmt.Errorf("NewContext: failed to create a GPU device, tried: %s", strings.Join(errs, ", "))
|
||||
}
|
||||
return nil, errors.New("NewContext: no available GPU drivers")
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
w.readClipboard()
|
||||
}
|
||||
|
||||
func (w *window) readClipboard() error {
|
||||
if err := windows.OpenClipboard(w.hwnd); err != nil {
|
||||
return err
|
||||
}
|
||||
defer windows.CloseClipboard()
|
||||
mem, err := windows.GetClipboardData(windows.CF_UNICODETEXT)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ptr, err := windows.GlobalLock(mem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer windows.GlobalUnlock(mem)
|
||||
content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
|
||||
go func() {
|
||||
w.w.Event(clipboard.Event{Text: content})
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) Option(opts *Options) {
|
||||
w.opts = opts
|
||||
if o := opts.Size; o != nil {
|
||||
dpi := windows.GetSystemDPI()
|
||||
cfg := configForDPI(dpi)
|
||||
width := int32(cfg.Px(o.Width))
|
||||
height := int32(cfg.Px(o.Height))
|
||||
|
||||
// Include the window decorations.
|
||||
wr := windows.Rect{
|
||||
Right: width,
|
||||
Bottom: height,
|
||||
}
|
||||
dwStyle := uint32(windows.WS_OVERLAPPEDWINDOW)
|
||||
dwExStyle := uint32(windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE)
|
||||
windows.AdjustWindowRectEx(&wr, dwStyle, 0, dwExStyle)
|
||||
|
||||
dw, dh := width, height
|
||||
width = wr.Right - wr.Left
|
||||
height = wr.Bottom - wr.Top
|
||||
w.deltas.width = width - dw
|
||||
w.deltas.height = height - dh
|
||||
|
||||
w.opts.Size = o
|
||||
windows.MoveWindow(w.hwnd, 0, 0, width, height, true)
|
||||
}
|
||||
if o := opts.MinSize; o != nil {
|
||||
w.opts.MinSize = o
|
||||
}
|
||||
if o := opts.MaxSize; o != nil {
|
||||
w.opts.MaxSize = o
|
||||
}
|
||||
if o := opts.Title; o != nil {
|
||||
windows.SetWindowText(w.hwnd, *opts.Title)
|
||||
}
|
||||
if o := opts.WindowMode; o != nil {
|
||||
w.SetWindowMode(*o)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetWindowMode(mode WindowMode) {
|
||||
// https://devblogs.microsoft.com/oldnewthing/20100412-00/?p=14353
|
||||
switch mode {
|
||||
case Windowed:
|
||||
if w.placement == nil {
|
||||
return
|
||||
}
|
||||
windows.SetWindowPlacement(w.hwnd, w.placement)
|
||||
w.placement = nil
|
||||
style := windows.GetWindowLong(w.hwnd)
|
||||
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style|windows.WS_OVERLAPPEDWINDOW)
|
||||
windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST,
|
||||
0, 0, 0, 0,
|
||||
windows.SWP_NOOWNERZORDER|windows.SWP_FRAMECHANGED,
|
||||
)
|
||||
case Fullscreen:
|
||||
if w.placement != nil {
|
||||
return
|
||||
}
|
||||
w.placement = windows.GetWindowPlacement(w.hwnd)
|
||||
style := windows.GetWindowLong(w.hwnd)
|
||||
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style&^windows.WS_OVERLAPPEDWINDOW)
|
||||
mi := windows.GetMonitorInfo(w.hwnd)
|
||||
windows.SetWindowPos(w.hwnd, 0,
|
||||
mi.Monitor.Left, mi.Monitor.Top,
|
||||
mi.Monitor.Right-mi.Monitor.Left,
|
||||
mi.Monitor.Bottom-mi.Monitor.Top,
|
||||
windows.SWP_NOOWNERZORDER|windows.SWP_FRAMECHANGED,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
w.writeClipboard(s)
|
||||
}
|
||||
|
||||
func (w *window) writeClipboard(s string) error {
|
||||
if err := windows.OpenClipboard(w.hwnd); err != nil {
|
||||
return err
|
||||
}
|
||||
defer windows.CloseClipboard()
|
||||
if err := windows.EmptyClipboard(); err != nil {
|
||||
return err
|
||||
}
|
||||
u16, err := gowindows.UTF16FromString(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n := len(u16) * int(unsafe.Sizeof(u16[0]))
|
||||
mem, err := windows.GlobalAlloc(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ptr, err := windows.GlobalLock(mem)
|
||||
if err != nil {
|
||||
windows.GlobalFree(mem)
|
||||
return err
|
||||
}
|
||||
var u16v []uint16
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&u16v))
|
||||
hdr.Data = ptr
|
||||
hdr.Cap = len(u16)
|
||||
hdr.Len = len(u16)
|
||||
copy(u16v, u16)
|
||||
windows.GlobalUnlock(mem)
|
||||
if err := windows.SetClipboardData(windows.CF_UNICODETEXT, mem); err != nil {
|
||||
windows.GlobalFree(mem)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) SetCursor(name pointer.CursorName) {
|
||||
c, err := loadCursor(name)
|
||||
if err != nil {
|
||||
c = resources.cursor
|
||||
}
|
||||
w.cursor = c
|
||||
if w.cursorIn {
|
||||
windows.SetCursor(w.cursor)
|
||||
}
|
||||
}
|
||||
|
||||
func loadCursor(name pointer.CursorName) (syscall.Handle, error) {
|
||||
var curID uint16
|
||||
switch name {
|
||||
default:
|
||||
fallthrough
|
||||
case pointer.CursorDefault:
|
||||
return resources.cursor, nil
|
||||
case pointer.CursorText:
|
||||
curID = windows.IDC_IBEAM
|
||||
case pointer.CursorPointer:
|
||||
curID = windows.IDC_HAND
|
||||
case pointer.CursorCrossHair:
|
||||
curID = windows.IDC_CROSS
|
||||
case pointer.CursorColResize:
|
||||
curID = windows.IDC_SIZEWE
|
||||
case pointer.CursorRowResize:
|
||||
curID = windows.IDC_SIZENS
|
||||
case pointer.CursorGrab:
|
||||
curID = windows.IDC_SIZEALL
|
||||
case pointer.CursorNone:
|
||||
return 0, nil
|
||||
}
|
||||
return windows.LoadCursor(curID)
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {}
|
||||
|
||||
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||
|
||||
func (w *window) HDC() syscall.Handle {
|
||||
return w.hdc
|
||||
}
|
||||
|
||||
func (w *window) HWND() (syscall.Handle, int, int) {
|
||||
return w.hwnd, w.width, w.height
|
||||
}
|
||||
|
||||
func (w *window) Close() {
|
||||
windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0)
|
||||
}
|
||||
|
||||
func convertKeyCode(code uintptr) (string, bool) {
|
||||
if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
|
||||
return string(rune(code)), true
|
||||
}
|
||||
var r string
|
||||
switch code {
|
||||
case windows.VK_ESCAPE:
|
||||
r = key.NameEscape
|
||||
case windows.VK_LEFT:
|
||||
r = key.NameLeftArrow
|
||||
case windows.VK_RIGHT:
|
||||
r = key.NameRightArrow
|
||||
case windows.VK_RETURN:
|
||||
r = key.NameReturn
|
||||
case windows.VK_UP:
|
||||
r = key.NameUpArrow
|
||||
case windows.VK_DOWN:
|
||||
r = key.NameDownArrow
|
||||
case windows.VK_HOME:
|
||||
r = key.NameHome
|
||||
case windows.VK_END:
|
||||
r = key.NameEnd
|
||||
case windows.VK_BACK:
|
||||
r = key.NameDeleteBackward
|
||||
case windows.VK_DELETE:
|
||||
r = key.NameDeleteForward
|
||||
case windows.VK_PRIOR:
|
||||
r = key.NamePageUp
|
||||
case windows.VK_NEXT:
|
||||
r = key.NamePageDown
|
||||
case windows.VK_F1:
|
||||
r = "F1"
|
||||
case windows.VK_F2:
|
||||
r = "F2"
|
||||
case windows.VK_F3:
|
||||
r = "F3"
|
||||
case windows.VK_F4:
|
||||
r = "F4"
|
||||
case windows.VK_F5:
|
||||
r = "F5"
|
||||
case windows.VK_F6:
|
||||
r = "F6"
|
||||
case windows.VK_F7:
|
||||
r = "F7"
|
||||
case windows.VK_F8:
|
||||
r = "F8"
|
||||
case windows.VK_F9:
|
||||
r = "F9"
|
||||
case windows.VK_F10:
|
||||
r = "F10"
|
||||
case windows.VK_F11:
|
||||
r = "F11"
|
||||
case windows.VK_F12:
|
||||
r = "F12"
|
||||
case windows.VK_TAB:
|
||||
r = key.NameTab
|
||||
case windows.VK_SPACE:
|
||||
r = key.NameSpace
|
||||
case windows.VK_OEM_1:
|
||||
r = ";"
|
||||
case windows.VK_OEM_PLUS:
|
||||
r = "+"
|
||||
case windows.VK_OEM_COMMA:
|
||||
r = ","
|
||||
case windows.VK_OEM_MINUS:
|
||||
r = "-"
|
||||
case windows.VK_OEM_PERIOD:
|
||||
r = "."
|
||||
case windows.VK_OEM_2:
|
||||
r = "/"
|
||||
case windows.VK_OEM_3:
|
||||
r = "`"
|
||||
case windows.VK_OEM_4:
|
||||
r = "["
|
||||
case windows.VK_OEM_5, windows.VK_OEM_102:
|
||||
r = "\\"
|
||||
case windows.VK_OEM_6:
|
||||
r = "]"
|
||||
case windows.VK_OEM_7:
|
||||
r = "'"
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
return r, true
|
||||
}
|
||||
|
||||
func configForDPI(dpi int) unit.Metric {
|
||||
const inchPrDp = 1.0 / 96.0
|
||||
ppdp := float32(dpi) * inchPrDp
|
||||
return unit.Metric{
|
||||
PxPerDp: ppdp,
|
||||
PxPerSp: ppdp,
|
||||
}
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
@@ -1,757 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build (linux && !android && !nox11) || freebsd || openbsd
|
||||
// +build linux,!android,!nox11 freebsd openbsd
|
||||
|
||||
package wm
|
||||
|
||||
/*
|
||||
#cgo openbsd CFLAGS: -I/usr/X11R6/include -I/usr/local/include
|
||||
#cgo 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"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/unit"
|
||||
|
||||
syscall "golang.org/x/sys/unix"
|
||||
|
||||
"gioui.org/app/internal/xkb"
|
||||
)
|
||||
|
||||
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
|
||||
// "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
|
||||
}
|
||||
stage system.Stage
|
||||
cfg unit.Metric
|
||||
width int
|
||||
height int
|
||||
notify struct {
|
||||
read, write int
|
||||
}
|
||||
dead bool
|
||||
|
||||
animating bool
|
||||
|
||||
pointerBtns pointer.Buttons
|
||||
|
||||
clipboard struct {
|
||||
content []byte
|
||||
}
|
||||
cursor pointer.CursorName
|
||||
mode WindowMode
|
||||
|
||||
wakeups chan struct{}
|
||||
}
|
||||
|
||||
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(s string) {
|
||||
w.clipboard.content = []byte(s)
|
||||
C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
|
||||
}
|
||||
|
||||
func (w *x11Window) Option(opts *Options) {
|
||||
var shints C.XSizeHints
|
||||
if o := opts.MinSize; o != nil {
|
||||
shints.min_width = C.int(w.cfg.Px(o.Width))
|
||||
shints.min_height = C.int(w.cfg.Px(o.Height))
|
||||
shints.flags = C.PMinSize
|
||||
}
|
||||
if o := opts.MaxSize; o != nil {
|
||||
shints.max_width = C.int(w.cfg.Px(o.Width))
|
||||
shints.max_height = C.int(w.cfg.Px(o.Height))
|
||||
shints.flags = shints.flags | C.PMaxSize
|
||||
}
|
||||
if shints.flags != 0 {
|
||||
C.XSetWMNormalHints(w.x, w.xw, &shints)
|
||||
}
|
||||
|
||||
if o := opts.Size; o != nil {
|
||||
C.XResizeWindow(w.x, w.xw, C.uint(w.cfg.Px(o.Width)), C.uint(w.cfg.Px(o.Height)))
|
||||
}
|
||||
|
||||
var title string
|
||||
if o := opts.Title; o != nil {
|
||||
title = *o
|
||||
}
|
||||
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)
|
||||
|
||||
if o := opts.WindowMode; o != nil {
|
||||
w.SetWindowMode(*o)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) SetCursor(name pointer.CursorName) {
|
||||
switch name {
|
||||
case pointer.CursorNone:
|
||||
w.cursor = name
|
||||
C.XFixesHideCursor(w.x, w.xw)
|
||||
return
|
||||
case pointer.CursorGrab:
|
||||
name = "hand1"
|
||||
}
|
||||
if w.cursor == pointer.CursorNone {
|
||||
C.XFixesShowCursor(w.x, w.xw)
|
||||
}
|
||||
cname := C.CString(string(name))
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
c := C.XcursorLibraryLoadCursor(w.x, cname)
|
||||
if c == 0 {
|
||||
name = pointer.CursorDefault
|
||||
}
|
||||
w.cursor = name
|
||||
// If c if null (i.e. name was not found),
|
||||
// XDefineCursor will use the default cursor.
|
||||
C.XDefineCursor(w.x, w.xw, c)
|
||||
}
|
||||
|
||||
func (w *x11Window) SetWindowMode(mode WindowMode) {
|
||||
switch mode {
|
||||
case w.mode:
|
||||
return
|
||||
case Windowed:
|
||||
C.XDeleteProperty(w.x, w.xw, w.atoms.wmStateFullscreen)
|
||||
case Fullscreen:
|
||||
C.XChangeProperty(w.x, w.xw, w.atoms.wmState, C.XA_ATOM,
|
||||
32, C.PropModeReplace,
|
||||
(*C.uchar)(unsafe.Pointer(&w.atoms.wmStateFullscreen)), 1,
|
||||
)
|
||||
default:
|
||||
return
|
||||
}
|
||||
w.mode = mode
|
||||
// "A Client wishing to change the state of a window MUST send
|
||||
// a _NET_WM_STATE client message to the root window (see below)."
|
||||
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,
|
||||
}
|
||||
arr := (*[5]C.long)(unsafe.Pointer(&ev.data))
|
||||
arr[0] = 2 // _NET_WM_STATE_TOGGLE
|
||||
arr[1] = C.long(w.atoms.wmStateFullscreen)
|
||||
arr[2] = 0
|
||||
arr[3] = 1 // application
|
||||
arr[4] = 0
|
||||
C.XSendEvent(
|
||||
w.x,
|
||||
C.XDefaultRootWindow(w.x), // MUST be the root window
|
||||
C.False,
|
||||
C.SubstructureNotifyMask|C.SubstructureRedirectMask,
|
||||
&xev,
|
||||
)
|
||||
}
|
||||
|
||||
func (w *x11Window) ShowTextInput(show bool) {}
|
||||
|
||||
func (w *x11Window) SetInputHint(_ key.InputHint) {}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
var x11OneByte = make([]byte, 1)
|
||||
|
||||
func (w *x11Window) Wakeup() {
|
||||
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.width, w.height
|
||||
}
|
||||
|
||||
func (w *x11Window) setStage(s system.Stage) {
|
||||
if s == w.stage {
|
||||
return
|
||||
}
|
||||
w.stage = s
|
||||
w.w.Event(system.StageEvent{Stage: s})
|
||||
}
|
||||
|
||||
func (w *x11Window) loop() {
|
||||
h := x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
||||
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.
|
||||
buf := make([]byte, 100)
|
||||
|
||||
loop:
|
||||
for !w.dead {
|
||||
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.
|
||||
if syn = h.handleEvents(); !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 = h.handleEvents()
|
||||
if w.dead {
|
||||
break loop
|
||||
}
|
||||
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clear notifications.
|
||||
for {
|
||||
_, err := syscall.Read(w.notify.read, buf)
|
||||
if err == syscall.EAGAIN {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-w.wakeups:
|
||||
w.w.Event(WakeupEvent{})
|
||||
default:
|
||||
}
|
||||
|
||||
if (anim || syn) && w.width != 0 && w.height != 0 {
|
||||
w.w.Event(FrameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: w.width,
|
||||
Y: w.height,
|
||||
},
|
||||
Metric: w.cfg,
|
||||
},
|
||||
Sync: syn,
|
||||
})
|
||||
}
|
||||
}
|
||||
w.w.Event(system.DestroyEvent{Err: nil})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
w.w.Event(e)
|
||||
}
|
||||
case C.ButtonPress, C.ButtonRelease:
|
||||
bevt := (*C.XButtonEvent)(unsafe.Pointer(xev))
|
||||
ev := pointer.Event{
|
||||
Type: 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.Type = 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:
|
||||
// scroll up
|
||||
ev.Type = pointer.Scroll
|
||||
ev.Scroll.Y = -scrollScale
|
||||
case C.Button5:
|
||||
// scroll down
|
||||
ev.Type = pointer.Scroll
|
||||
ev.Scroll.Y = +scrollScale
|
||||
case 6:
|
||||
// http://xahlee.info/linux/linux_x11_mouse_button_number.html
|
||||
// scroll left
|
||||
ev.Type = pointer.Scroll
|
||||
ev.Scroll.X = -scrollScale * 2
|
||||
case 7:
|
||||
// scroll right
|
||||
ev.Type = 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.w.Event(ev)
|
||||
case C.MotionNotify:
|
||||
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
|
||||
w.w.Event(pointer.Event{
|
||||
Type: 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.w.Event(key.FocusEvent{Focus: true})
|
||||
case C.FocusOut:
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
case C.ConfigureNotify: // window configuration change
|
||||
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
||||
w.width = int(cevt.width)
|
||||
w.height = int(cevt.height)
|
||||
// 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.w.Event(clipboard.Event{Text: str})
|
||||
case C.SelectionRequest:
|
||||
cevt := (*C.XSelectionRequestEvent)(unsafe.Pointer(xev))
|
||||
if cevt.selection != w.atoms.clipboard || 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.dead = true
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return redraw
|
||||
}
|
||||
|
||||
var (
|
||||
x11Threads sync.Once
|
||||
)
|
||||
|
||||
func init() {
|
||||
x11Driver = newX11Window
|
||||
}
|
||||
|
||||
func newX11Window(gioWin Callbacks, opts *Options) 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}
|
||||
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,
|
||||
}
|
||||
var width, height int
|
||||
if o := opts.Size; o != nil {
|
||||
width = cfg.Px(o.Width)
|
||||
height = cfg.Px(o.Height)
|
||||
}
|
||||
win := C.XCreateWindow(dpy, C.XDefaultRootWindow(dpy),
|
||||
0, 0, C.uint(width), C.uint(height),
|
||||
0, C.CopyFromParent, C.InputOutput, nil,
|
||||
C.CWEventMask|C.CWBackPixmap|C.CWOverrideRedirect, &swa)
|
||||
|
||||
w := &x11Window{
|
||||
w: gioWin, x: dpy, xw: win,
|
||||
width: width,
|
||||
height: height,
|
||||
cfg: cfg,
|
||||
xkb: xkb,
|
||||
xkbEventBase: xkbEventBase,
|
||||
wakeups: make(chan struct{}, 1),
|
||||
}
|
||||
w.notify.read = pipe[0]
|
||||
w.notify.write = pipe[1]
|
||||
|
||||
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(filepath.Base(os.Args[0]))
|
||||
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.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)
|
||||
|
||||
// extensions
|
||||
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
|
||||
|
||||
w.Option(opts)
|
||||
|
||||
// make the window visible on the screen
|
||||
C.XMapWindow(dpy, win)
|
||||
|
||||
go func() {
|
||||
w.w.SetDriver(w)
|
||||
w.w.Event(ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
||||
w.setStage(system.StageRunning)
|
||||
w.loop()
|
||||
w.w.Event(ViewEvent{})
|
||||
w.destroy()
|
||||
}()
|
||||
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
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build android || (darwin && ios)
|
||||
// +build android darwin,ios
|
||||
|
||||
package wm
|
||||
|
||||
// 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
|
||||
fn()
|
||||
})
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
// +build linux,!android,!nowayland freebsd
|
||||
|
||||
/* Generated by wayland-scanner 1.17.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 *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", "", types + 0 },
|
||||
{ "enable", "", types + 0 },
|
||||
{ "disable", "", types + 0 },
|
||||
{ "set_surrounding_text", "sii", types + 0 },
|
||||
{ "set_text_change_cause", "u", types + 0 },
|
||||
{ "set_content_type", "uu", types + 0 },
|
||||
{ "set_cursor_rectangle", "iiii", types + 0 },
|
||||
{ "commit", "", types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message zwp_text_input_v3_events[] = {
|
||||
{ "enter", "o", types + 4 },
|
||||
{ "leave", "o", types + 5 },
|
||||
{ "preedit_string", "?sii", types + 0 },
|
||||
{ "commit_string", "?s", types + 0 },
|
||||
{ "delete_surrounding_text", "uu", types + 0 },
|
||||
{ "done", "u", 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", "", types + 0 },
|
||||
{ "get_text_input", "no", 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,
|
||||
};
|
||||
|
||||
@@ -1,819 +0,0 @@
|
||||
/* Generated by wayland-scanner 1.17.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;
|
||||
|
||||
/**
|
||||
* @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;
|
||||
/**
|
||||
* @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;
|
||||
|
||||
#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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
@@ -1,77 +0,0 @@
|
||||
// +build linux,!android,!nowayland freebsd
|
||||
|
||||
/* Generated by wayland-scanner 1.17.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 *types[] = {
|
||||
NULL,
|
||||
&zxdg_toplevel_decoration_v1_interface,
|
||||
&xdg_toplevel_interface,
|
||||
};
|
||||
|
||||
static const struct wl_message zxdg_decoration_manager_v1_requests[] = {
|
||||
{ "destroy", "", types + 0 },
|
||||
{ "get_toplevel_decoration", "no", 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", "", types + 0 },
|
||||
{ "set_mode", "u", types + 0 },
|
||||
{ "unset_mode", "", types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message zxdg_toplevel_decoration_v1_events[] = {
|
||||
{ "configure", "u", 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,
|
||||
};
|
||||
|
||||
@@ -1,376 +0,0 @@
|
||||
/* Generated by wayland-scanner 1.17.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;
|
||||
|
||||
/**
|
||||
* @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;
|
||||
/**
|
||||
* @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;
|
||||
|
||||
#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 a 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 a 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
|
||||
@@ -1,176 +0,0 @@
|
||||
// +build linux,!android,!nowayland freebsd
|
||||
|
||||
/* Generated by wayland-scanner 1.17.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 *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,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_wm_base_requests[] = {
|
||||
{ "destroy", "", types + 0 },
|
||||
{ "create_positioner", "n", types + 4 },
|
||||
{ "get_xdg_surface", "no", types + 5 },
|
||||
{ "pong", "u", types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_wm_base_events[] = {
|
||||
{ "ping", "u", types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_wm_base_interface = {
|
||||
"xdg_wm_base", 2,
|
||||
4, xdg_wm_base_requests,
|
||||
1, xdg_wm_base_events,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_positioner_requests[] = {
|
||||
{ "destroy", "", types + 0 },
|
||||
{ "set_size", "ii", types + 0 },
|
||||
{ "set_anchor_rect", "iiii", types + 0 },
|
||||
{ "set_anchor", "u", types + 0 },
|
||||
{ "set_gravity", "u", types + 0 },
|
||||
{ "set_constraint_adjustment", "u", types + 0 },
|
||||
{ "set_offset", "ii", types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_positioner_interface = {
|
||||
"xdg_positioner", 2,
|
||||
7, xdg_positioner_requests,
|
||||
0, NULL,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_surface_requests[] = {
|
||||
{ "destroy", "", types + 0 },
|
||||
{ "get_toplevel", "n", types + 7 },
|
||||
{ "get_popup", "n?oo", types + 8 },
|
||||
{ "set_window_geometry", "iiii", types + 0 },
|
||||
{ "ack_configure", "u", types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_surface_events[] = {
|
||||
{ "configure", "u", types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_surface_interface = {
|
||||
"xdg_surface", 2,
|
||||
5, xdg_surface_requests,
|
||||
1, xdg_surface_events,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_toplevel_requests[] = {
|
||||
{ "destroy", "", types + 0 },
|
||||
{ "set_parent", "?o", types + 11 },
|
||||
{ "set_title", "s", types + 0 },
|
||||
{ "set_app_id", "s", types + 0 },
|
||||
{ "show_window_menu", "ouii", types + 12 },
|
||||
{ "move", "ou", types + 16 },
|
||||
{ "resize", "ouu", types + 18 },
|
||||
{ "set_max_size", "ii", types + 0 },
|
||||
{ "set_min_size", "ii", types + 0 },
|
||||
{ "set_maximized", "", types + 0 },
|
||||
{ "unset_maximized", "", types + 0 },
|
||||
{ "set_fullscreen", "?o", types + 21 },
|
||||
{ "unset_fullscreen", "", types + 0 },
|
||||
{ "set_minimized", "", types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_toplevel_events[] = {
|
||||
{ "configure", "iia", types + 0 },
|
||||
{ "close", "", types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_toplevel_interface = {
|
||||
"xdg_toplevel", 2,
|
||||
14, xdg_toplevel_requests,
|
||||
2, xdg_toplevel_events,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_popup_requests[] = {
|
||||
{ "destroy", "", types + 0 },
|
||||
{ "grab", "ou", types + 22 },
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_popup_events[] = {
|
||||
{ "configure", "iiii", types + 0 },
|
||||
{ "popup_done", "", types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_popup_interface = {
|
||||
"xdg_popup", 2,
|
||||
2, xdg_popup_requests,
|
||||
2, xdg_popup_events,
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,148 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// package wm implements platform specific windows
|
||||
// and GPU contexts.
|
||||
package wm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image/color"
|
||||
|
||||
"gioui.org/io/key"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type Size struct {
|
||||
Width unit.Value
|
||||
Height unit.Value
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Size *Size
|
||||
MinSize *Size
|
||||
MaxSize *Size
|
||||
Title *string
|
||||
WindowMode *WindowMode
|
||||
StatusColor *color.NRGBA
|
||||
NavigationColor *color.NRGBA
|
||||
Orientation *Orientation
|
||||
CustomRenderer bool
|
||||
}
|
||||
|
||||
type WakeupEvent struct{}
|
||||
|
||||
type WindowMode uint8
|
||||
|
||||
const (
|
||||
Windowed WindowMode = iota
|
||||
Fullscreen
|
||||
)
|
||||
|
||||
type Orientation uint8
|
||||
|
||||
const (
|
||||
AnyOrientation Orientation = iota
|
||||
LandscapeOrientation
|
||||
PortraitOrientation
|
||||
)
|
||||
|
||||
type FrameEvent struct {
|
||||
system.FrameEvent
|
||||
|
||||
Sync bool
|
||||
}
|
||||
|
||||
type Callbacks interface {
|
||||
SetDriver(d Driver)
|
||||
Event(e event.Event)
|
||||
}
|
||||
|
||||
type Context interface {
|
||||
API() gpu.API
|
||||
RenderTarget() gpu.RenderTarget
|
||||
Present() error
|
||||
Refresh() error
|
||||
Release()
|
||||
Lock() error
|
||||
Unlock()
|
||||
}
|
||||
|
||||
// ErrDeviceLost is returned from Context.Present when
|
||||
// the underlying GPU device is gone and should be
|
||||
// recreated.
|
||||
var ErrDeviceLost = errors.New("GPU device lost")
|
||||
|
||||
// Driver is the interface for the platform implementation
|
||||
// of a window.
|
||||
type Driver interface {
|
||||
// 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(s string)
|
||||
|
||||
// Option processes option changes.
|
||||
Option(opts *Options)
|
||||
|
||||
// SetCursor updates the current cursor to name.
|
||||
SetCursor(name pointer.CursorName)
|
||||
|
||||
// Close the window.
|
||||
Close()
|
||||
// Wakeup wakes up the event loop and sends a WakeupEvent.
|
||||
Wakeup()
|
||||
}
|
||||
|
||||
type windowRendezvous struct {
|
||||
in chan windowAndOptions
|
||||
out chan windowAndOptions
|
||||
errs chan error
|
||||
}
|
||||
|
||||
type windowAndOptions struct {
|
||||
window Callbacks
|
||||
opts *Options
|
||||
}
|
||||
|
||||
func newWindowRendezvous() *windowRendezvous {
|
||||
wr := &windowRendezvous{
|
||||
in: make(chan windowAndOptions),
|
||||
out: make(chan windowAndOptions),
|
||||
errs: make(chan error),
|
||||
}
|
||||
go func() {
|
||||
var main windowAndOptions
|
||||
var out chan windowAndOptions
|
||||
for {
|
||||
select {
|
||||
case w := <-wr.in:
|
||||
var err error
|
||||
if main.window != nil {
|
||||
err = errors.New("multiple windows are not supported")
|
||||
}
|
||||
wr.errs <- err
|
||||
main = w
|
||||
out = wr.out
|
||||
case out <- main:
|
||||
}
|
||||
}
|
||||
}()
|
||||
return wr
|
||||
}
|
||||
|
||||
func (_ WakeupEvent) ImplementsEvent() {}
|
||||
Reference in New Issue
Block a user