Merge pull request #368 from FreeSlave/x11_additions

X11 loop cleanup, less redraws. Support for non-resizable windows, mi…
This commit is contained in:
Vadim Lopatin 2017-06-08 08:52:30 +03:00 committed by GitHub
commit 47615966db
4 changed files with 254 additions and 115 deletions

View File

@ -358,6 +358,9 @@ extern (C) int UIAppMain(string[] args) {
MenuItem windowItem = new MenuItem(new Action(3, "MENU_WINDOW"c));
windowItem.add(new Action(30, "MENU_WINDOW_PREFERENCES"));
windowItem.add(new Action(31, UIString.fromId("MENU_WINDOW_MINIMIZE")));
windowItem.add(new Action(32, UIString.fromId("MENU_WINDOW_MAXIMIZE")));
windowItem.add(new Action(33, UIString.fromId("MENU_WINDOW_RESTORE")));
MenuItem helpItem = new MenuItem(new Action(4, "MENU_HELP"c));
helpItem.add(new Action(40, "MENU_HELP_VIEW_HELP"));
MenuItem aboutItem = new MenuItem(new Action(41, "MENU_HELP_ABOUT"));
@ -377,6 +380,15 @@ extern (C) int UIAppMain(string[] args) {
if (a.id == ACTION_FILE_EXIT) {
window.close();
return true;
} else if (a.id == 31) {
window.minimizeWindow();
return true;
} else if (a.id == 32) {
window.maximizeWindow();
return true;
} else if (a.id == 33) {
window.restoreWindow();
return true;
} else if (a.id == 41) {
window.showMessageBox(UIString.fromRaw("About"d), UIString.fromRaw("DLangUI demo app\n(C) Vadim Lopatin, 2014\nhttp://github.com/buggins/dlangui"d));
return true;

View File

@ -23,6 +23,9 @@ MENU_VIEW_THEME_DARK=Dark
MENU_VIEW_THEME_CUSTOM1=Custom 1
MENU_WINDOW=&Window
MENU_WINDOW_PREFERENCES=&Preferences
MENU_WINDOW_MINIMIZE=Minimize
MENU_WINDOW_MAXIMIZE=Maximize
MENU_WINDOW_RESTORE=Restore
MENU_HELP=&Help
MENU_HELP_VIEW_HELP=&View help
MENU_HELP_ABOUT=&About

View File

@ -21,6 +21,9 @@ MENU_VIEW_THEME_DARK=Тёмная
MENU_VIEW_THEME_CUSTOM1=Пример 1
MENU_WINDOW=&Окно
MENU_WINDOW_PREFERENCES=&Настройки
MENU_WINDOW_MINIMIZE=Свернуть
MENU_WINDOW_MAXIMIZE=Развернуть
MENU_WINDOW_RESTORE=Восстановить
MENU_HELP=&Справка
MENU_HELP_VIEW_HELP=&Просмотр справки
MENU_HELP_ABOUT=&О программе

View File

@ -40,28 +40,43 @@ static if (ENABLE_OPENGL) {
//pragma(lib, "X11");
private __gshared Display * x11display;
private __gshared Display * x11display2;
private __gshared int x11screen;
private __gshared XIM xim;
alias XWindow = x11.Xlib.Window;
alias DWindow = dlangui.platforms.common.platform.Window;
private __gshared string localClipboardContent;
private __gshared Atom atom_UTF8_STRING;
private __gshared Atom atom_CLIPBOARD;
private __gshared Atom atom_TARGETS;
private __gshared
{
Display * x11display;
Display * x11display2;
int x11screen;
XIM xim;
private __gshared Atom atom_WM_PROTOCOLS;
private __gshared Atom atom_WM_DELETE_WINDOW;
string localClipboardContent;
bool _enableOpengl = false;
private __gshared Atom atom_NET_WM_ICON;
private __gshared Atom atom_NET_WM_NAME;
private __gshared Atom atom_NET_WM_ICON_NAME;
Cursor[CursorType.Hand + 1] x11cursors;
private __gshared Atom atom_DLANGUI_TIMER_EVENT;
private __gshared Atom atom_DLANGUI_TASK_EVENT;
Atom atom_UTF8_STRING;
Atom atom_CLIPBOARD;
Atom atom_TARGETS;
Atom atom_WM_PROTOCOLS;
Atom atom_WM_DELETE_WINDOW;
Atom atom_NET_WM_ICON;
Atom atom_NET_WM_NAME;
Atom atom_NET_WM_ICON_NAME;
Atom atom_NET_WM_STATE;
Atom atom_NET_WM_STATE_MODAL;
Atom atom_NET_WM_STATE_MAXIMIZED_VERT;
Atom atom_NET_WM_STATE_MAXIMIZED_HORZ;
Atom atom_NET_WM_STATE_HIDDEN;
Atom atom_NET_WM_STATE_FULLSCREEN;
Atom atom_DLANGUI_TIMER_EVENT;
Atom atom_DLANGUI_TASK_EVENT;
Atom atom_DLANGUI_CLOSE_WINDOW_EVENT;
}
static void setupX11Atoms()
{
@ -75,14 +90,18 @@ static void setupX11Atoms()
atom_NET_WM_ICON = XInternAtom(x11display, "_NET_WM_ICON", True);
atom_NET_WM_NAME = XInternAtom(x11display, "_NET_WM_NAME", True);
atom_NET_WM_ICON_NAME = XInternAtom(x11display, "_NET_WM_ICON_NAME", True);
atom_NET_WM_STATE = XInternAtom(x11display, "_NET_WM_STATE", True);
atom_NET_WM_STATE_MODAL = XInternAtom(x11display, "_NET_WM_STATE_MODAL", True);
atom_NET_WM_STATE_MAXIMIZED_VERT = XInternAtom(x11display, "_NET_WM_STATE_MAXIMIZED_VERT", True);
atom_NET_WM_STATE_MAXIMIZED_HORZ = XInternAtom(x11display, "_NET_WM_STATE_MAXIMIZED_HORZ", True);
atom_NET_WM_STATE_HIDDEN = XInternAtom(x11display, "_NET_WM_STATE_HIDDEN", True);
atom_NET_WM_STATE_FULLSCREEN = XInternAtom(x11display, "_NET_WM_STATE_FULLSCREEN", True);
atom_DLANGUI_TIMER_EVENT = XInternAtom(x11display, "DLANGUI_TIMER_EVENT", False);
atom_DLANGUI_TASK_EVENT = XInternAtom(x11display, "DLANGUI_TASK_EVENT", False);
atom_DLANGUI_CLOSE_WINDOW_EVENT = XInternAtom(x11display, "DLANGUI_CLOSE_WINDOW_EVENT", False);
}
private __gshared bool _enableOpengl = false;
private __gshared Cursor[CursorType.Hand + 1] x11cursors;
// Cursor font constants
enum {
XC_X_cursor=0,
@ -205,6 +224,12 @@ class X11Window : DWindow {
protected GC _gc;
private __gshared XIC xic;
X11Window[] _children;
X11Window _parent;
bool _needRedraw;
int _cachedWidth, _cachedHeight;
static if (ENABLE_OPENGL) {
GLXContext _glc;
}
@ -217,9 +242,9 @@ class X11Window : DWindow {
width = 500;
if (height == 0)
height = 300;
_dx = width;
_dy = height;
//create(flags);
_cachedWidth = _dx = width;
_cachedHeight = _dy = height;
_flags = flags;
/* get the colors black and white (see section for details) */
ulong black, white;
@ -282,18 +307,42 @@ class X11Window : DWindow {
return;
}
//XMapWindow(x11display, _win);
//XSync(x11display, false);
//readln();
/* here is where some properties of the window can be set.
The third and fourth items indicate the name which appears
at the top of the window and the name of the minimized window
respectively.
*/
windowCaption = caption;
XSetWMProtocols(x11display, _win, &atom_WM_DELETE_WINDOW, 1);
_children.reserve(20);
_parent = cast(X11Window) parent;
if (_parent)
_parent._children ~= this;
if (!(flags & WindowFlag.Resizable)) {
XSizeHints sizeHints;
sizeHints.min_width = width;
sizeHints.min_height = height;
sizeHints.max_width = width;
sizeHints.max_height = height;
sizeHints.flags = PMaxSize | PMinSize;
XSetWMNormalHints(x11display, _win, &sizeHints);
}
if (flags & WindowFlag.Fullscreen) {
if (atom_NET_WM_STATE_FULLSCREEN != None) {
changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_FULLSCREEN);
}
else
Log.w("Missing _NET_WM_STATE_FULLSCREEN atom");
}
if (flags & WindowFlag.Modal) {
if (_parent) {
XSetTransientForHint(x11display, _win, _parent._win);
} else {
Log.w("Top-level modal window");
}
if (atom_NET_WM_STATE_MODAL != None) {
changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_MODAL);
} else {
Log.w("Missing _NET_WM_STATE_MODAL atom");
}
}
/* this routine determines which types of input are allowed in
the input. see the appropriate section for details...
*/
@ -321,15 +370,26 @@ class X11Window : DWindow {
}
~this() {
debug Log.d("Destroying X11 window");
if (timer) {
timer.stop();
}
if (_parent) {
import std.algorithm : countUntil, remove;
ptrdiff_t index = countUntil(_parent._children,this);
if (index > -1 ) {
_parent._children = _parent._children.remove(index);
}
_parent = null;
}
static if (ENABLE_OPENGL) {
if (_glc) {
glXDestroyContext(x11display, _glc);
_glc = null;
}
}
if (_drawbuf)
destroy(_drawbuf);
if (_gc) {
XFreeGC(x11display, _gc);
_gc = null;
@ -369,6 +429,79 @@ class X11Window : DWindow {
_mainWidget.setFocus();
}
protected final void changeWindowState(int action, Atom firstProperty, Atom secondProperty = None) nothrow
{
XEvent ev;
memset(&ev, 0, ev.sizeof);
ev.xany.type = ClientMessage;
ev.xclient.window = _win;
ev.xclient.message_type = atom_NET_WM_STATE;
ev.xclient.format = 32;
ev.xclient.data.l[0] = action;
ev.xclient.data.l[1] = firstProperty;
if (secondProperty != None)
ev.xclient.data.l[2] = secondProperty;
ev.xclient.data.l[3] = 0;
XSendEvent(x11display, RootWindow(x11display, x11screen), false, SubstructureNotifyMask|SubstructureRedirectMask, &ev);
}
protected enum {
_NET_WM_STATE_REMOVE = 0,
_NET_WM_STATE_ADD,
_NET_WM_STATE_TOGGLE
}
override bool setWindowState(WindowState newState, bool activate = false, Rect newWindowRect = RECT_VALUE_IS_NOT_SET) {
if (_win == None) {
return false;
}
bool result = false;
switch(newState) {
case WindowState.maximized:
if (atom_NET_WM_STATE != None && atom_NET_WM_STATE_MAXIMIZED_HORZ != None && atom_NET_WM_STATE_MAXIMIZED_VERT != None) {
changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_MAXIMIZED_HORZ, atom_NET_WM_STATE_MAXIMIZED_VERT);
result = true;
}
break;
case WindowState.minimized:
if (atom_NET_WM_STATE != None && atom_NET_WM_STATE_HIDDEN != None) {
changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_HIDDEN);
result = true;
}
break;
case WindowState.hidden:
XUnmapWindow(x11display, _win);
result = true;
break;
case WindowState.normal:
if (atom_NET_WM_STATE != None &&
atom_NET_WM_STATE_MAXIMIZED_HORZ != None &&
atom_NET_WM_STATE_MAXIMIZED_VERT != None &&
atom_NET_WM_STATE_HIDDEN != None)
{
changeWindowState(_NET_WM_STATE_REMOVE, atom_NET_WM_STATE_MAXIMIZED_HORZ, atom_NET_WM_STATE_MAXIMIZED_VERT);
changeWindowState(_NET_WM_STATE_REMOVE, atom_NET_WM_STATE_HIDDEN);
changeWindowState(_NET_WM_STATE_REMOVE, atom_NET_WM_STATE_FULLSCREEN);
result = true;
}
break;
case WindowState.fullscreen:
if (atom_NET_WM_STATE != None && atom_NET_WM_STATE_FULLSCREEN != None) {
changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_FULLSCREEN);
result = true;
}
break;
default:
break;
}
if (activate) {
XMapRaised(x11display, _win);
result = true;
}
XFlush(x11display);
return result;
}
override @property dstring windowCaption() {
return _caption;
}
@ -399,6 +532,7 @@ class X11Window : DWindow {
immutable int iconw = 32;
immutable int iconh = 32;
ColorDrawBuf iconDraw = new ColorDrawBuf(iconw, iconh);
scope(exit) destroy(iconDraw);
iconDraw.fill(0xFF000000);
iconDraw.drawRescaled(Rect(0, 0, iconw, iconh), icon, Rect(0, 0, icon.width, icon.height));
iconDraw.invertAndPreMultiplyAlpha();
@ -411,31 +545,19 @@ class X11Window : DWindow {
}
XChangeProperty(x11display, _win, atom_NET_WM_ICON, XA_CARDINAL, 32, PropModeReplace, cast(ubyte*)propData.ptr, cast(int)propData.length);
}
/// request window redraw
override void invalidate() {
debug(x11) Log.d("Window.invalidate()");
XEvent ev;
memset(&ev, 0, ev.sizeof);
ev.type = Expose;
ev.xexpose.window = _win;
static if (true) {
//ev.xclient.display = x11display2;
//ev.xclient.message_type = atom_DLANGUI_TASK_EVENT;
//ev.xclient.format = 32;
//ev.xclient.data.l[0] = event.uniqueId;
XLockDisplay(x11display2);
XSendEvent(x11display2, _win, false, StructureNotifyMask, &ev);
XFlush(x11display2);
XUnlockDisplay(x11display2);
} else {
XSendEvent(x11display, _win, false, ExposureMask, &ev);
XFlush(x11display);
if (!_needRedraw) {
debug(x11) Log.d("Window.invalidate()");
_needRedraw = true;
}
}
/// close window
override void close() {
Log.d("X11Window.close()");
_platform.closeWindow(this);
}
ColorDrawBuf _drawbuf;
@ -475,7 +597,7 @@ class X11Window : DWindow {
0, 0, 0, 0,
_drawbuf.width,
_drawbuf.height);
XFlush(x11display);
//XFlush(x11display); // no need to XFlush since it will be called in event loop
}
}
@ -500,21 +622,21 @@ class X11Window : DWindow {
}
}
void processExpose() {
XWindowAttributes window_attributes_return;
XGetWindowAttributes(x11display, _win, &window_attributes_return);
void redraw() {
_needRedraw = false;
//Use values cached by ConfigureNotify to avoid XGetWindowAttributes call.
//XWindowAttributes window_attributes_return;
//XGetWindowAttributes(x11display, _win, &window_attributes_return);
//Log.d(format("XGetWindowAttributes reported size %d, %d", window_attributes_return.width, window_attributes_return.height));
int width = window_attributes_return.width;
int height = window_attributes_return.height;
immutable width = _cachedWidth;
immutable height = _cachedHeight;
if (width > 0 && height > 0)
onResize(width, height);
debug(x11) Log.d(format("processExpose(%d, %d)", width, height));
debug(x11) Log.d(format("redraw(%d, %d)", width, height));
if (_enableOpengl)
drawUsingOpengl();
else
drawUsingBitmap();
//Log.d("processExpose - drawing finished");
}
protected ButtonDetails _lbutton;
@ -926,12 +1048,11 @@ class X11Window : DWindow {
TimerThread timer;
private long _nextExpectedTimerTs;
XEvent ev;
/// schedule timer for interval in milliseconds - call window.onTimer when finished
override protected void scheduleSystemTimer(long intervalMillis) {
if (!timer) {
timer = new TimerThread(delegate() {
XEvent ev;
memset(&ev, 0, ev.sizeof);
//ev.xclient = XClientMessageEvent.init;
ev.xclient.type = ClientMessage;
@ -1042,7 +1163,19 @@ class X11Platform : Platform {
* Closes window earlier created with createWindow()
*/
override void closeWindow(DWindow w) {
_windowMap.remove((cast(X11Window)w)._win);
X11Window window = cast(X11Window)w;
XEvent ev;
memset(&ev, 0, ev.sizeof);
ev.xclient.type = ClientMessage;
ev.xclient.message_type = atom_DLANGUI_CLOSE_WINDOW_EVENT;
ev.xclient.window = window._win;
ev.xclient.display = x11display2;
ev.xclient.format = 32;
Log.d("Sending close window event");
XLockDisplay(x11display2);
XSendEvent(x11display2, window._win, false, StructureNotifyMask, &ev);
XFlush(x11display2);
XUnlockDisplay(x11display2);
}
bool handleTimers() {
@ -1056,6 +1189,10 @@ class X11Platform : Platform {
return handled;
}
final bool allWindowsClosed() {
return _windowMap.length == 0;
}
/**
* Starts application message loop.
*
@ -1068,8 +1205,6 @@ class X11Platform : Platform {
char[255] text; /* a char buffer for KeyPress Events */
Log.d("enterMessageLoop()");
/* look for events forever... */
bool finished = false;
XComposeStatus compose;
import core.sys.posix.sys.select;
@ -1077,23 +1212,27 @@ class X11Platform : Platform {
fd_set fdSet;
FD_ZERO(&fdSet);
FD_SET(x11displayFd, &fdSet);
while(!finished) {
/* get the next event and stuff it into our event variable.
Note: only events we set the mask for are detected!
*/
//bool timersHandled = handleTimers();
//if (timersHandled)
//XFlush(x11display);
//while (XEventsQueued(x11display, QueuedAfterFlush)) {//QueuedAfterFlush
scope(exit) FD_ZERO(&fdSet);
while(!allWindowsClosed()) {
// Note: only events we set the mask for are detected!
foreach(win; _windowMap) {
if (win._needRedraw) {
win.redraw();
}
}
XFlush(x11display);
int eventsInQueue = XEventsQueued(x11display, QueuedAlready);
if (!eventsInQueue) {
timeval zeroTime;
auto selectResult = select(x11displayFd + 1, &fdSet, null, null, &zeroTime);
import core.stdc.errno;
int selectResult;
do {
timeval zeroTime;
selectResult = select(x11displayFd + 1, &fdSet, null, null, &zeroTime);
} while(selectResult == -1 && errno == EINTR);
if (selectResult < 0) {
Log.e("select error");
Log.e("X11: display fd select error");
} else if (selectResult == 1) {
Log.d("Pending");
Log.d("X11: XPending");
eventsInQueue = XPending(x11display);
}
}
@ -1103,21 +1242,26 @@ class X11Platform : Platform {
}
foreach(eventIndex; 0..eventsInQueue)
{
//Thread.sleep(dur!("msecs")(10));
//continue;
if (allWindowsClosed())
break;
XNextEvent(x11display, &event);
switch (event.type) {
case ConfigureNotify:
X11Window w = findWindow(event.xconfigure.window);
if (w) {
w._cachedWidth = event.xconfigure.width;
w._cachedHeight = event.xconfigure.height;
} else {
Log.e("ConfigureNotify: Window not found");
}
break;
case Expose:
if (event.xexpose.count == 0) {
/* the window was exposed redraw it! */
//redraw();
X11Window w = findWindow(event.xexpose.window);
if (w) {
w.processExpose();
w.invalidate();
} else {
Log.e("Window not found");
Log.e("Expose: Window not found");
}
} else {
Log.d("Expose: non-0 count");
@ -1167,8 +1311,6 @@ class X11Platform : Platform {
//event.xkey.keycode,
event.xkey.state);
}
} else {
Log.e("Window not found");
}
@ -1235,56 +1377,37 @@ class X11Platform : Platform {
case CreateNotify:
Log.d("X11: CreateNotify event");
X11Window w = findWindow(event.xcreatewindow.window);
if (w) {
//w.processExpose();
} else {
if (!w) {
Log.e("Window not found");
}
break;
case DestroyNotify:
Log.d("X11: DestroyNotify event");
X11Window w = findWindow(event.xdestroywindow.window);
if (w) {
//w.processExpose();
} else {
Log.e("Window not found");
}
break;
case ResizeRequest:
Log.d("X11: ResizeRequest event");
X11Window w = findWindow(event.xresizerequest.window);
if (w) {
//w.processExpose();
} else {
if (!w) {
Log.e("Window not found");
}
break;
case FocusIn:
Log.d("X11: FocusIn event");
X11Window w = findWindow(event.xfocus.window);
if (w) {
//w.processExpose();
} else {
if (!w) {
Log.e("Window not found");
}
break;
case FocusOut:
Log.d("X11: FocusOut event");
X11Window w = findWindow(event.xfocus.window);
if (w) {
//w.processExpose();
} else {
if (!w) {
Log.e("Window not found");
}
break;
case KeymapNotify:
Log.d("X11: KeymapNotify event");
X11Window w = findWindow(event.xkeymap.window);
if (w) {
//w.processExpose();
} else {
Log.e("Window not found");
}
break;
case ClientMessage:
debug(x11) Log.d("X11: ClientMessage event");
@ -1298,9 +1421,12 @@ class X11Platform : Platform {
Log.d("Handling WM_PROTOCOLS");
if ((event.xclient.format == 32) && (event.xclient.data.l[0]) == atom_WM_DELETE_WINDOW) {
Log.d("Handling WM_DELETE_WINDOW");
closeWindow(w);
_windowMap.remove(w._win);
destroy(w);
}
} else if (event.xclient.message_type == atom_DLANGUI_CLOSE_WINDOW_EVENT) {
_windowMap.remove(w._win);
destroy(w);
}
} else {
Log.e("Window not found");
@ -1310,11 +1436,6 @@ class X11Platform : Platform {
break;
}
}
if (_windowMap.length == 0) {
finished = true;
}
//Thread.sleep(dur!("msecs")(10));
//XFlush(x11display);
}
return 0;
}