/****************************************************************************** On Linux, windows are organized into up to 3 HWND families: 1. resizable extent (or none if window is not resizable) 2. borders 3. client /******************************************************************************/ #include "stdafx.h" #if MAC #include "../Platforms/Mac/MyWindow.h" #include "../Platforms/Mac/MyOpenGLView.h" #endif namespace EE{ /******************************************************************************/ #if WINDOWS_OLD #include #include #define GET_POINTERID_WPARAM LOWORD #define WM_POINTERDOWN 0x0246 #define WM_POINTERUP 0x0247 #define WM_POINTERENTER 0x0249 #define WM_POINTERLEAVE 0x024A #define WM_POINTERUPDATE 0x0245 #define WM_POINTERCAPTURECHANGED 0x024C #define WM_DPICHANGED 0x02E0 typedef enum tagPOINTER_INPUT_TYPE { PT_POINTER=0x00000001, PT_TOUCH =0x00000002, PT_PEN =0x00000003, PT_MOUSE =0x00000004, }POINTER_INPUT_TYPE; static BOOL (WINAPI *GetPointerType)(UINT32 pointerId, POINTER_INPUT_TYPE *pointerType); static HPOWERNOTIFY PowerNotify; struct WindowCaptureEx { HDC dc; HBITMAP bitmap; VecI2 size; WindowCaptureEx() {dc=null; bitmap=null; size.zero();} ~WindowCaptureEx() {del();} void del() { if(bitmap){DeleteObject(bitmap); bitmap=null;} if(dc ){DeleteDC (dc ); dc =null;} size.zero(); } Bool capture(Image &image, Ptr hwnd=null) { Bool ok=false; #if DX11 if(hwnd==App.hwnd() && SwapChainDesc.SwapEffect!=DXGI_SWAP_EFFECT_DISCARD) // on DX10+ when swap chain flip mode is enabled, using 'GetDC' will result in a black image { Renderer.capture(image, -1, -1, -1, -1, 1, false); }else #endif if(HDC src_dc=GetDC((HWND)hwnd)) { //if(HBITMAP src_bitmap=(HBITMAP)GetCurrentObject(src_dc, OBJ_BITMAP)) { //BITMAP bmp; if(GetObject(src_bitmap, SIZE(bmp), &bmp)) { //Int width=bmp.bmWidth, height=bmp.bmHeight; don't use bitmap size because it returns full window size (including borders, not client only) Int width, height; if(hwnd) { VecI2 size=WindowSize(true, hwnd); width =size.x; height=size.y; }else { width =D.screenW(); height=D.screenH(); } if(width!=size.x || height!=size.y) { if(!dc)dc=CreateCompatibleDC(src_dc); if(bitmap){DeleteObject(bitmap); bitmap=null;} if(bitmap=CreateCompatibleBitmap(src_dc, width, height)){size.set(width, height); SelectObject(dc, bitmap);}else size.zero(); } if(bitmap && BitBlt(dc, 0, 0, size.x, size.y, src_dc, 0, 0, SRCCOPY)) { if(image.hwType()!=IMAGE_B8G8R8A8 || image.size()!=size || image.d()!=1 || image.pitch()!=image.w()*image.bytePP())image.createSoftTry(size.x, size.y, 1, IMAGE_B8G8R8A8); if(image.is() && image.lock(LOCK_WRITE)) { BITMAPINFO bi; Zero(bi); bi.bmiHeader.biSize =SIZE(bi.bmiHeader); bi.bmiHeader.biWidth = image.w(); bi.bmiHeader.biHeight=-image.h(); // positive height would flip the image bi.bmiHeader.biPlanes=1; bi.bmiHeader.biBitCount=32; bi.bmiHeader.biCompression=BI_RGB; ok=(GetDIBits(dc, bitmap, 0, image.h(), image.data(), &bi, DIB_RGB_COLORS)>0); image.unlock(); } } } } ReleaseDC((HWND)hwnd, src_dc); } return ok; } }; void WindowCapture::del() { Delete((WindowCaptureEx*&)data); } Bool WindowCapture::capture(Image &image, Ptr hwnd) { WindowCaptureEx* &wc=(WindowCaptureEx*&)data; if(!wc)New(wc); return wc->capture(image, hwnd); } #else void WindowCapture::del() { } Bool WindowCapture::capture(Image &image, Ptr hwnd) { return false; } #endif /******************************************************************************/ #if WINDOWS_OLD static ITaskbarList3 *TaskbarList; void WindowSetText(C Str &text, Ptr hwnd) { SetWindowText((HWND)hwnd, text); } Str WindowGetText(Ptr hwnd) { wchar_t temp[16*1024]; temp[0]='\0'; GetWindowText((HWND)hwnd, temp, Elms(temp)); return temp; } void WindowMinimize(Bool force, Ptr hwnd) { if(force)ShowWindow((HWND)hwnd, SW_MINIMIZE); else PostMessage((HWND)hwnd, WM_SYSCOMMAND, SC_MINIMIZE, NULL); } void WindowMaximize(Bool force, Ptr hwnd) { if(force)ShowWindow((HWND)hwnd, SW_MAXIMIZE); else PostMessage((HWND)hwnd, WM_SYSCOMMAND, SC_MAXIMIZE, NULL); } void WindowReset(Bool force, Ptr hwnd) { if(force)ShowWindow((HWND)hwnd, SW_RESTORE); else PostMessage((HWND)hwnd, WM_SYSCOMMAND, SC_RESTORE, NULL); } void WindowToggle(Bool force, Ptr hwnd) { if(WindowMaximized(hwnd))WindowReset (force, hwnd); else WindowMaximize(force, hwnd); } void WindowActivate(Ptr hwnd) { if(WindowMinimized(hwnd))WindowReset(false, hwnd); UIntPtr act_thread=GetThreadIdFromWindow(WindowActive()), cur_thread=GetThreadId ( ); if(cur_thread!=act_thread)AttachThreadInput(act_thread, cur_thread, true); BringWindowToTop ((HWND)hwnd); SetForegroundWindow((HWND)hwnd); if(cur_thread!=act_thread)AttachThreadInput(act_thread, cur_thread, false); } void WindowHide(Ptr hwnd) { ShowWindow((HWND)hwnd, SW_HIDE); } void WindowShow(Ptr hwnd) { ShowWindow((HWND)hwnd, SW_SHOWNA); } void WindowClose(Ptr hwnd) { PostMessage((HWND)hwnd, WM_SYSCOMMAND, SC_CLOSE, NULL); } void WindowFlash(Ptr hwnd) { FlashWindow((HWND)hwnd, true); } /******************************************************************************/ void WindowSetNormal ( Ptr hwnd) {if(TaskbarList) TaskbarList->SetProgressState((HWND)hwnd, TBPF_NOPROGRESS );} void WindowSetWorking ( Ptr hwnd) {if(TaskbarList) TaskbarList->SetProgressState((HWND)hwnd, TBPF_INDETERMINATE);} void WindowSetProgress(Flt progress, Ptr hwnd) {if(TaskbarList){TaskbarList->SetProgressState((HWND)hwnd, TBPF_NORMAL ); TaskbarList->SetProgressValue((HWND)hwnd, RoundU(Sat(progress)*65536), 65536);}} void WindowSetPaused (Flt progress, Ptr hwnd) {if(TaskbarList){TaskbarList->SetProgressState((HWND)hwnd, TBPF_PAUSED ); TaskbarList->SetProgressValue((HWND)hwnd, RoundU(Sat(progress)*65536), 65536);}} void WindowSetError (Flt progress, Ptr hwnd) {if(TaskbarList){TaskbarList->SetProgressState((HWND)hwnd, TBPF_ERROR ); TaskbarList->SetProgressValue((HWND)hwnd, RoundU(Sat(progress)*65536), 65536);}} /******************************************************************************/ Byte WindowGetAlpha(Ptr hwnd) { BYTE alpha; if(hwnd && GetLayeredWindowAttributes((HWND)hwnd, null, &alpha, null))return alpha; return 255; } void WindowAlpha(Byte alpha, Ptr hwnd) { if(hwnd) { if(alpha==255) { SetWindowLong((HWND)hwnd, GWL_EXSTYLE, GetWindowLong((HWND)hwnd, GWL_EXSTYLE) & ~WS_EX_LAYERED); }else { SetWindowLong((HWND)hwnd, GWL_EXSTYLE, GetWindowLong((HWND)hwnd, GWL_EXSTYLE) | WS_EX_LAYERED); SetLayeredWindowAttributes((HWND)hwnd, 0, alpha, LWA_ALPHA); } } } /******************************************************************************/ void WindowMove(Int dx, Int dy, Ptr hwnd) { if(dx || dy) { RECT rect; GetWindowRect((HWND)hwnd, &rect); MoveWindow((HWND)hwnd, rect.left+dx, rect.top+dy, rect.right-rect.left, rect.bottom-rect.top, true); } } void WindowPos(Int x, Int y, Ptr hwnd) { RECT rect; GetWindowRect((HWND)hwnd, &rect); MoveWindow((HWND)hwnd, x, y, rect.right-rect.left, rect.bottom-rect.top, true); } void WindowSize(Int w, Int h, Bool client, Ptr hwnd) { RECT rect; GetWindowRect((HWND)hwnd, &rect); if(client) { RECT client; GetClientRect((HWND)hwnd, &client); w+=(rect.right -rect.left)-(client.right -client.left); h+=(rect.bottom-rect.top )-(client.bottom-client.top ); } MoveWindow((HWND)hwnd, rect.left, rect.top, w, h, true); } VecI2 WindowSize(Bool client, Ptr hwnd) { RECT rect; if(!client) { if(!GetWindowRect((HWND)hwnd, &rect))goto error; }else { if(!GetClientRect((HWND)hwnd, &rect))goto error; } return VecI2(rect.right-rect.left, rect.bottom-rect.top); error: return 0; } RectI WindowRect(Bool client, Ptr hwnd) // !! 'WindowRect' can return weird position when the window is minimized !! { RECT rect; if(!client) { if(!GetWindowRect((HWND)hwnd, &rect))goto error; }else { POINT pos={0, 0}; if(!GetClientRect ((HWND)hwnd, &rect))goto error; ClientToScreen((HWND)hwnd, &pos ); rect.left +=pos.x; rect.top +=pos.y; rect.right+=pos.x; rect.bottom+=pos.y; } #if DEBUG && 0 LogN(S+"WindowRect("+client+")="+RectI(rect.left, rect.top, rect.right, rect.bottom).asText()); #endif return RectI(rect.left, rect.top, rect.right, rect.bottom); error: return RectI(0, 0, 0, 0); } Bool WindowMaximized(Ptr hwnd) { return IsZoomed((HWND)hwnd)!=0; } Bool WindowMinimized(Ptr hwnd) { return IsIconic((HWND)hwnd)!=0; } void WindowSendData(CPtr data, Int size, Ptr hwnd) { if(hwnd && size>=0) { COPYDATASTRUCT ds; ds.dwData=0; // custom ID, not used ds.lpData=Ptr(data); ds.cbData=size; SendMessage((HWND)hwnd, WM_COPYDATA, (WPARAM)App.hwnd(), (LPARAM)&ds); } } /******************************************************************************/ Ptr WindowActive() { return (Ptr)GetForegroundWindow(); } Ptr WindowMouse() { POINT cur; if(GetCursorPos(&cur))return (Ptr)WindowFromPoint(cur); return null; } Ptr WindowParent(Ptr hwnd) { return (Ptr)GetParent((HWND)hwnd); } /******************************************************************************/ static BOOL CALLBACK EnumWindowList(HWND hwnd, LPARAM hwnds_ptr) { MemPtr &hwnds=*(MemPtr*)hwnds_ptr; hwnds.add(hwnd); return true; } void WindowList(MemPtr hwnds) { hwnds.clear(); EnumWindows(EnumWindowList, LPARAM(&hwnds)); } /******************************************************************************/ #elif MAC /******************************************************************************/ void WindowSetText(C Str &text, Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd) if(NSStringAuto string=text) [window setTitle:string]; } Str WindowGetText(Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd)return [window title]; // do not release [window title] as it will crash return S; } void WindowActivate(Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd) if(window==App.hwnd()) { ProcessSerialNumber psn; if(GetProcessForPID(App.processID(), &psn)==noErr)SetFrontProcess(&psn); } } Ptr WindowMouse() { return Ms._on_client ? App.hwnd() : null; } Bool WindowMaximized(Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd)return [window isZoomed]; return false; } Bool WindowMinimized(Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd)return [window isMiniaturized]; return false; } void WindowClose(Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd)[window performClose:NSApp]; } void WindowMinimize(Bool force, Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd)[window performMiniaturize:NSApp]; } void WindowMaximize(Bool force, Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd) { if( [window isMiniaturized])WindowActivate(hwnd); if(![window isZoomed])[window performZoom:NSApp]; } } void WindowReset(Bool force, Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd) { if([window isMiniaturized])WindowActivate(hwnd);else if([window isZoomed])[window performZoom:NSApp]; } } void WindowToggle(Bool force, Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd) { if([window isMiniaturized])WindowActivate(hwnd);else [window performZoom:NSApp]; } } void WindowHide(Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd) if(window==App.hwnd())[NSApp hide:NSApp]; } void WindowShow(Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd) if(window==App.hwnd())[NSApp unhideWithoutActivation]; } static VecI2 WindowSize(Bool client, NSWindow *window) { NSRect rect=[window frame]; if(client)rect=[window contentRectForFrameRect:rect]; return VecI2(Round(rect.size.width), Round(rect.size.height)); } static void GetWindowRect(Bool client, NSWindow *window, RectI &r) { NSRect rect=[window frame]; if(client)rect=[window contentRectForFrameRect:rect]; r.min.x= Round(rect.origin.x); r.max.x=r.min.x+Round(rect.size.width ); r.max.y=D.screenH()-Round(rect.origin.y); r.min.y=r.max.y-Round(rect.size.height); } VecI2 WindowSize(Bool client, Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd) { if(hwnd==App.hwnd() && D.full())return D.res(); return WindowSize(client, window); } return 0; } RectI WindowRect(Bool client, Ptr hwnd) { RectI r; if(NSWindow *window=(NSWindow*)hwnd) { if(hwnd==App.hwnd() && D.full())r.set(0, 0, D.resW(), D.resH()); else GetWindowRect(client, window, r); }else r.zero(); return r; } void WindowMove(Int dx, Int dy, Ptr hwnd) { if((dx || dy) && hwnd) { RectI r=WindowRect(false, hwnd); WindowPos(r.min.x+dx, r.min.y+dy, hwnd); } } void WindowPos(Int x, Int y, Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd) { NSPoint p; p.x=x; p.y=D.screenH()-y; [window setFrameTopLeftPoint:p]; } } void WindowSize(Int w, Int h, Bool client, Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd) { // don't use [window setContentSize:NSSize] as it will resize while preserving the bottom left window position NSRect rect=[window frame]; Flt rect_h=rect.size.height; rect.size.width =w; rect.size.height=h; if(client) { #if 1 rect.size.width +=App._bound.w(); rect.size.height+=App._bound.h(); #else rect.size=[window frameRectForContentRect:rect].size; #endif } rect.origin.y+=rect_h-rect.size.height; // 'origin' is bottom-left window position [window setFrame:rect display:true]; // 'setFrame' includes borders } } Ptr WindowParent(Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd)return [window parentWindow]; return null; } void WindowFlash(Ptr hwnd) { if(App.hwnd()==hwnd) // on Mac OS only our window can be flashed if(NSApplication *app=NSApp) // get current application id [app requestUserAttention:NSInformationalRequest]; } Byte WindowGetAlpha(Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd)return FltToByte(window.alphaValue); return 255; } void WindowAlpha(Byte alpha, Ptr hwnd) { if(NSWindow *window=(NSWindow*)hwnd)[window setAlphaValue:alpha/255.0f]; } void WindowSendData(CPtr data, Int size, Ptr hwnd) {} Ptr WindowActive() {return App.active() ? App.hwnd() : null;} /******************************************************************************/ #elif LINUX /******************************************************************************/ #define _NET_WM_STATE_REMOVE 0 #define _NET_WM_STATE_ADD 1 #define _NET_WM_STATE_TOGGLE 2 static Atom xdnd_req, WM_PROTOCOLS, WM_DELETE_WINDOW, XdndDrop, XdndActionCopy, XdndPosition, XdndEnter, XdndStatus, XdndTypeList, XdndFinished, XdndSelection, PRIMARY, WM_STATE, _NET_WM_STATE, _NET_WM_STATE_HIDDEN, _NET_WM_STATE_FOCUSED, _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_STATE_MAXIMIZED_HORZ, _NET_WM_STATE_FULLSCREEN, _NET_WM_STATE_DEMANDS_ATTENTION, _NET_WM_NAME, _NET_FRAME_EXTENTS, UTF8_STRING, _MOTIF_WM_HINTS; static XWindow xdnd_source; static long xdnd_version; static VecI2 xdnd_pos; static int XInput2Extension; static Colormap HwndColormap; static Byte ButtonPressCount[8]; static XIM IM; static XIC IC; static Str8 ClassName; struct MotifWmHints2 { unsigned long flags; unsigned long functions; unsigned long decorations; long input_mode; unsigned long status; }; /******************************************************************************/ void WindowSetText(C Str &text, Ptr hwnd) { if(XDisplay && hwnd) { Str8 utf=UTF8(text); XStoreName (XDisplay, XWindow(hwnd), Str8(text)); if(_NET_WM_NAME && UTF8_STRING)XChangeProperty(XDisplay, XWindow(hwnd), _NET_WM_NAME, UTF8_STRING, 8, PropModeReplace, (unsigned char*)utf(), utf.length()); } } Str WindowGetText(Ptr hwnd) { Str s; if(XDisplay && hwnd) { if(_NET_WM_NAME && UTF8_STRING) { Atom type=NULL; int format=0; unsigned long items=0, bytes_after=0; unsigned char *data=null; if(!XGetWindowProperty(XDisplay, XWindow(hwnd), _NET_WM_NAME, 0, 4096, false, UTF8_STRING, &type, &format, &items, &bytes_after, &data))s=FromUTF8((char*)data); if(data)XFree(data); } if(!s.is()) { char *name=null; XFetchName(XDisplay, XWindow(hwnd), &name); if(name){s=name; XFree(name);} } } return s; } void WindowActivate(Ptr hwnd) { if(XDisplay && hwnd) { XRaiseWindow (XDisplay, XWindow(hwnd)); XSetInputFocus(XDisplay, XWindow(hwnd), RevertToParent, CurrentTime); } } Ptr WindowMouse() { if(XDisplay) { XWindow root, child; int rx, ry, x, y; unsigned int mask; XQueryPointer(XDisplay, DefaultRootWindow(XDisplay), &root, &child, &rx, &ry, &x, &y, &mask); return Ptr(XmuClientWindow(XDisplay, XWindow(WindowParentTop(Ptr(child))))); } return null; } Bool WindowMaximized(Ptr hwnd) { if(XDisplay && hwnd && _NET_WM_STATE) { UInt flags=0; Atom type=NULL; int format=0; unsigned long items=0, bytes_after=0; unsigned char *data=null; if(!XGetWindowProperty(XDisplay, XWindow(hwnd), _NET_WM_STATE, 0, 1024, false, XA_ATOM, &type, &format, &items, &bytes_after, &data)) if(Atom *atoms=(Atom*)data) for(unsigned long i=0; istate==IconicState); if(data)XFree(data); return min; } return false; } void WindowClose(Ptr hwnd) { if(XDisplay && hwnd)XDestroyWindow(XDisplay, XWindow(hwnd)); } void WindowMinimize(Bool force, Ptr hwnd) { if(XDisplay && hwnd)XIconifyWindow(XDisplay, XWindow(hwnd), DefaultScreen(XDisplay)); } void WindowMaximize(Bool force, Ptr hwnd) { if(XDisplay && hwnd && _NET_WM_STATE && _NET_WM_STATE_MAXIMIZED_HORZ && _NET_WM_STATE_MAXIMIZED_VERT) { #if 1 XEvent e; Zero(e); e.xclient.type =ClientMessage; e.xclient.window =XWindow(hwnd); e.xclient.message_type=_NET_WM_STATE; e.xclient.format =32; e.xclient.data.l[0]=_NET_WM_STATE_ADD; e.xclient.data.l[1]=_NET_WM_STATE_MAXIMIZED_HORZ; e.xclient.data.l[2]=_NET_WM_STATE_MAXIMIZED_VERT; e.xclient.data.l[3]=1; e.xclient.data.l[4]=0; XSendEvent(XDisplay, DefaultRootWindow(XDisplay), false, SubstructureRedirectMask|SubstructureNotifyMask, &e); #else // this doesn't work entirely correctly Atom type=null; int format=0; unsigned long items=0, bytes_after=0; unsigned char *data=null; if(!XGetWindowProperty(XDisplay, XWindow(hwnd), _NET_WM_STATE, 0, 1024, false, XA_ATOM, &type, &format, &items, &bytes_after, &data)) { Atom *atoms=(Atom*)data, temp[1024]; if(itemsTitle=ref new Platform::String(text); #endif } } Str WindowGetText(Ptr hwnd) { if(hwnd==App.hwnd())return App.name(); return S; } void WindowActivate(Ptr hwnd) { } Ptr WindowMouse() { return (App.active() && !App.minimized()) ? App.hwnd() : null; } Bool WindowMaximized(Ptr hwnd) { return hwnd==App.hwnd() && App.maximized(); } Bool WindowMinimized(Ptr hwnd) { return hwnd==App.hwnd() && App.minimized(); } void WindowClose(Ptr hwnd) { } void WindowMinimize(Bool force, Ptr hwnd) { #if WINDOWS_NEW //if(hwnd==App.hwnd())Windows::ApplicationModel::Core::CoreApplication::Exit(); this causes a crash, TODO: find alternative #elif ANDROID if(hwnd==App.hwnd() && AndroidApp && AndroidApp->activity)ANativeActivity_finish(AndroidApp->activity); #endif } void WindowMaximize(Bool force, Ptr hwnd) { } void WindowReset(Bool force, Ptr hwnd) { } void WindowToggle(Bool force, Ptr hwnd) { } void WindowHide(Ptr hwnd) { WindowMinimize(false, hwnd); } void WindowShow(Ptr hwnd) { WindowReset(false, hwnd); } VecI2 WindowSize(Bool client, Ptr hwnd) { if(hwnd==App.hwnd()) { #if WINDOWS_NEW if(App.hwnd()) { Windows::Foundation::Rect rect=App.Hwnd()->Bounds; // this returns the client rect return VecI2(DipsToPixels(rect.Width), DipsToPixels(rect.Height)); } #endif return D.res(); } return 0; } RectI WindowRect(Bool client, Ptr hwnd) { if(hwnd==App.hwnd()) { #if WINDOWS_NEW if(App.hwnd()) { Windows::Foundation::Rect rect=App.Hwnd()->Bounds; // this returns the client rect return RectI(DipsToPixels(rect.X), DipsToPixels(rect.Y), DipsToPixels(rect.X+rect.Width), DipsToPixels(rect.Y+rect.Height)); } #endif return RectI(0, 0, D.resW(), D.resH()); } return RectI(0, 0, 0, 0); } void WindowMove(Int dx, Int dy, Ptr hwnd) { } void WindowPos(Int x, Int y, Ptr hwnd) { } void WindowSize(Int w, Int h, Bool client, Ptr hwnd) { } Ptr WindowParent(Ptr hwnd) { return null; } void WindowFlash(Ptr hwnd) { } Byte WindowGetAlpha(Ptr hwnd) {return 255;} void WindowAlpha(Byte alpha, Ptr hwnd) {} void WindowSendData(CPtr data, Int size, Ptr hwnd) {} Ptr WindowActive() {return App.active() ? App.hwnd() : null;} /******************************************************************************/ #endif #if WINDOWS_NEW // TODO: WINDOWS_NEW TaskBar Progress - check this in the future as right now this is not available in UWP void WindowSetNormal ( Ptr hwnd) {} void WindowSetWorking ( Ptr hwnd) {} void WindowSetProgress(Flt progress, Ptr hwnd) {} void WindowSetPaused (Flt progress, Ptr hwnd) {} void WindowSetError (Flt progress, Ptr hwnd) {} #elif !WINDOWS void WindowSetNormal ( Ptr hwnd) {} void WindowSetWorking ( Ptr hwnd) {} void WindowSetProgress(Flt progress, Ptr hwnd) {} void WindowSetPaused (Flt progress, Ptr hwnd) {} void WindowSetError (Flt progress, Ptr hwnd) {} #endif UInt WindowProc(Ptr hwnd) { #if WINDOWS_OLD DWORD proc=0; GetWindowThreadProcessId((HWND)hwnd, &proc); return proc; #else if(hwnd==App.hwnd())return App.processID(); return 0; #endif } #if !WINDOWS_OLD void WindowList(MemPtr hwnds) { if(App.hwnd())hwnds.setNum(1)[0]=App.hwnd();else hwnds.clear(); } #endif /******************************************************************************/ Ptr WindowParentTop(Ptr hwnd) { for(; Ptr parent=WindowParent(hwnd); )hwnd=parent; return hwnd; } /******************************************************************************/ void WindowMsgBox(C Str &title, C Str &text, Bool error) { #if WINDOWS_OLD MessageBox(null, text, title, MB_OK|MB_TOPMOST|(error ? MB_ICONERROR : 0)); // this does not require 'FixNewLine' #elif WINDOWS_NEW if(auto dialog=ref new Windows::UI::Popups::MessageDialog(ref new Platform::String(text), ref new Platform::String(title))) // this does not require 'FixNewLine' { dialog->Commands->Append(ref new Windows::UI::Popups::UICommand("OK")); dialog->ShowAsync(); } #elif LINUX // TODO: what if zenity is not installed? Str safe_title= Str(title).replace('`', '\'').replace('"', '\''); Str safe_text =XmlString(Str(text ).replace('`', '\'')); Run("zenity", S+(error ? "--error" : "--info")+" --title=\""+safe_title+"\" --text=\""+safe_text+"\""); #elif MAC CFStringRef cf_title= CFStringCreateWithCString(kCFAllocatorDefault, title.is() ? UTF8(title)() : "", kCFStringEncodingUTF8); // 'CFUserNotificationDisplayAlert' will freeze if this param is null CFStringRef cf_text =(text .is() ? CFStringCreateWithCString(kCFAllocatorDefault, UTF8(text ) , kCFStringEncodingUTF8) : null); CFUserNotificationDisplayAlert(0, error ? kCFUserNotificationStopAlertLevel : kCFUserNotificationNoteAlertLevel, null, null, null, cf_title, cf_text, CFSTR("OK"), null, null, null); if(cf_title)CFRelease(cf_title); if(cf_text )CFRelease(cf_text ); #elif IOS if(NSString *ns_title=AppleString(title)) // have to use 'AppleString' because it will get copied in the local function below { if(NSString *ns_text=AppleString(text)) // have to use 'AppleString' because it will get copied in the local function below { dispatch_async(dispatch_get_main_queue(), ^{ // this is needed in case we're calling from a secondary thread if(UIAlertController *alert_controller=[UIAlertController alertControllerWithTitle:ns_title message:ns_text preferredStyle:UIAlertControllerStyleAlert]) { [alert_controller addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]]; [[[[UIApplication sharedApplication] keyWindow] rootViewController] presentViewController:alert_controller animated:YES completion:nil]; //[alert_controller release]; release will crash } }); [ns_text release]; } [ns_title release]; } #elif ANDROID // we need to call the code on UI thread, so we need to call java that will do this JNI jni; if(jni && ActivityClass) if(JMethodID messageBox=jni->GetStaticMethodID(ActivityClass, "messageBox", "(Ljava/lang/String;Ljava/lang/String;Z)V")) if(JString ti=JString(jni, title)) if(JString te=JString(jni, text )) jni->CallStaticVoidMethod(ActivityClass, messageBox, ti(), te(), jboolean(false)); #elif WEB JavaScriptRun(S+"alert(\""+CString(text)+"\")"); #endif } /******************************************************************************/ #if WINDOWS_OLD static void UpdateCandidates() // this function does not remove exising candidates unless it founds new ones (this is because candidates for "q6" keyboard input on QuanPin vista/7 would get cleared) { Memc &candidate=(Kb._imm_candidate_hidden ? Kb._imm_candidate_temp : Kb._imm_candidate); candidate.clear(); Int size=ImmGetCandidateList(Kb._imc, 0, null, 0); if( size>0) { Memt temp; CANDIDATELIST *list=(CANDIDATELIST*)temp.setNumZero(size).data(); ImmGetCandidateList(Kb._imc, 0, list, size); if(list->dwStyle==IME_CAND_CODE) { if(list->dwCount==1)candidate.New()=(Char)list->dwOffset[0];else if(list->dwCount> 1){} // text representations of individual DBCS character values in hexadecimal notation ? }else if(list->dwPageSize) { Int start=Max((Int)list->dwPageStart, (Int)(list->dwSelection/list->dwPageSize*list->dwPageSize) ), // must be aligned to page size end =Min((Int)list->dwCount , start+(Int) list->dwPageSize , start+10); // limit to 10 elements for(Int i=start; idwOffset[i])); if(Is(c))candidate.add(c);else break; } } } } static Bool Updating; static SByte Activate=-1; static Bool NonClientClick=false, ErasedBackground=false; enum PAUSE_MODE : Byte { NOT_PAUSED, PAUSED_WAS_INACTIVE, PAUSED_WAS_ACTIVE, PAUSED_TIMER, }; static PAUSE_MODE PauseMode=NOT_PAUSED; static UIntPtr PauseTimer; static void Pause(Bool pause) { if(pause!=(PauseMode!=NOT_PAUSED))switch(PauseMode) { case NOT_PAUSED: // is not paused, pause now { if(App.flag&APP_NO_PAUSE_ON_WINDOW_MOVE_SIZE) { Time.skipUpdate(); PauseTimer=SetTimer(App.Hwnd(), 1, USER_TIMER_MINIMUM, null); PauseMode=PAUSED_TIMER; }else { PauseMode=(App.active() ? PAUSED_WAS_ACTIVE : PAUSED_WAS_INACTIVE); App.setActive(false); } }break; case PAUSED_TIMER: KillTimer(App.Hwnd(), PauseTimer); PauseTimer=0; PauseMode=NOT_PAUSED; break; case PAUSED_WAS_ACTIVE : App.setActive(true); // !! no break on purpose !! case PAUSED_WAS_INACTIVE: PauseMode=NOT_PAUSED; break; } } static Byte ResetCursorCounter; static void ResetCursor() {Ms.resetCursor(); if(ResetCursorCounter){ResetCursorCounter--; App._callbacks.include(ResetCursor);}} // calling immediately may not have any effect, we have to try a few times static void ConditionalDraw() { if(!(App.active() || (App.flag&APP_WORK_IN_BACKGROUND)))DrawState(); // draw only if will not draw by itself } static LRESULT CALLBACK WindowMsg(HWND hwnd, UInt msg, WPARAM wParam, LPARAM lParam) { #if 0 switch(msg) { case WM_KEYDOWN: case WM_KEYUP: case WM_MOUSEMOVE: case WM_MBUTTONUP: case WM_MOUSEWHEEL: case WM_NCPAINT: case WM_NCHITTEST: //case WM_NCCALCSIZE: case WM_ERASEBKGND: case WM_SETCURSOR: case WM_GETMINMAXINFO: case WM_PAINT: case WM_GETICON: case WM_GETTEXT: case WM_GETOBJECT: break; default: { Str s=S+"frame:"+Time.frame()+", WM_"; switch(msg) { case WM_POWERBROADCAST: s+=S+"POWERBROADCAST:"+wParam; break; case WM_DISPLAYCHANGE: s+=S+"DISPLAYCHANGE:"+LOWORD(lParam)+'x'+HIWORD(lParam); break; case WM_ACTIVATE: s+=S+"ACTIVATE:"+(wParam==WA_ACTIVE || wParam==WA_CLICKACTIVE); break; case WM_NCACTIVATE: s+=S+"NCACTIVATE"; break; case WM_ACTIVATEAPP: s+=S+"ACTIVATEAPP"; break; case WM_MOVE: s+=S+"MOVE:"+(short)LOWORD(lParam)+','+(short)HIWORD(lParam); break; case WM_SIZE: s+=S+"SIZE:"+LOWORD(lParam)+','+HIWORD(lParam); break; case WM_NCHITTEST: s+=S+"NCHITTEST"; break; case WM_NCCALCSIZE: s+=S+"NCCALCSIZE"; break; case WM_SETFOCUS: s+=S+"SETFOCUS"; break; case WM_KILLFOCUS: s+=S+"KILLFOCUS"; break; case WM_DWMNCRENDERINGCHANGED: s+=S+"DWMNCRENDERINGCHANGED"; break; case WM_SETTINGCHANGE: { s+=S+"SETTINGCHANGE: SPI_"; switch(wParam) { default: s+=TextHex(wParam); break; case SPI_SETWORKAREA: {RECT rect; SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0); s+=S+"SETWORKAREA:"+rect.left+','+rect.top+','+rect.right+','+rect.bottom;} break; } }break; case WM_WINDOWPOSCHANGING: {WINDOWPOS &wp=*(WINDOWPOS*)lParam; s+=S+"WINDOWPOSCHANGING:"+wp.x+','+wp.y+' '+wp.cx+','+wp.cy;} break; case WM_WINDOWPOSCHANGED : {WINDOWPOS &wp=*(WINDOWPOS*)lParam; s+=S+"WINDOWPOSCHANGED:" +wp.x+','+wp.y+' '+wp.cx+','+wp.cy;} break; case WM_STYLECHANGING: s+=S+"STYLECHANGING"; break; case WM_STYLECHANGED: s+=S+"STYLECHANGED"; break; default: s+=TextHex(msg)+", wParam:"+TextHex(wParam)+", lParam:"+TextHex((ULong)lParam); break; } LogN(s); }break; } #endif switch(msg) { // TIMER case WM_TIMER: if(wParam==PauseTimer && !App._close && !Updating)App.update(); break; // allows app updating while window moving/resizing (don't do this if app requested close or it's inside update already, this can happen if in the app we've displayed a system message box making the app paused, and then moving the window, in that case WM_TIMER gets called on the same thread, however inside the messagebox call) // APPLICATION case WM_CLOSE: if(!(App.flag&APP_NO_CLOSE))App.close(); return 0; /*case WM_ACTIVATEAPP: // !! Warning: when clicking on the apps bar in the taskbar to minimize it, WM_ACTIVATEAPP gets called with "wParam==1", but when clicking on the apps bar in the taskbar to activate it, WM_ACTIVATEAPP does not get called !! that's why WM_ACTIVATEAPP is ignored and only WM_ACTIVATE gets checked { Bool active=(wParam!=0); // 'wParam' can be 0 or 1 App.setActive(active); //LogN(S+"WM_ACTIVATEAPP, active:"+active+", wParam:"+wParam); }break;*/ case WM_ACTIVATE: // !! Warning: when clicking on the apps bar in the taskbar to minimize it, WM_ACTIVATE gets called with "LOWORD(wParam)!=WA_INACTIVE" !! { //Bool active=(LOWORD(wParam)!=WA_INACTIVE), minimized=(HIWORD(wParam)!=0); active&=!minimized; // LOWORD(wParam) can be: WA_INACTIVE, WA_ACTIVE, WA_CLICKACTIVE. HIWORD(wParam)!=0 indicates being minimized Activate=(wParam==WA_ACTIVE || wParam==WA_CLICKACTIVE); // it's active only for WA_ACTIVE WA_CLICKACTIVE and when HIWORD(wParam)==0, which is "active && !minimized" // instead of activating the app here, we need to wait to check if WM_NCLBUTTONDOWN gets called to determine whether app was activated with a click on the title bar }break; case WM_INITMENUPOPUP : case WM_ENTERSIZEMOVE: Pause(true ); break; case WM_UNINITMENUPOPUP: case WM_EXITSIZEMOVE : Pause(false); break; // WM_EXITSIZEMOVE called when (finished dragging by title bar or resizing by edge/corner, snapped by User), NOT called when (maximized, snapped by OS) case WM_MOVE: // called when moved (dragged by title bar, snapped by User/OS, maximized, minimized) if(!WindowMinimized(hwnd) && !WindowMaximized(hwnd) && !D.full()) // use 'hwnd' instead of 'App.hwnd' because WM_MOVE is being called while window is being created "_hwnd=CreateWindowEx(..)" and pointer wasn't set yet, need to check for 'WindowMinimized' and 'WindowMaximized' instead of 'App.minimized' and 'App.maximized' because these are not yet available { //VecI2 client((short)LOWORD(lParam), (short)HIWORD(lParam)); App._window_pos=WindowRect(false, hwnd).min; // remember window position for later restoring }break; case WM_SIZE: // called when resized (resized by edge/corner, snapped by User/OS, maximized, minimized) { App._minimized=(wParam==SIZE_MINIMIZED); App._maximized=(wParam==SIZE_MAXIMIZED); if(!App.minimized() && !D.full() && D.created()) { App._window_resized.set(LOWORD(lParam), HIWORD(lParam)); ConditionalDraw(); // draw too because WM_PAINT won't be called when window is getting smaller } }break; case WM_WINDOWPOSCHANGED: // called when moved/resized (dragged by title bar, resized by edge/corner, snapped by User/OS, maximized, minimized) Ms.clipUpdate(); break; case WM_DISPLAYCHANGE: if(D.initialized()) // needed only if device already initialized (to skip setting mouse cursor and screen size when initializing) { ResetCursorCounter=8; App._callbacks.include(ResetCursor); // it was noticed that after changing resolution, Windows will rescale current cursor, to prevent that, we need to reset it, calling immediately may not have any effect, we have to try a few times if(auto screen_changed=D.screen_changed)screen_changed(D.w(), D.h()); // if 'D.scale' is set based on current screen resolution, then we may need to adjust it }break; case WM_SYSCOMMAND: switch(wParam) { case SC_CLOSE : if(!(App.flag&APP_NO_CLOSE))App.close(); return 0; case SC_MONITORPOWER: return 0; case SC_SIZE : return 0; case SC_MINIMIZE : if(!(App.flag&APP_MINIMIZABLE))return 0; break; case SC_MAXIMIZE : if(!(App.flag&APP_MAXIMIZABLE))return 0; break; case SC_MOVE : case SC_KEYMENU : if(D.full())return 0; break; }break; // POWER case WM_POWERBROADCAST: switch(wParam) { case PBT_APMQUERYSUSPEND: return App._stay_awake ? BROADCAST_QUERY_DENY : true; // system asks if it's OK to sleep case PBT_APMSUSPEND : // suspending case PBT_APMRESUMEAUTOMATIC: // resuming { if(auto sleep=App.sleep)sleep(wParam==PBT_APMSUSPEND); // copy first to avoid multi-thread issues }break; }break; //case WM_DPICHANGED: break; // MOUSE, because there can be a case when the Window is activated by System through WM_ACTIVATE, but we don't activate the App due to Ms.exclusive or Ms.clip, then we always need to activate when the user clicks on the client area case WM_LBUTTONDOWN: App.setActive(true); Ms.push (0); return 0; case WM_RBUTTONDOWN: Ms.push (1); return 0; case WM_MBUTTONDOWN: Ms.push (2); return 0; case WM_LBUTTONUP : Ms.release(0); return 0; case WM_RBUTTONUP : Ms.release(1); return 0; case WM_MBUTTONUP : Ms.release(2); return 0; case WM_XBUTTONDOWN: Ms.push ((GET_XBUTTON_WPARAM(wParam)&XBUTTON1) ? 3 : 4); return 0; case WM_XBUTTONUP : Ms.release((GET_XBUTTON_WPARAM(wParam)&XBUTTON1) ? 3 : 4); return 0; case WM_NCLBUTTONDOWN: // when clicking on the title bar, this will get called before WM_ACTIVATE, but when clicking on minimize/maximize/close, then it will get called after { //LogN(S+"WM_NCLBUTTONDOWN, frame:"+Time.frame()); NonClientClick=true; Time.skipUpdate(); // pause can occur when holding mouse button over title bar or window buttons for less time than move/size gets activated, thus causing slow down, so call 'skipUpdate' }break; //case WM_NCLBUTTONUP: break; this is never called case WM_SETCURSOR: // this will get called only if the mouse is on the app window, there will be different results if mouse is on client or for example on the title bar { if(Ms._on_client=(LOWORD(lParam)==HTCLIENT)){Ms.resetVisibility(); return true;} // call this here to make sure that we have correct cursor set, in Mouse.update there's also '_on_client' modification, because this isn't called when mouse goes outside the window, return true only when on client, so for example at the window edge, system resize cursor can be assigned by default functions }break; #define WM_MOUSEHWHEEL 0x020E case WM_MOUSEHWHEEL: Ms._wheel.x+=Flt(GET_WHEEL_DELTA_WPARAM(wParam))/WHEEL_DELTA; break; case WM_MOUSEWHEEL : Ms._wheel.y+=Flt(GET_WHEEL_DELTA_WPARAM(wParam))/WHEEL_DELTA; break; // KEYBOARD // Order of events 0-WM_INPUT, 1-WM_KEYDOWN, 2-WM_CHAR, 3-WM_KEYUP case WM_INPUT: { UINT size=0; GetRawInputData((HRAWINPUT)lParam, RID_INPUT, null, &size, sizeof(RAWINPUTHEADER)); Memt temp; temp.setNum(size); if(GetRawInputData((HRAWINPUT)lParam, RID_INPUT, temp.data(), &size, sizeof(RAWINPUTHEADER))==size) { RAWINPUT &raw=*(RAWINPUT*)temp.data(); switch(raw.header.dwType) { case RIM_TYPEKEYBOARD: { KB_KEY key; switch(raw.data.keyboard.VKey) { case VK_CONTROL: if(raw.data.keyboard.Flags&RI_KEY_E0)goto def; key=KB_LCTRL; break; // skip RI_KEY_E0 right control (it's already handled in WM_KEYDOWN) case VK_SHIFT : key=((raw.data.keyboard.MakeCode==42) ? KB_LSHIFT : KB_RSHIFT); break; // 42=KB_LSHIFT, 54=KB_RSHIFT case 255 : if(raw.data.keyboard.MakeCode==42 && (raw.data.keyboard.Flags&(RI_KEY_E0|RI_KEY_E1))==RI_KEY_E0){key=KB_PRINT; break;} goto def; // detect KB_PRINT because in 'Kb.exclusive', WM_HOTKEY isn't called default: goto def; } if(raw.data.keyboard.Flags&RI_KEY_BREAK)Kb.release(key);else Kb.push(key, raw.data.keyboard.MakeCode); return 0; }break; case RIM_TYPEMOUSE: { if(raw.data.mouse.usFlags&MOUSE_MOVE_ABSOLUTE) { }else { Ms._delta_relative.x+=raw.data.mouse.lLastX; Ms._delta_relative.y-=raw.data.mouse.lLastY; } }break; } } }break; case WM_KEYDOWN : case WM_SYSKEYDOWN: // SYSKEYDOWN handles Alt+keys { #if DEBUG U16 rep=lParam&0xFFFF; Bool ext=(lParam>>24)&1, ctx_code=(lParam>>29)&1, prev_state=(lParam>>30)&1, trans_state=(lParam>>31)&1; #endif Byte scan_code=(lParam>>16)&0xFF; KB_KEY key=KB_KEY(wParam); switch(key) { case KB_CTRL : if(lParam&(1<<24))key=KB_RCTRL;else return 0; break; // can't push KB_LCTRL, because it could be triggered by KB_RALT, just ignore this rely on RawInput/DirectInput //case KB_SHIFT: key=((lParam&(1<<24)) ? KB_RSHIFT : KB_LSHIFT); break; this is not working OK, lParam&(1<<24) is always false //case KB_SHIFT: key=((scan_code==42 ) ? KB_LSHIFT : KB_RSHIFT); break; 42=KB_LSHIFT, 54=KB_RSHIFT, releasing doesn't work OK //case KB_SHIFT: key=(KB_KEY)MapVirtualKey((lParam>>16)&0xFF, MAPVK_VSC_TO_VK_EX); break; releasing doesn't work OK case KB_ALT : key=((lParam&(1<<24)) ? KB_RALT : KB_LALT ); break; } Kb.push(key, scan_code); // !! queue characters after push !! if(Kb.anyCtrl() && !Kb.anyAlt()) // if Control is on, then WM_CHAR will not be called, so we must add this char here, don't do this with Alt pressed, because if Ctrl+Alt are pressed, then accented characters will get generated (even if it's left Alt) { if(key>='A' && key<='Z')Kb.queue(Char(key + (Kb.anyShift() ? 0 : 'a'-'A')), scan_code);else if(key>='0' && key<='9')Kb.queue(Char(key) , scan_code); } }return 0; case WM_SYSCHAR: // SYSCHAR handles Alt+chars, also disables beep on Alt+key menu sound when keyboard in non exclusive mode case WM_CHAR : { #if DEBUG U16 rep=lParam&0xFFFF; Bool ext=(lParam>>24)&1, ctx_code=(lParam>>29)&1, prev_state=(lParam>>30)&1, trans_state=(lParam>>31)&1; #endif Byte scan_code=(lParam>>16)&0xFF; #ifdef UNICODE Kb.queue((Char)wParam, scan_code); #else Kb.queue(Char8To16Fast(wParam), scan_code); // we can assume that Str was already initialized #endif }return 0; case WM_KEYUP : case WM_SYSKEYUP: // for KB_SHIFT this will be called only if both shifts are released { #if DEBUG Byte scan_code=(lParam>>16)&0xFF; #endif KB_KEY key=KB_KEY(wParam); switch(key) { case KB_CTRL : if(lParam&(1<<24))key=KB_RCTRL;else #if KB_RAW_INPUT return 0; #else if(Kb._special&1){key=KB_LCTRL; FlagDisable(Kb._special, 1);}else return 0; // release LCTRL only if it was locked #endif break; //case KB_SHIFT: key=((lParam&(1<<24)) ? KB_RSHIFT : KB_LSHIFT); break; this is not working OK, lParam&(1<<24) is always false //case KB_SHIFT: key=((scan_code==42 ) ? KB_LSHIFT : KB_RSHIFT); break; 42=KB_LSHIFT, 54=KB_RSHIFT, will not be called for one Shift key if other is already pressed //case KB_SHIFT: key=(KB_KEY)MapVirtualKey((lParam>>16)&0xFF, MAPVK_VSC_TO_VK_EX); break; will not be called for one Shift key if other is already pressed case KB_ALT : key=((lParam&(1<<24)) ? KB_RALT : KB_LALT ); break; } Kb.release(key); }return 0; case WM_HOTKEY: { #if !KB_RAW_INPUT if((lParam>>16)==VK_SNAPSHOT)Kb.push(KB_PRINT, -1); // this is needed only for DirectInput non-exclusive mode #endif }break; // IME case WM_INPUTLANGCHANGE : Kb.setLayout(); break; // LANG_TYPE((lParam>>16)&0xFF) case WM_IME_CHAR : return 0; // don't process by OS case WM_IME_KEYUP : if(!D.exclusive())break; return 0; // don't process by OS case WM_IME_KEYDOWN : if(!D.exclusive())break; return 0; // don't process by OS case WM_IME_SETCONTEXT : if(!D.exclusive())break; lParam=0; break ; // disables drawing of some windows, "lParam=0" disables drawing candidate list by OS, and enables accessing it manually using GetCandidateList case WM_IME_REQUEST : if(!D.exclusive())break; return 0; // don't process by OS case WM_IME_SELECT : if(!D.exclusive())break; return 0; // don't process by OS case WM_IME_COMPOSITIONFULL : if(!D.exclusive())break; return 0; // don't process by OS case WM_IME_STARTCOMPOSITION: if(!D.exclusive())break; return 0; // don't process by OS case WM_IME_ENDCOMPOSITION : if(!D.exclusive())break; return 0; // don't process by OS case WM_IME_COMPOSITION : { Kb._imm_buffer.clear(); // always 'clear' even for 'reserve' to avoid copying old data in 'setNum' Kb._imm_cursor =LOWORD(ImmGetCompositionString(Kb._imc, GCS_CURSORPOS, null, 0)); Int chars_needed= ImmGetCompositionString(Kb._imc, GCS_COMPSTR , null, 0)/SIZE(Char); if( chars_needed>0) { Kb._imm_buffer.reserve(chars_needed); ImmGetCompositionString(Kb._imc, GCS_COMPSTR, Kb._imm_buffer._d.data(), chars_needed*SIZE(Char)); Kb._imm_buffer._d [chars_needed]=0; Kb._imm_buffer._length=Length(Kb._imm_buffer()); } Kb._imm_selection=-1; Int clause_size=ImmGetCompositionStringA(Kb._imc, GCS_COMPCLAUSE, null, 0); // use A because W crashes for GCS_COMPCLAUSE under WinXP if( clause_size>0) { Memc clause; clause.setNum(clause_size/SIZE(UInt)); ImmGetCompositionStringA(Kb._imc, GCS_COMPCLAUSE, clause.data(), clause.elms()*clause.elmSize()); // use A because W crashes for GCS_COMPCLAUSE under WinXP FREPA(clause)if(clause[i]>=Kb.immCursor()) { Kb._imm_selection=clause[i]; if(InRange(i+1, clause))Kb._imm_selection.y=clause[i+1]; break; } } if(lParam&GCS_RESULTSTR) { Int chars_needed= ImmGetCompositionString(Kb._imc, GCS_RESULTSTR, null , 0)/SIZE(Char); if( chars_needed>0){Memt imm; imm.setNum(chars_needed); ImmGetCompositionString(Kb._imc, GCS_RESULTSTR, imm.data(), chars_needed*SIZE(Char)); FREP(chars_needed)Kb.queue(imm[i], -1);} } #if SUPPORT_WINDOWS_XP // additional check for WinXP if(OSVerNumber().x<=5) // WinXP and below (WinXP is 5.1, Vista is 6.0, Win7 is 6.1, Win8 is 6.2, Win8.1 is 6.3, Win10 is 10) UpdateCandidates(); // must be called here as well because of Chinese NeiMa on WinXP (NeiMa doesn't call IMN_CHANGECANDIDATE), however this can't be called for Vista or newer because it causes disappearing of candidates for QuanPin when typing "q6" #endif if(!D.exclusive())break; }return 0; // don't process by OS case WM_IME_NOTIFY: { switch(wParam) { case IMN_OPENCANDIDATE : if( Kb._imm_candidate_hidden){Kb._imm_candidate_hidden=false; Swap(Kb._imm_candidate_temp, Kb._imm_candidate);} break; // open must "show" candidates case IMN_CLOSECANDIDATE : if(!Kb._imm_candidate_hidden){Kb._imm_candidate_hidden=true ; Swap(Kb._imm_candidate_temp, Kb._imm_candidate);} break; // close must "hide" candidates case IMN_CHANGECANDIDATE: UpdateCandidates(); break; } if(!D.exclusive())break; }return 0; // don't process by OS // TOUCH case WM_POINTERDOWN : case WM_POINTERENTER: { POINT point={GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; VecI2 posi(point.x, point.y); ScreenToClient(App.Hwnd(), &point); UInt id=GET_POINTERID_WPARAM(wParam); CPtr pid=CPtr(id); Bool stylus=false; if(GetPointerType){POINTER_INPUT_TYPE type=PT_POINTER; if(GetPointerType(id, &type))if(type==PT_PEN)stylus=true;} Vec2 pos=D.windowPixelToScreen(VecI2(point.x, point.y)); Touch *touch=FindTouchByHandle(pid); if( !touch)touch=&Touches.New().init(posi, pos, pid, stylus);else { touch->_remove=false; // disable 'remove' in case it was enabled (for example the same touch was released in same/previous frame) if(msg!=WM_POINTERENTER)touch->reinit(posi, pos); // re-initialize for push (don't do this for hover because it can be called the same frame that release is called, and for release we want to keep the original values) } if(msg!=WM_POINTERENTER){touch->_state=BS_ON|BS_PUSHED; touch->_force=1;} }return 0; // don't process by OS case WM_POINTERUPDATE: { UInt id=GET_POINTERID_WPARAM(wParam); CPtr pid=CPtr(id); if(Touch *touch=FindTouchByHandle(pid)) { POINT point={GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; VecI2 posi(point.x, point.y); ScreenToClient(App.Hwnd(), &point); touch->_deltai+=posi-touch->_posi; touch->_posi =posi; touch->_pos =D.windowPixelToScreen(VecI2(point.x, point.y)); } }return 0; // don't process by OS case WM_POINTERUP : case WM_POINTERLEAVE: { UInt id=GET_POINTERID_WPARAM(wParam); CPtr pid=CPtr(id); if(Touch *touch=FindTouchByHandle(pid)) { POINT point={GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; VecI2 posi(point.x, point.y); ScreenToClient(App.Hwnd(), &point); touch->_deltai+=posi-touch->_posi; touch->_posi =posi; touch->_pos =D.windowPixelToScreen(VecI2(point.x, point.y)); touch->_remove =true; if(touch->_state&BS_ON) // check for state in case it was manually eaten { touch->_state|= BS_RELEASED; touch->_state&=~BS_ON; } } }return 0; // don't process by OS case WM_POINTERCAPTURECHANGED: { UInt id=GET_POINTERID_WPARAM(wParam); CPtr pid=CPtr(id); if(Touch *touch=FindTouchByHandle(pid)) { touch->_remove=true; if(touch->_state&BS_ON) // check for state in case it was manually eaten { touch->_state|= BS_RELEASED; touch->_state&=~BS_ON; } } }return 0; // don't process by OS // DRAW case WM_PAINT: ConditionalDraw(); break; case WM_ERASEBKGND: if(ErasedBackground)return 0; ErasedBackground=true; break; // DROP case WM_DROPFILES: if(App.drop) { HDROP handle=HDROP(wParam); wchar_t name[MAX_LONG_PATH]; Memc names; for(Int i=0; DragQueryFile(handle, i++, name, Elms(name))>0; )names.add(name); POINT point; DragQueryPoint(handle, &point); Vec2 pos=D.windowPixelToScreen(VecI2(point.x, point.y)); // get precise drop position DragFinish(handle); // release before the callback App.drop(names, Gui.objAtPos(pos), pos); if(!D.full() && !(App.flag&APP_WORK_IN_BACKGROUND))DrawState(); return 0; }break; // RECEIVE DATA case WM_COPYDATA: if(App.receive_data) { if(COPYDATASTRUCT *ds=(COPYDATASTRUCT*)lParam)App.receive_data(ds->lpData, ds->cbData, (Ptr)wParam); }break; // DEVICES case WM_DEVICECHANGE: { if(wParam==DBT_DEVICEARRIVAL || wParam==DBT_DEVICEREMOVECOMPLETE || wParam==DBT_DEVNODES_CHANGED)ListJoypads(); }break; } def: return DefWindowProc(hwnd, msg, wParam, lParam); } #endif /******************************************************************************/ #if WINDOWS static BOOL CALLBACK EnumResources(HMODULE hModule, LPCWSTR lpType, LPWSTR lpName, LONG_PTR lParam) { *((C wchar_t**)lParam)=lpName; return false; } static ATOM WindowClass=0; #elif LINUX struct XProp { Int format, count; Atom type; Ptr data; }; static Byte Deactivate=0; // after how many frames to deactivate static void ReadProperty(XProp &p, XWindow w, Atom prop) { unsigned char *ret=null; Atom type=NULL; int fmt =0; unsigned long count=0; unsigned long bytes_left=0; int bytes_fetch = 0; do{ if(ret){XFree(ret); ret=null;} XGetWindowProperty(XDisplay, w, prop, 0, bytes_fetch, false, AnyPropertyType, &type, &fmt, &count, &bytes_left, &ret); bytes_fetch+=bytes_left; }while(bytes_left); p.data =ret; p.format=fmt; p.count =count; p.type =type; } static Atom PickTarget(Atom list[], int list_count) { Atom request=NULL; for(int i=0; idepth, InputOutput, vis_info->visual, CWBackPixmap|CWBorderPixel|CWColormap|CWEventMask, &win_attr)) { XEvent event; Zero(event); event.xclient.type =ClientMessage; event.xclient.message_type=_NET_REQUEST_FRAME_EXTENTS; event.xclient.display =XDisplay; event.xclient.window =hwnd; event.xclient.format =32; XSendEvent(XDisplay, root_win, false, SubstructureRedirectMask|SubstructureNotifyMask, &event); XSync(XDisplay, false); // TODO: test on Ubuntu newer than 14.10 if multiple attempts are still needed (14.10 fails always) REP(1024) // 1024 attempts { Atom type=NULL; int format=0; unsigned long items=0, bytes_after=0; unsigned char *data=null; if(!XGetWindowProperty(XDisplay, hwnd, _NET_FRAME_EXTENTS, 0, SIZE(unsigned long)*4, false, XA_CARDINAL, &type, &format, &items, &bytes_after, &data)) if(type==XA_CARDINAL && format==32 && items>=4)if(long *l=(long*)data) { long left =l[0], right =l[1], top =l[2], bottom=l[3]; App._bound.set(-left, -top, right, bottom); } if(data){XFree(data); break;} usleep(1); } XDestroyWindow(XDisplay, hwnd); } } static Bool InitXInput2() { int event, error; if(XQueryExtension(XDisplay, "XInputExtension", &XInput2Extension, &event, &error)) { int major=2, minor=2; // 2.2 for multi touch if(!XIQueryVersion(XDisplay, &major, &minor)) { if(Compare(VecI2(major, minor), VecI2(2, 2))>=0) // got the version { unsigned char mask[3]={0,0,0}; XIEventMask event_mask; Zero(event_mask); event_mask.deviceid=XIAllMasterDevices; event_mask.mask_len=SIZE(mask); event_mask.mask =mask; XISetMask(mask, XI_RawMotion); XISetMask(mask, XI_RawButtonPress); XISetMask(mask, XI_RawButtonRelease); if(!XISelectEvents(XDisplay, DefaultRootWindow(XDisplay), &event_mask, 1)) { return true; } } } } return false; } static void SetXInputValues(C Dbl *input, unsigned char *mask, Int mask_len, Dbl *output, Int output_elms) { const Int max_axis=16; Int mask_elms=Min(max_axis, mask_len*8); // bits per byte ZeroN(output, output_elms); for(int i=0, o=0; i