diff --git a/Ide/LgiIdeProj.xml b/Ide/LgiIdeProj.xml --- a/Ide/LgiIdeProj.xml +++ b/Ide/LgiIdeProj.xml @@ -1,274 +1,290 @@ - - - - - - + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - + + + + + + + .\buildHaiku.py - ./buildHaiku.py + ./Makefile.linux ./MacCocoa/LgiIde.xcodeproj Makefile.haiku ./lgiide ./lgiide LgiIde.exe LgiIde.exe WINDOWS WINNATIVE WINDOWS WINNATIVE POSIX POSIX LIBPNG_VERSION=\"1.2\" LIBPNG_VERSION=\"1.2\" MingW /home/matthew/Code/Lgi/trunk/Ide/Code/IdeProjectSettings.cpp + + ./Code ./Resources ../include ./Code ./Resources ../include ..\include\lgi\win /local/include ..\include\lgi\win /local/include ../include/lgi/linux ../include/lgi/linux/Gtk ../../../../codelib/openssl/include ../include/lgi/linux ../include/lgi/linux/Gtk ../../../../codelib/openssl/include ../include/lgi/haiku ../include/lgi/haiku ../include/lgi/mac/cocoa ../include/lgi/mac/cocoa imm32 imm32 magic pthread `pkg-config --libs gtk+-3.0` -static-libgcc magic pthread `pkg-config --libs gtk+-3.0` -static-libgcc -static-libgcc gnu network be -static-libgcc gnu network be + ..\Debug ..\Release + Executable lgiide lgiide LgiIde.exe LgiIde.exe lgiide lgiide C:\Data\CodeLib\Gtk\include\gtk-2.0\gtk C:\Data\CodeLib\Gtk\include\gtk-2.0\gdk C:\Data\CodeLib\Gtk\include\gtk-2.0\gtk C:\Data\CodeLib\Gtk\include\gtk-2.0\gdk `pkg-config --cflags gtk+-3.0` `pkg-config --cflags gtk+-3.0` POSIX _GNU_SOURCE POSIX _GNU_SOURCE + + 4 4 0 1 + + SOME_TEST=testing /home/matthew/code/scribe/trunk_os/Linux/ScribeProj.xml + + - + + + + - + + + + diff --git a/Ide/Makefile.linux b/Ide/Makefile.linux --- a/Ide/Makefile.linux +++ b/Ide/Makefile.linux @@ -1,423 +1,421 @@ #!/usr/bin/make # # This makefile generated by LgiIde # http://www.memecode.com/lgi.php # .SILENT : CC = gcc CPP = g++ Target = ./lgiide ifndef Build Build = Debug endif BuildDir = $(Build) Flags = -fPIC -w -fno-inline -fpermissive ifeq ($(Build),Debug) Flags += -g -std=c++14 Tag = d Defs = -D_DEBUG -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX Libs = \ -L../Debug \ -lmagic \ -lpthread \ `pkg-config --libs gtk+-3.0` \ -static-libgcc \ -llgi-gtk3$(Tag) \ -L../$(BuildDir) Inc = \ `pkg-config --cflags gtk+-3.0` \ -I./Resources \ -I./Code \ -I../include/lgi/linux/Gtk \ -I../include/lgi/linux \ -I../include \ -I../../../../codelib/openssl/include else Flags += -s -Os -std=c++14 Defs = -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX Libs = \ -L../Release \ -lmagic \ -lpthread \ `pkg-config --libs gtk+-3.0` \ -static-libgcc \ -llgi-gtk3$(Tag) \ -L../$(BuildDir) Inc = \ `pkg-config --cflags gtk+-3.0` \ -I./Resources \ -I./Code \ -I../include/lgi/linux/Gtk \ -I../include/lgi/linux \ -I../include \ -I../../../../codelib/openssl/include endif # Dependencies Source = Code/WebFldDlg.cpp \ Code/SysCharSupport.cpp \ Code/SpaceTabConv.cpp \ Code/SimpleCppParser.cpp \ Code/PythonParser.cpp \ Code/ProjectNode.cpp \ Code/NewProjectFromTemplate.cpp \ Code/MissingFiles.cpp \ Code/MemDumpViewer.cpp \ Code/LgiUtils.cpp \ Code/LgiIde.cpp \ Code/levenshtein.c \ Code/JavascriptParser.cpp \ Code/IdeProjectSettings.cpp \ Code/IdeProject.cpp \ Code/IdeDoc.cpp \ Code/IdeCommon.cpp \ Code/History.cpp \ - Code/Debugger.cpp \ - Code/DebugContext.cpp \ Code/FtpThread.cpp \ Code/FindSymbol.cpp \ Code/FindInFiles.cpp \ Code/DocEditStyling.cpp \ Code/DocEdit.cpp \ Code/Debugger.cpp \ Code/DebugContext.cpp \ Code/AddFtpFile.cpp \ ../src/common/Text/TextConvert.cpp \ ../src/common/Text/HtmlParser.cpp \ ../src/common/Text/HtmlCommon.cpp \ ../src/common/Text/Html.cpp \ ../src/common/Text/Homoglyphs/HomoglyphsTable.cpp \ ../src/common/Text/Homoglyphs/Homoglyphs.cpp \ ../src/common/Text/DocView.cpp \ ../src/common/Net/OpenSSLSocket.cpp \ ../src/common/Net/Http.cpp \ ../src/common/Net/Ftp.cpp \ ../src/common/Lgi/Mdi.cpp \ ../src/common/Lgi/LgiMain.cpp \ ../src/common/Lgi/About.cpp \ ../src/common/Gdc2/Filters/Png.cpp \ ../src/common/Coding/ParseCpp.cpp \ ../src/common/Coding/LexCpp.cpp SourceLst := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(Source))) Objects := $(addprefix $(BuildDir)/,$(SourceLst)) Deps := $(patsubst %.o,%.d,$(Objects)) # Target # Executable target $(Target) : ../$(BuildDir)/liblgi-gtk3$(Tag).so $(Objects) mkdir -p $(BuildDir) @echo Linking $(Target) [$(Build)]... $(CPP) -Wl,-export-dynamic,-R. -o \ $(Target) $(Objects) $(Libs) @echo Done. ../$(BuildDir)/liblgi-gtk3$(Tag).so : ../include/lgi/common/App.h \ ../include/lgi/common/Array.h \ ../include/lgi/common/AutoPtr.h \ ../include/lgi/common/Base64.h \ ../include/lgi/common/Bitmap.h \ ../include/lgi/common/Box.h \ ../include/lgi/common/Button.h \ ../include/lgi/common/CairoSurface.h \ ../include/lgi/common/Cancel.h \ ../include/lgi/common/Capabilities.h \ ../include/lgi/common/Charset.h \ ../include/lgi/common/CheckBox.h \ ../include/lgi/common/ClipBoard.h \ ../include/lgi/common/Colour.h \ ../include/lgi/common/ColourSpace.h \ ../include/lgi/common/Com.h \ ../include/lgi/common/Combo.h \ ../include/lgi/common/Containers.h \ ../include/lgi/common/Core.h \ ../include/lgi/common/Css.h \ ../include/lgi/common/CssTools.h \ ../include/lgi/common/CurrentTime.h \ ../include/lgi/common/DataDlg.h \ ../include/lgi/common/DateTime.h \ ../include/lgi/common/Dialog.h \ ../include/lgi/common/DisplayString.h \ ../include/lgi/common/DocView.h \ ../include/lgi/common/Dom.h \ ../include/lgi/common/DomFields.h \ ../include/lgi/common/DragAndDrop.h \ ../include/lgi/common/DropFiles.h \ ../include/lgi/common/Edit.h \ ../include/lgi/common/Error.h \ ../include/lgi/common/EventTargetThread.h \ ../include/lgi/common/File.h \ ../include/lgi/common/FileSelect.h \ ../include/lgi/common/Filter.h \ ../include/lgi/common/FindReplaceDlg.h \ ../include/lgi/common/Font.h \ ../include/lgi/common/FontCache.h \ ../include/lgi/common/FontSelect.h \ ../include/lgi/common/Gdc2.h \ ../include/lgi/common/GdcTools.h \ ../include/lgi/common/GdiLeak.h \ ../include/lgi/common/HashTable.h \ ../include/lgi/common/ImageList.h \ ../include/lgi/common/Input.h \ ../include/lgi/common/ItemContainer.h \ ../include/lgi/common/Json.h \ ../include/lgi/common/Layout.h \ ../include/lgi/common/Lgi.h \ ../include/lgi/common/LgiClasses.h \ ../include/lgi/common/LgiCommon.h \ ../include/lgi/common/LgiDefs.h \ ../include/lgi/common/LgiInc.h \ ../include/lgi/common/LgiInterfaces.h \ ../include/lgi/common/LgiMsgs.h \ ../include/lgi/common/LgiNetInc.h \ ../include/lgi/common/LgiRes.h \ ../include/lgi/common/LgiString.h \ ../include/lgi/common/LgiUiBase.h \ ../include/lgi/common/Library.h \ ../include/lgi/common/LibraryUtils.h \ ../include/lgi/common/List.h \ ../include/lgi/common/ListItemCheckBox.h \ ../include/lgi/common/ListItemRadioBtn.h \ ../include/lgi/common/LMallocArray.h \ ../include/lgi/common/Mail.h \ ../include/lgi/common/Matrix.h \ ../include/lgi/common/Mem.h \ ../include/lgi/common/Menu.h \ ../include/lgi/common/Message.h \ ../include/lgi/common/Mime.h \ ../include/lgi/common/Mru.h \ ../include/lgi/common/Mutex.h \ ../include/lgi/common/Net.h \ ../include/lgi/common/NetTools.h \ ../include/lgi/common/Notifications.h \ ../include/lgi/common/OAuth2.h \ ../include/lgi/common/OptionsFile.h \ ../include/lgi/common/Palette.h \ ../include/lgi/common/Panel.h \ ../include/lgi/common/Password.h \ ../include/lgi/common/Path.h \ ../include/lgi/common/PixelRops.h \ ../include/lgi/common/Point.h \ ../include/lgi/common/Popup.h \ ../include/lgi/common/PopupList.h \ ../include/lgi/common/Printer.h \ ../include/lgi/common/Profile.h \ ../include/lgi/common/Progress.h \ ../include/lgi/common/ProgressDlg.h \ ../include/lgi/common/ProgressView.h \ ../include/lgi/common/Properties.h \ ../include/lgi/common/RadioGroup.h \ ../include/lgi/common/Range.h \ ../include/lgi/common/Rect.h \ ../include/lgi/common/RectF.h \ ../include/lgi/common/RefCount.h \ ../include/lgi/common/RegKey.h \ ../include/lgi/common/Res.h \ ../include/lgi/common/Rops.h \ ../include/lgi/common/ScrollBar.h \ ../include/lgi/common/SkinEngine.h \ ../include/lgi/common/Slider.h \ ../include/lgi/common/Splitter.h \ ../include/lgi/common/StatusBar.h \ ../include/lgi/common/Store3Defs.h \ ../include/lgi/common/Stream.h \ ../include/lgi/common/StringClass.h \ ../include/lgi/common/StringLayout.h \ ../include/lgi/common/StructuredIo.h \ ../include/lgi/common/StructuredLog.h \ ../include/lgi/common/SubProcess.h \ ../include/lgi/common/TableLayout.h \ ../include/lgi/common/TabView.h \ ../include/lgi/common/TextFile.h \ ../include/lgi/common/TextLabel.h \ ../include/lgi/common/TextLog.h \ ../include/lgi/common/TextView3.h \ ../include/lgi/common/Thread.h \ ../include/lgi/common/ThreadEvent.h \ ../include/lgi/common/Token.h \ ../include/lgi/common/ToolBar.h \ ../include/lgi/common/ToolTip.h \ ../include/lgi/common/TrayIcon.h \ ../include/lgi/common/Tree.h \ ../include/lgi/common/Undo.h \ ../include/lgi/common/Unicode.h \ ../include/lgi/common/UnicodeString.h \ ../include/lgi/common/UnrolledList.h \ ../include/lgi/common/Variant.h \ ../include/lgi/common/View.h \ ../include/lgi/common/Widgets.h \ ../include/lgi/common/Window.h \ ../include/lgi/common/XmlTree.h \ ../include/lgi/linux/Gtk/LgiOsClasses.h \ ../include/lgi/linux/Gtk/LgiOsDefs.h \ ../include/lgi/linux/Gtk/LgiWidget.h \ ../include/lgi/linux/Gtk/LgiWinManGlue.h \ ../include/lgi/linux/SymLookup.h \ ../include/lgi/mac/cocoa/LCocoaView.h \ ../include/lgi/mac/cocoa/LgiMac.h \ ../include/lgi/mac/cocoa/LgiOs.h \ ../include/lgi/mac/cocoa/LgiOsClasses.h \ ../include/lgi/mac/cocoa/LgiOsDefs.h \ ../include/lgi/mac/cocoa/ObjCWrapper.h \ ../include/lgi/mac/cocoa/SymLookup.h \ ../private/common/FontPriv.h \ ../private/common/ViewPriv.h \ ../private/linux/AppPriv.h \ ../src/common/Gdc2/15Bit.cpp \ ../src/common/Gdc2/16Bit.cpp \ ../src/common/Gdc2/24Bit.cpp \ ../src/common/Gdc2/32Bit.cpp \ ../src/common/Gdc2/8Bit.cpp \ ../src/common/Gdc2/Alpha.cpp \ ../src/common/Gdc2/Colour.cpp \ ../src/common/Gdc2/Filters/Filter.cpp \ ../src/common/Gdc2/Font/Charset.cpp \ ../src/common/Gdc2/Font/DisplayString.cpp \ ../src/common/Gdc2/Font/Font.cpp \ ../src/common/Gdc2/Font/FontSystem.cpp \ ../src/common/Gdc2/Font/FontType.cpp \ ../src/common/Gdc2/Font/StringLayout.cpp \ ../src/common/Gdc2/Font/TypeFace.cpp \ ../src/common/Gdc2/GdcCommon.cpp \ ../src/common/Gdc2/Path/Path.cpp \ ../src/common/Gdc2/Rect.cpp \ ../src/common/Gdc2/RopsCases.cpp \ ../src/common/Gdc2/Surface.cpp \ ../src/common/Gdc2/Tools/ColourReduce.cpp \ ../src/common/Gdc2/Tools/GdcTools.cpp \ ../src/common/General/Containers.cpp \ ../src/common/General/DateTime.cpp \ ../src/common/General/ExeCheck.cpp \ ../src/common/General/FileCommon.cpp \ ../src/common/General/Password.cpp \ ../src/common/General/Properties.cpp \ ../src/common/Hash/md5/md5.c \ ../src/common/Hash/md5/md5.h \ ../src/common/Hash/sha1/sha1.c \ ../src/common/Hash/sha1/sha1.h \ ../src/common/Lgi/Alert.cpp \ ../src/common/Lgi/AppCommon.cpp \ ../src/common/Lgi/Css.cpp \ ../src/common/Lgi/CssTools.cpp \ ../src/common/Lgi/DataDlg.cpp \ ../src/common/Lgi/DragAndDropCommon.cpp \ ../src/common/Lgi/FileSelect.cpp \ ../src/common/Lgi/FindReplace.cpp \ ../src/common/Lgi/FontSelect.cpp \ ../src/common/Lgi/GuiUtils.cpp \ ../src/common/Lgi/Input.cpp \ ../src/common/Lgi/LgiCommon.cpp \ ../src/common/Lgi/Library.cpp \ ../src/common/Lgi/LMsg.cpp \ ../src/common/Lgi/MemStream.cpp \ ../src/common/Lgi/MenuCommon.cpp \ ../src/common/Lgi/Mru.cpp \ ../src/common/Lgi/Mutex.cpp \ ../src/common/Lgi/Object.cpp \ ../src/common/Lgi/OptionsFile.cpp \ ../src/common/Lgi/Rand.cpp \ ../src/common/Lgi/Stream.cpp \ ../src/common/Lgi/SubProcess.cpp \ ../src/common/Lgi/ThreadCommon.cpp \ ../src/common/Lgi/ThreadEvent.cpp \ ../src/common/Lgi/ToolTip.cpp \ ../src/common/Lgi/TrayIcon.cpp \ ../src/common/Lgi/Variant.cpp \ ../src/common/Lgi/ViewCommon.cpp \ ../src/common/Lgi/WindowCommon.cpp \ ../src/common/Net/Base64.cpp \ ../src/common/Net/MDStringToDigest.cpp \ ../src/common/Net/Net.cpp \ ../src/common/Net/NetTools.cpp \ ../src/common/Net/Uri.cpp \ ../src/common/Resource/LgiRes.cpp \ ../src/common/Resource/Res.cpp \ ../src/common/Skins/Gel/Gel.cpp \ ../src/common/Text/DocView.cpp \ ../src/common/Text/String.cpp \ ../src/common/Text/TextView3.cpp \ ../src/common/Text/Token.cpp \ ../src/common/Text/Unicode.cpp \ ../src/common/Text/Utf8.cpp \ ../src/common/Text/XmlTree.cpp \ ../src/common/Widgets/Bitmap.cpp \ ../src/common/Widgets/Box.cpp \ ../src/common/Widgets/Button.cpp \ ../src/common/Widgets/CheckBox.cpp \ ../src/common/Widgets/Combo.cpp \ ../src/common/Widgets/Edit.cpp \ ../src/common/Widgets/ItemContainer.cpp \ ../src/common/Widgets/List.cpp \ ../src/common/Widgets/Panel.cpp \ ../src/common/Widgets/Popup.cpp \ ../src/common/Widgets/Progress.cpp \ ../src/common/Widgets/ProgressDlg.cpp \ ../src/common/Widgets/RadioGroup.cpp \ ../src/common/Widgets/ScrollBar.cpp \ ../src/common/Widgets/Slider.cpp \ ../src/common/Widgets/Splitter.cpp \ ../src/common/Widgets/StatusBar.cpp \ ../src/common/Widgets/TableLayout.cpp \ ../src/common/Widgets/TabView.cpp \ ../src/common/Widgets/TextLabel.cpp \ ../src/common/Widgets/ToolBar.cpp \ ../src/common/Widgets/Tree.cpp \ ../src/linux/General/File.cpp \ ../src/linux/General/Mem.cpp \ ../src/linux/General/ShowFileProp_Linux.cpp \ ../src/linux/Gtk/Gdc2.cpp \ ../src/linux/Gtk/LgiWidget.cpp \ ../src/linux/Gtk/MemDC.cpp \ ../src/linux/Gtk/PrintDC.cpp \ ../src/linux/Gtk/ScreenDC.cpp \ ../src/linux/Lgi/App.cpp \ ../src/linux/Lgi/ClipBoard.cpp \ ../src/linux/Lgi/DragAndDrop.cpp \ ../src/linux/Lgi/General.cpp \ ../src/linux/Lgi/Layout.cpp \ ../src/linux/Lgi/Menu.cpp \ ../src/linux/Lgi/Printer.cpp \ ../src/linux/Lgi/Thread.cpp \ ../src/linux/Lgi/View.cpp \ ../src/linux/Lgi/Widgets.cpp \ ../src/linux/Lgi/Window.cpp export Build=$(Build); \ $(MAKE) -C .. -f Makefile.linux .SECONDEXPANSION: $(Objects): $(BuildDir)/%.o: $$(wildcard %.c*) mkdir -p $(@D) @echo $( Makefile.linux Makefile.win64 Makefile.macosx gcc 0 - + ./include ./private/common ./include ./private/common ./include/lgi/linux ./include/lgi/linux/Gtk ./private/linux ./include/lgi/linux ./include/lgi/linux/Gtk ./private/linux ./include/lgi/win ./private/win ./include/lgi/win ./private/win ./include/lgi/haiku ./private/haiku ./include/lgi/haiku ./private/haiku /usr/include/libappindicator3-0.1 `pkg-config --cflags gtk+-3.0` `pkg-config --cflags gstreamer-1.0` /usr/include/libappindicator3-0.1 `pkg-config --cflags gtk+-3.0` `pkg-config --cflags gstreamer-1.0` - magic appindicator3 crypt -static-libgcc `pkg-config --libs gtk+-3.0` magic appindicator3 crypt -static-libgcc `pkg-config --libs gtk+-3.0` -static-libgcc gnu network be -static-libgcc gnu network be - lgi-gtk3 lgi-gtk3 - DynamicLibrary LGI_LIBRARY LGI_LIBRARY POSIX _GNU_SOURCE POSIX _GNU_SOURCE - + - + - + diff --git a/Lvc/Src/PatchViewer.cpp b/Lvc/Src/PatchViewer.cpp --- a/Lvc/Src/PatchViewer.cpp +++ b/Lvc/Src/PatchViewer.cpp @@ -1,604 +1,610 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Box.h" #include "lgi/common/TextLog.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Edit.h" #include "lgi/common/Button.h" #include "lgi/common/FileSelect.h" #include "lgi/common/OptionsFile.h" #include "lgi/common/XmlTreeUi.h" enum Ctrls { IDC_STATIC = -1, IDC_BOX1 = 100, IDC_BOX2, IDC_IN, IDC_OUT, IDC_TBL, IDC_PATCH_FILE, IDC_BROWSE_PATCH, IDC_BASE_DIR, IDC_BROWSE_BASE, IDC_APPLY, IDC_SAVE_PATCH, }; template ssize_t lineLen(T *start) { auto s = start; for (; *s && *s != '\n'; s++); return s - start; } class PatchView : public LTextLog { public: PatchView(int id) : LTextLog(id) { } void PourStyle(size_t Start, ssize_t Length) { for (auto l: Line) { auto s = Text + l->Start; if (*s == '+' && s[1] != '+') { l->c = LColour::Green; } else if (*s == '-' && s[1] != '-') { l->c = LColour::Red; } else if (*s == '@' && s[1] == '@') { l->c.Rgb(128, 128, 128); l->Back.Rgb(222, 222, 222); } } } }; class FileView : public LTextLog { public: LArray Fore, Back; FileView(int id) : LTextLog(id) { } bool Open(const char *Name, const char *Cs = NULL) { Fore.Length(0); Back.Length(0); return LTextView3::Open(Name, Cs); } void Reset(const char16 *t) { NameW(t); Fore.Length(0); Back.Length(0); } void Update() { PourStyle(0, Size); Invalidate(); } void PourStyle(size_t Start, ssize_t Length) { for (size_t i=0; iStart; if (Fore.IdxCheck(i)) { auto &c = Fore[i]; if (c.IsValid()) l->c = c; } if (Back.IdxCheck(i)) { auto &c = Back[i]; if (c.IsValid()) l->Back = c; } } } }; class PatchViewer : public LWindow, public LXmlTreeUi { LBox *box1 = NULL; LBox *box2 = NULL; FileView *in = NULL; LTextLog *out = NULL; LTableLayout *tbl = NULL; LOptionsFile *Opts = NULL; public: PatchViewer(LViewI *Parent, LOptionsFile *opts) : Opts(opts) { LRect r(0, 0, 1600, 1000); SetPos(r); Name("Patcher"); MoveToCenter(); Map("PatchFile", IDC_PATCH_FILE); Map("PatchBaseDir", IDC_BASE_DIR); if (!Attach(0)) return; AddView(box1 = new LBox(IDC_BOX1, true)); box1->AddView(tbl = new LTableLayout(IDC_TBL)); tbl->GetCss(true)->Height("6em"); int y = 0; auto c = tbl->GetCell(0, y); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Patch file:")); c->VerticalAlign(LCss::VerticalMiddle); c->PaddingLeft("0.5em"); c->PaddingTop("0.5em"); c = tbl->GetCell(1, y); c->Add(new LEdit(IDC_PATCH_FILE, 0, 0, 60, 20)); c->PaddingTop("0.5em"); c = tbl->GetCell(2, y++); c->Add(new LButton(IDC_BROWSE_PATCH, 0, 0, -1, -1, "...")); c->PaddingRight("0.5em"); c->PaddingTop("0.5em"); c = tbl->GetCell(0, y); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Base folder:")); c->VerticalAlign(LCss::VerticalMiddle); c->PaddingLeft("0.5em"); c = tbl->GetCell(1, y); c->Add(new LEdit(IDC_BASE_DIR, 0, 0, 60, 20)); c = tbl->GetCell(2, y++); c->Add(new LButton(IDC_BROWSE_BASE, 0, 0, -1, -1, "...")); c->PaddingRight("0.5em"); c = tbl->GetCell(0, y); c = tbl->GetCell(1, y); c->Add(new LButton(IDC_SAVE_PATCH, 0, 0, -1, -1, "Save Patch")); c->TextAlign(LCss::AlignRight); c = tbl->GetCell(2, y); c->Add(new LButton(IDC_APPLY, 0, 0, -1, -1, "Apply")); c->PaddingRight("0.5em"); box1->AddView(box2 = new LBox(IDC_BOX2)); box2->AddView(in = new FileView(IDC_IN)); box2->AddView(out = new PatchView(IDC_OUT)); Convert(Opts, this, true); AttachChildren(); Visible(true); in->Name("...select a patch blob..."); auto p = GetCtrlName(IDC_PATCH_FILE); if (p) Open(p); } ~PatchViewer() { Convert(Opts, this, false); } bool Open(const char *Patch) { if (!out->LTextView3::Open(Patch)) return false; SetCtrlName(IDC_PATCH_FILE, Patch); return true; } template LArray GetLines(T *txt) { LArray lines; lines.Add(txt); for (auto t = txt; *t; t++) { if (*t == '\n' && *t) lines.Add(t + 1); } return lines; } struct FilePatch { LString File; LArray Hunks; }; bool Apply() { auto patch = out->NameW(); auto lines = GetLines(out->NameW()); LArray Patches; auto error = [](const char *msg) -> bool { LgiTrace("Apply error: %s\n", msg); return false; }; auto scanLines = [&lines](const char16 *key, size_t from) -> ssize_t { auto len = Strlen(key); for (size_t i = from; i LString { auto ptr = lines[idx]; LString s(ptr, lineLen(ptr)); return s; }; ssize_t aFile = -1, bFile = -1; for (size_t i=0; i 0 && bFile > 0 && aFile == bFile - 1) { FilePatch &fp = Patches.New(); auto s = lineAt(aFile); fp.File = s(6,-1); LgiTrace("File: %s\n", fp.File.Get()); // Collect blobs LRange *cur = NULL; size_t n = bFile + 1; for (; n < lines.Length(); n++) { auto ln = lines[n]; if (*ln == ' ') continue; bool start = ln[0] == '@' && ln[1] == '@'; bool end = (ln[0] == '-' && ln[1] == '-'); bool finish = Strnicmp(ln, L"diff ", 5) == 0; if ((start || end || finish) && cur) { cur->Len = n - cur->Start; cur = NULL; } if (start && !cur) { cur = &fp.Hunks.New(); cur->Start = n; } if (finish) break; } for (auto &b: fp.Hunks) LgiTrace(" Blob: %i:%i\n", (int)b.Start, (int)b.Len); i = n; aFile = bFile = -1; } } // Iterate all the patches and apply them... for (auto &fp: Patches) { LFile::Path path(GetCtrlName(IDC_BASE_DIR), fp.File); if (!path.Exists()) { LgiTrace("%s:%i - File to patch '%s' doesn't exist.\n", _FL, path.GetFull().Get()); return false; } LFile f(path, O_READ); auto utf = f.Read(); size_t crlf = 0, lf = 0; for (const char *s = utf; *s; s++) { if (s[0] == '\r' && s[1] == '\n') { crlf++; s++; } else if (s[0] == '\n') { lf++; } } auto eol = crlf > lf ? L"\r\n" : L"\n"; auto eolLen = Strlen(eol); LAutoWString wide(Utf8ToWide(utf)); LArray docLines; for (char16 *w = wide; *w; ) { for (auto e = w; true; e++) { if (!Strncmp(e, eol, eolLen)) { docLines.New().Reset(NewStrW(w, e - w)); w = e + eolLen; break; } if (!*e) { docLines.New().Reset(NewStrW(w, e - w)); w = e; break; } } } /* for (size_t i=0; i (ssize_t)docLines.Length()) return error("docLine out of range."); auto docTxt = docLines[docLine].Get(); auto docTxtLen = lineLen(docTxt); auto inputLineLen = lineLen(inputTxt); if (*inputTxt == '+') { // Process insert: add 'inputLine' at docLine LAutoWString insert(NewStrW(inputTxt + 1, inputLineLen - 1)); docLines.AddAt(docLine, insert); } else { // Check content... if (docTxtLen != inputLineLen - 1) return error("doc and input line lengths don't match."); auto cmp = Strncmp(docTxt, inputTxt + 1/*prefix char*/, docTxtLen); if (cmp) return error("doc and input line content don't match."); if (*inputTxt == '-') { // Process delete... docLines.DeleteAt(docLine--, true); } } docLine++; inputTxt = lines[hunk.Start + ++inputLine]; } } } return true; } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_BROWSE_PATCH: { - LFileSelect s; - s.Parent(this); - s.InitialDir(GetCtrlName(IDC_PATCH_FILE)); - if (s.Open()) + auto s = new LFileSelect; + s->Parent(this); + s->InitialDir(GetCtrlName(IDC_PATCH_FILE)); + s->Open([this](auto s, auto status) { - SetCtrlName(IDC_PATCH_FILE, s.Name()); - Open(s.Name()); - } + if (status) + { + SetCtrlName(IDC_PATCH_FILE, s->Name()); + Open(s->Name()); + } + delete s; + }); break; } case IDC_BROWSE_BASE: { - LFileSelect s; - s.Parent(this); - if (s.OpenFolder()) + auto s = new LFileSelect; + s->Parent(this); + s->OpenFolder([this](auto s, auto status) { - SetCtrlName(IDC_BASE_DIR, s.Name()); - } + if (status) + SetCtrlName(IDC_BASE_DIR, s->Name()); + delete s; + }); break; } case IDC_SAVE_PATCH: { auto patchFile = GetCtrlName(IDC_PATCH_FILE); out->Save(patchFile); break; } case IDC_OUT: { if (n.Type != LNotifyCursorChanged) break; auto car = out->GetCaret(); auto txt = out->NameW(); if (!txt) break; ssize_t caratLine = -1; LArray lines; lines.Add(txt); for (auto t = txt; *t; t++) { if (caratLine < 0 && (t - txt) >= car) caratLine = lines.Length() - 1; if (*t == '\n' && *t) lines.Add(t + 1); } if (caratLine < 0) { LgiTrace("%s:%i - No line num for carat pos.\n", _FL); break; } if (caratLine >= (ssize_t)lines.Length()) { LgiTrace("%s:%i - carat line greater than lines.len.\n", _FL); break; } LRange blob(-1, 0); for (ssize_t s = caratLine; s >= 0; s--) { auto ln = lines[s]; if (ln[0] == '@' && ln[1] == '@') { blob.Start = s; break; } } for (ssize_t e = caratLine; e < (ssize_t)lines.Length(); e++) { auto ln = lines[e]; if ( (ln[0] == '@' && ln[1] == '@') || (ln[0] == '-' && ln[1] == '-') ) { blob.Len = e - blob.Start; break; } else if (e == lines.Length() - 1) { blob.Len = e - blob.Start + 1; break; } } if (!blob.Valid()) { LgiTrace("%s:%i - invalid patch blob size.\n", _FL); break; } auto filesIdx = blob.Start; while (filesIdx >= 0) { auto t = lines[filesIdx]; if (Strnicmp(t, L"--- a/", 6) == 0) break; filesIdx--; } if (filesIdx == 0) { LgiTrace("%s:%i - couldn't find file names lines.\n", _FL); break; } auto aFile = lines[filesIdx]; auto bFile = lines[filesIdx + 1]; LFile::Path p(GetCtrlName(IDC_BASE_DIR), LString(aFile, lineLen(aFile))(5,-1)); if (!p.Exists()) { LgiTrace("%s:%i - file '%s' doesn't exist.\n", _FL, p.GetFull().Get()); break; } if (!in->Open(p)) { LgiTrace("%s:%i - failed to open file '%s'.\n", _FL, p.GetFull().Get()); break; } auto sText = lines[blob.Start]; auto eText = lines[blob.End()]; auto ln = LString(sText, lineLen(sText)); auto hdr = ln.SplitDelimit(); auto before = hdr[1].SplitDelimit(","); auto after = hdr[2].SplitDelimit(","); auto beforeLn = -before[0].Int() - 1; auto beforeEnd = beforeLn + before[1].Int(); size_t blobIdx = blob.Start + 1; for (ssize_t i=beforeLn; iBack[i].Rgb(222, 222, 222); auto patchLn = lines[blobIdx++]; while (*patchLn == '+') patchLn = lines[blobIdx++]; if (*patchLn != '+') { auto inTxt = in->TextAtLine(i); auto inLen = lineLen(inTxt); if (Strnicmp(inTxt, patchLn + 1, inLen)) { in->Fore[i] = LColour::Red; } else { if (*patchLn == '-') in->Fore[i] = LColour::Green; } } } in->Update(); in->SetLine((int)beforeLn); break; } case IDC_APPLY: { Apply(); break; } } return LWindow::OnNotify(Ctrl, n); } }; void OpenPatchViewer(LViewI *Parent, LOptionsFile *Opts) { new PatchViewer(Parent, Opts); } diff --git a/Makefile.linux b/Makefile.linux --- a/Makefile.linux +++ b/Makefile.linux @@ -1,217 +1,219 @@ #!/usr/bin/make # # This makefile generated by LgiIde # http://www.memecode.com/lgi.php # .SILENT : CC = gcc CPP = g++ Target = lgi-gtk3 ifndef Build Build = Debug endif BuildDir = $(Build) Flags = -fPIC -w -fno-inline -fpermissive ifeq ($(Build),Debug) Flags += -g -std=c++14 Tag = d - Defs = -D_DEBUG -DLINUX -D_REENTRANT -DLGI_LIBRARY + Defs = -D_DEBUG -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX -DLGI_LIBRARY Libs = \ -lmagic \ -lappindicator3 \ -lcrypt \ -static-libgcc \ `pkg-config --libs gtk+-3.0` Inc = \ `pkg-config --cflags gtk+-3.0` \ `pkg-config --cflags gstreamer-1.0` \ + -I/usr/include/libappindicator3-0.1 \ -I./private/linux \ -I./private/common \ -I./include/lgi/linux/Gtk \ -I./include/lgi/linux \ -I./include else Flags += -s -Os -std=c++14 - Defs = -DLINUX -D_REENTRANT -DLGI_LIBRARY + Defs = -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX -DLGI_LIBRARY Libs = \ -lmagic \ -lappindicator3 \ -lcrypt \ -static-libgcc \ `pkg-config --libs gtk+-3.0` Inc = \ `pkg-config --cflags gtk+-3.0` \ `pkg-config --cflags gstreamer-1.0` \ + -I/usr/include/libappindicator3-0.1 \ -I./private/linux \ -I./private/common \ -I./include/lgi/linux/Gtk \ -I./include/lgi/linux \ -I./include endif # Dependencies Source = src/linux/Lgi/Window.cpp \ src/linux/Lgi/Widgets.cpp \ src/linux/Lgi/View.cpp \ src/linux/Lgi/Thread.cpp \ src/linux/Lgi/Printer.cpp \ src/linux/Lgi/Menu.cpp \ src/linux/Lgi/Layout.cpp \ src/linux/Lgi/General.cpp \ src/linux/Lgi/DragAndDrop.cpp \ src/linux/Lgi/ClipBoard.cpp \ src/linux/Lgi/App.cpp \ src/linux/Gtk/ScreenDC.cpp \ src/linux/Gtk/PrintDC.cpp \ src/linux/Gtk/MemDC.cpp \ src/linux/Gtk/LgiWidget.cpp \ src/linux/Gtk/Gdc2.cpp \ src/linux/General/ShowFileProp_Linux.cpp \ src/linux/General/Mem.cpp \ src/linux/General/File.cpp \ src/common/Widgets/Tree.cpp \ src/common/Widgets/ToolBar.cpp \ src/common/Widgets/TextLabel.cpp \ src/common/Widgets/TabView.cpp \ src/common/Widgets/TableLayout.cpp \ src/common/Widgets/StatusBar.cpp \ src/common/Widgets/Splitter.cpp \ src/common/Widgets/Slider.cpp \ src/common/Widgets/ScrollBar.cpp \ src/common/Widgets/RadioGroup.cpp \ src/common/Widgets/ProgressDlg.cpp \ src/common/Widgets/Progress.cpp \ src/common/Widgets/Popup.cpp \ src/common/Widgets/Panel.cpp \ src/common/Widgets/List.cpp \ src/common/Widgets/ItemContainer.cpp \ src/common/Widgets/Edit.cpp \ src/common/Widgets/Combo.cpp \ src/common/Widgets/CheckBox.cpp \ src/common/Widgets/Button.cpp \ src/common/Widgets/Box.cpp \ src/common/Widgets/Bitmap.cpp \ src/common/Text/XmlTree.cpp \ src/common/Text/Utf8.cpp \ src/common/Text/Unicode.cpp \ src/common/Text/Token.cpp \ src/common/Text/TextView3.cpp \ src/common/Text/String.cpp \ src/common/Text/DocView.cpp \ src/common/Skins/Gel/Gel.cpp \ src/common/Resource/Res.cpp \ src/common/Resource/LgiRes.cpp \ src/common/Net/Uri.cpp \ src/common/Net/NetTools.cpp \ src/common/Net/Net.cpp \ src/common/Net/MDStringToDigest.cpp \ src/common/Net/Base64.cpp \ src/common/Lgi/WindowCommon.cpp \ src/common/Lgi/ViewCommon.cpp \ src/common/Lgi/Variant.cpp \ src/common/Lgi/TrayIcon.cpp \ src/common/Lgi/ToolTip.cpp \ src/common/Lgi/ThreadEvent.cpp \ src/common/Lgi/ThreadCommon.cpp \ src/common/Lgi/SubProcess.cpp \ src/common/Lgi/Stream.cpp \ src/common/Lgi/Rand.cpp \ src/common/Lgi/OptionsFile.cpp \ src/common/Lgi/Object.cpp \ src/common/Lgi/Mutex.cpp \ src/common/Lgi/Mru.cpp \ src/common/Lgi/MenuCommon.cpp \ src/common/Lgi/MemStream.cpp \ src/common/Lgi/LMsg.cpp \ src/common/Lgi/Library.cpp \ src/common/Lgi/LgiCommon.cpp \ src/common/Lgi/Input.cpp \ src/common/Lgi/GuiUtils.cpp \ src/common/Lgi/FontSelect.cpp \ src/common/Lgi/FindReplace.cpp \ src/common/Lgi/FileSelect.cpp \ src/common/Lgi/DragAndDropCommon.cpp \ src/common/Lgi/DataDlg.cpp \ src/common/Lgi/CssTools.cpp \ src/common/Lgi/Css.cpp \ src/common/Lgi/AppCommon.cpp \ src/common/Lgi/Alert.cpp \ src/common/Hash/sha1/sha1.c \ src/common/Hash/md5/md5.c \ src/common/General/Properties.cpp \ src/common/General/Password.cpp \ src/common/General/FileCommon.cpp \ src/common/General/ExeCheck.cpp \ src/common/General/DateTime.cpp \ src/common/General/Containers.cpp \ src/common/Gdc2/Tools/GdcTools.cpp \ src/common/Gdc2/Tools/ColourReduce.cpp \ src/common/Gdc2/Surface.cpp \ src/common/Gdc2/Rect.cpp \ src/common/Gdc2/Path/Path.cpp \ src/common/Gdc2/GdcCommon.cpp \ src/common/Gdc2/Font/TypeFace.cpp \ src/common/Gdc2/Font/StringLayout.cpp \ src/common/Gdc2/Font/FontType.cpp \ src/common/Gdc2/Font/FontSystem.cpp \ src/common/Gdc2/Font/Font.cpp \ src/common/Gdc2/Font/DisplayString.cpp \ src/common/Gdc2/Font/Charset.cpp \ src/common/Gdc2/Filters/Filter.cpp \ src/common/Gdc2/Colour.cpp \ src/common/Gdc2/Alpha.cpp \ src/common/Gdc2/8Bit.cpp \ src/common/Gdc2/32Bit.cpp \ src/common/Gdc2/24Bit.cpp \ src/common/Gdc2/16Bit.cpp \ src/common/Gdc2/15Bit.cpp SourceLst := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(Source))) Objects := $(addprefix $(BuildDir)/,$(SourceLst)) Deps := $(patsubst %.o,%.d,$(Objects)) # Target TargetFile = lib$(Target)$(Tag).so $(TargetFile) : $(Objects) mkdir -p $(BuildDir) @echo Linking $(TargetFile) [$(Build)]... $(CPP)$s -shared \ \ -o $(BuildDir)/$(TargetFile) \ $(Objects) \ $(Libs) @echo Done. .SECONDEXPANSION: $(Objects): $(BuildDir)/%.o: $$(wildcard %.c*) mkdir -p $(@D) @echo $(WndStyle #else #define GViewFlags WndFlags #endif #if defined(__GTK_H__) struct LCaptureThread : public LThread, public LCancel { int view = -1; LString name; public: constexpr static int EventMs = 150; LCaptureThread(LView *v); ~LCaptureThread(); int Main(); }; #elif defined(MAC) extern OsThread LgiThreadInPaint; #elif defined(HAIKU) #include extern void *IsAttached(BView *v); #endif #define PAINT_VIRTUAL_CHILDREN 0 extern bool In_SetWindowPos; extern LMouse &lgi_adjust_click(LMouse &Info, LViewI *Wnd, bool Capturing = false, bool Debug = false); #ifdef __GTK_H__ extern LPoint GtkAbsPos(Gtk::GtkWidget *w); extern LRect GtkGetPos(Gtk::GtkWidget *w); #endif #if !WINNATIVE #include "lgi/common/ThreadEvent.h" class LPulseThread : public LThread, public LCancel { LView *View = NULL; LString ViewClass; int Length = 0; LThreadEvent Event; uint64_t WarnTs = 0; LString MakeName(LView *v, const char *Type) { LString s; s.Printf("LPulseThread.%s.%s", v->GetClass(), Type); return s; } public: LPulseThread(LView *view, int len) : View(view), LThread(MakeName(view, "Thread")), Event(MakeName(view, "Event")) { LAssert(View); Length = len; ViewClass = View->GetClass(); - // printf("PulseThread=%i, %s, %i\n", PulseThreadCount, View->GetClass(), Length); - Run(); } ~LPulseThread() { - Cancel(); View = NULL; Cancel(); Event.Signal(); WaitForExit(); - PulseThreadCount--; - LSleep(1); } int Main() { while (!IsCancelled() && LAppInst) { auto s = Event.Wait(Length); - if (IsCancelled() || s == LThreadEvent::WaitError) + if (!View || IsCancelled() || s == LThreadEvent::WaitError) break; - if (View && !View->PostEvent(M_PULSE)) - Cancel(); - auto r = View->PostEvent(M_PULSE, 0, 0, 50/*milliseconds*/); - #if 0 - if (!r) + if (!View->PostEvent(M_PULSE, 0, 0, 50/*milliseconds*/)) + { + auto now = LCurrentTime(); + if (now - WarnTs >= 5000) { - auto now = LCurrentTime(); - if (now - WarnTs >= 5000) - { - WarnTs = now; - printf("%s:%i - PulseThread::PostEvent failed for %p/%s.\n", _FL, View, ViewClass.Get()); - } + WarnTs = now; + printf("%s:%i - PulseThread::PostEvent failed for %p/%s.\n", _FL, View, ViewClass.Get()); } - #endif + } } return 0; } }; + #endif enum LViewFontType { /// The LView has a pointer to an externally owned font. GV_FontPtr, /// The LView owns the font object, and must free it. GV_FontOwned, /// The LApp's font cache owns the object. In this case, /// calling GetCssStyle on the LView will invalidate the /// font ptr causing it to be re-calculated. GV_FontCached, }; class LViewPrivate { public: // General LView *View = NULL; // Owning view LDragDropSource *DropSource = NULL; LDragDropTarget *DropTarget = NULL; bool IsThemed = false; int CtrlId = -1; int WantsPulse = -1; // Hierarchy LViewI *ParentI = NULL; LView *Parent = NULL; LViewI *Notify = NULL; // Size LPoint MinimumSize; // Font LFont *Font = NULL; LViewFontType FontOwnType = GV_FontPtr; // Style LAutoPtr Css; bool CssDirty = false; // This is set when 'Styles' changes, the next call to GetCss(...) parses // the styles into the 'Css' object. LString Styles; // Somewhat temporary object to store unparsed styles particular to // this view until runtime, where the view heirarchy is known. LString::Array Classes; // Event dispatch handle int SinkHnd = -1; // OS Specific #if WINNATIVE static OsView hPrevCapture; int WndStyle = 0; // Windows hWnd Style int WndExStyle = 0; // Windows hWnd ExStyle int WndDlgCode = 0; // Windows Dialog Code (WM_GETDLGCODE) LString WndClass; UINT_PTR TimerId = 0; HTHEME hTheme = NULL; #else // Cursor LPulseThread *PulseThread = NULL; LView *Popup = NULL; bool TabStop = false; bool WantsFocus = false; #if defined __GTK_H__ bool InPaint = false; bool GotOnCreate = false; #elif defined(MAC) && !defined(LGI_COCOA) static HIObjectClassRef BaseClass; #endif #endif #if defined(__GTK_H__) static LCaptureThread *CaptureThread; LMouse PrevMouse; #elif defined(MAC) #ifdef LGI_COCOA LString ClassName; bool AttachEvent; #elif defined LGI_CARBON EventHandlerRef DndHandler; LAutoString AcceptedDropFormat; #endif #elif defined(LGI_SDL) SDL_TimerID PulseId; int PulseLength = -1; #elif defined(HAIKU) BView *Hnd = NULL; LArray MsgQue; // For before the window is attached... #endif LViewPrivate(LView *view); ~LViewPrivate(); LView *GetParent() { if (Parent) return Parent; if (ParentI) return ParentI->GetGView(); return 0; } }; diff --git a/src/common/Lgi/ViewCommon.cpp b/src/common/Lgi/ViewCommon.cpp --- a/src/common/Lgi/ViewCommon.cpp +++ b/src/common/Lgi/ViewCommon.cpp @@ -1,2711 +1,2707 @@ /// \file /// \author Matthew Allen #ifdef LINUX #include #endif #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/TableLayout.h" #include "lgi/common/Button.h" #include "lgi/common/Css.h" #include "lgi/common/LgiRes.h" #include "lgi/common/EventTargetThread.h" #include "lgi/common/Popup.h" #include "lgi/common/CssTools.h" #include "ViewPriv.h" #if 0 #define DEBUG_CAPTURE(...) printf(__VA_ARGS__) #else #define DEBUG_CAPTURE(...) #endif -#if !WINNATIVE -int LPulseThread::PulseThreadCount = 0; -#endif - ////////////////////////////////////////////////////////////////////////////////////// // Helper LPoint lgi_view_offset(LViewI *v, bool Debug = false) { LPoint Offset; for (LViewI *p = v; p; p = p->GetParent()) { if (dynamic_cast(p)) break; LRect pos = p->GetPos(); LRect cli = p->GetClient(false); if (Debug) { const char *cls = p->GetClass(); LgiTrace(" Off[%s] += %i,%i = %i,%i (%s)\n", cls, pos.x1, pos.y1, Offset.x + pos.x1, Offset.y + pos.y1, cli.GetStr()); } Offset.x += pos.x1 + cli.x1; Offset.y += pos.y1 + cli.y1; } return Offset; } LMouse &lgi_adjust_click(LMouse &Info, LViewI *Wnd, bool Capturing, bool Debug) { static LMouse Temp; Temp = Info; if (Wnd) { if (Debug #if 0 || Capturing #endif ) LgiTrace("AdjustClick Target=%s -> Wnd=%s, Info=%i,%i\n", Info.Target?Info.Target->GetClass():"", Wnd?Wnd->GetClass():"", Info.x, Info.y); if (Temp.Target != Wnd) { if (Temp.Target) { auto *TargetWnd = Temp.Target->GetWindow(); auto *WndWnd = Wnd->GetWindow(); if (TargetWnd == WndWnd) { LPoint TargetOff = lgi_view_offset(Temp.Target, Debug); if (Debug) LgiTrace(" WndOffset:\n"); LPoint WndOffset = lgi_view_offset(Wnd, Debug); Temp.x += TargetOff.x - WndOffset.x; Temp.y += TargetOff.y - WndOffset.y; #if 0 LRect c = Wnd->GetClient(false); Temp.x -= c.x1; Temp.y -= c.y1; if (Debug) LgiTrace(" CliOff -= %i,%i\n", c.x1, c.y1); #endif Temp.Target = Wnd; } } else { LPoint Offset; Temp.Target = Wnd; if (Wnd->WindowVirtualOffset(&Offset)) { LRect c = Wnd->GetClient(false); Temp.x -= Offset.x + c.x1; Temp.y -= Offset.y + c.y1; } } } } LAssert(Temp.Target != NULL); return Temp; } ////////////////////////////////////////////////////////////////////////////////////// // LView class methods LViewI *LView::_Capturing = 0; LViewI *LView::_Over = 0; #if LGI_VIEW_HASH struct ViewTbl : public LMutex { typedef LHashTbl, int> T; private: T Map; public: ViewTbl() : Map(2000), LMutex("ViewTbl") { } T *Lock() { if (!LMutex::Lock(_FL)) return NULL; return ⤅ } } ViewTblInst; bool LView::LockHandler(LViewI *v, LView::LockOp Op) { ViewTbl::T *m = ViewTblInst.Lock(); if (!m) return false; int Ref = m->Find(v); bool Status = false; switch (Op) { case OpCreate: { if (Ref == 0) Status = m->Add(v, 1); else LAssert(!"Already exists?"); break; } case OpDelete: { if (Ref == 1) Status = m->Delete(v); else LAssert(!"Either locked or missing."); break; } case OpExists: { Status = Ref > 0; break; } } ViewTblInst.Unlock(); return Status; } #endif LView::LView(OsView view) { #ifdef _DEBUG _Debug = false; #endif d = new LViewPrivate(this); #ifdef LGI_SDL _View = this; #elif LGI_VIEW_HANDLE && !defined(HAIKU) _View = view; #endif Pos.ZOff(-1, -1); WndFlags = GWF_VISIBLE; #ifndef LGI_VIEW_HASH #error "LGI_VIEW_HASH needs to be defined" #elif LGI_VIEW_HASH LockHandler(this, OpCreate); // printf("Adding %p to hash\n", (LViewI*)this); #endif } LView::~LView() { if (d->SinkHnd >= 0) { LEventSinkMap::Dispatch.RemoveSink(this); d->SinkHnd = -1; } #if LGI_VIEW_HASH LockHandler(this, OpDelete); #endif for (unsigned i=0; iOwner == this) pu->Owner = NULL; } _Delete(); // printf("%p::~LView delete %p th=%u\n", this, d, GetCurrentThreadId()); DeleteObj(d); // printf("%p::~LView\n", this); } int LView::AddDispatch() { if (d->SinkHnd < 0) d->SinkHnd = LEventSinkMap::Dispatch.AddSink(this); return d->SinkHnd; } LString LView::CssStyles(const char *Set) { if (Set) { d->Styles = Set; d->CssDirty = true; } return d->Styles; } LString::Array *LView::CssClasses() { return &d->Classes; } LArray LView::IterateViews() { LArray a; for (auto c: Children) a.Add(c); return a; } bool LView::AddView(LViewI *v, int Where) { LAssert(!Children.HasItem(v)); bool Add = Children.Insert(v, Where); if (Add) { LView *gv = v->GetGView(); if (gv && gv->_Window != _Window) { LAssert(!_InLock); gv->_Window = _Window; } v->SetParent(this); v->OnAttach(); OnChildrenChanged(v, true); } return Add; } bool LView::DelView(LViewI *v) { bool Has = Children.HasItem(v); bool b = Children.Delete(v); if (Has) OnChildrenChanged(v, false); Has = Children.HasItem(v); LAssert(!Has); return b; } bool LView::HasView(LViewI *v) { return Children.HasItem(v); } OsWindow LView::WindowHandle() { auto w = GetWindow(); auto h = w ? w->WindowHandle() : NULL; return h; } LWindow *LView::GetWindow() { if (!_Window) { // Walk up parent list and find someone who has a window auto *w = d->GetParent(); for (; w; w = w->d ? w->d->GetParent() : NULL) { if (w->_Window) { LAssert(!_InLock); _Window = w->_Window; break; } } } return dynamic_cast(_Window); } bool LView::Lock(const char *file, int line, int TimeOut) { #ifdef HAIKU bool Debug = !Stricmp("LList", GetClass()); if (!d || !d->Hnd) { if (Debug) printf("%s:%i - no handle %p %p\n", _FL, d, d ? d->Hnd : NULL); return false; } if (d->Hnd->Parent() == NULL) { if (Debug) printf("%s:%p - Lock() no parent.\n", GetClass(), this); return true; } if (TimeOut >= 0) { auto r = d->Hnd->LockLooperWithTimeout(TimeOut * 1000); if (r == B_OK) { _InLock++; if (Debug) printf("%s:%p - Lock() cnt=%i par=%p.\n", GetClass(), this, _InLock, d->Hnd->Parent()); return true; } printf("%s:%i - Lock(%i) failed with %x.\n", _FL, TimeOut, r); return false; } auto r = d->Hnd->LockLooper(); if (r) { _InLock++; if (Debug) { auto w = WindowHandle(); printf("%s:%p - Lock() cnt=%i myThread=%i wndThread=%i.\n", GetClass(), this, _InLock, GetCurrentThreadId(), w ? w->Thread() : -1); } return true; } if (Debug) printf("%s:%i - Lock(%s:%i) failed.\n", _FL, file, line); return false; #else if (!_Window) GetWindow(); _InLock++; // LgiTrace("%s::%p Lock._InLock=%i %s:%i\n", GetClass(), this, _InLock, file, line); if (_Window && _Window->_Lock) { if (TimeOut < 0) { return _Window->_Lock->Lock(file, line); } else { return _Window->_Lock->LockWithTimeout(TimeOut, file, line); } } return true; #endif } void LView::Unlock() { #ifdef HAIKU if (!d || !d->Hnd) { printf("%s:%i - Unlock() error, no hnd.\n", _FL); return; } if (!d->Hnd->Parent()) { // printf("%s:%p - Unlock() no parent.\n", GetClass(), this); return; } if (_InLock > 0) { // printf("%s:%p - Calling UnlockLooper: %i.\n", GetClass(), this, _InLock); d->Hnd->UnlockLooper(); _InLock--; // printf("%s:%p - UnlockLooper done: %i.\n", GetClass(), this, _InLock); } else { printf("%s:%i - Unlock() without lock.\n", _FL); } #else if (_Window && _Window->_Lock) { _Window->_Lock->Unlock(); } _InLock--; // LgiTrace("%s::%p Unlock._InLock=%i\n", GetClass(), this, _InLock); #endif } void LView::OnMouseClick(LMouse &m) { } void LView::OnMouseEnter(LMouse &m) { } void LView::OnMouseExit(LMouse &m) { } void LView::OnMouseMove(LMouse &m) { } bool LView::OnMouseWheel(double Lines) { return false; } bool LView::OnKey(LKey &k) { return false; } void LView::OnAttach() { List::I it = Children.begin(); for (LViewI *v = *it; v; v = *++it) { if (!v->GetParent()) v->SetParent(this); } #if 0 // defined __GTK_H__ if (_View && !DropTarget()) { // If one of our parents is drop capable we need to set a dest here LViewI *p; for (p = GetParent(); p; p = p->GetParent()) { if (p->DropTarget()) { break; } } if (p) { Gtk::gtk_drag_dest_set( _View, (Gtk::GtkDestDefaults)0, NULL, 0, Gtk::GDK_ACTION_DEFAULT); // printf("%s:%i - Drop dest for '%s'\n", _FL, GetClass()); } else { Gtk::gtk_drag_dest_unset(_View); // printf("%s:%i - Not setting drop dest '%s'\n", _FL, GetClass()); } } #endif } void LView::OnCreate() { } void LView::OnDestroy() { } void LView::OnFocus(bool f) { // printf("%s::OnFocus(%i)\n", GetClass(), f); } void LView::OnPulse() { } void LView::OnPosChange() { } bool LView::OnRequestClose(bool OsShuttingDown) { return true; } int LView::OnHitTest(int x, int y) { return -1; } void LView::OnChildrenChanged(LViewI *Wnd, bool Attaching) { } void LView::OnPaint(LSurface *pDC) { auto c = GetClient(); LCssTools Tools(this); Tools.PaintContent(pDC, c); } int LView::OnNotify(LViewI *Ctrl, LNotification Data) { if (!Ctrl) return 0; if (Ctrl == (LViewI*)this && Data.Type == LNotifyActivate) { // Default activation is to focus the current control. Focus(true); } else if (d && d->Parent) { // default behaviour is just to pass the // notification up to the parent // FIXME: eventually we need to call the 'LNotification' parent fn... return d->Parent->OnNotify(Ctrl, Data); } return 0; } int LView::OnCommand(int Cmd, int Event, OsView Wnd) { return 0; } void LView::OnNcPaint(LSurface *pDC, LRect &r) { int Border = Sunken() || Raised() ? _BorderSize : 0; if (Border == 2) { LEdge e; if (Sunken()) e = Focus() ? EdgeWin7FocusSunken : DefaultSunkenEdge; else e = DefaultRaisedEdge; #if WINNATIVE if (d->IsThemed) DrawThemeBorder(pDC, r); else #endif LWideBorder(pDC, r, e); } else if (Border == 1) { LThinBorder(pDC, r, Sunken() ? DefaultSunkenEdge : DefaultRaisedEdge); } } #if LGI_COCOA || defined(__GTK_H__) /* uint64 nPaint = 0; uint64 PaintTime = 0; */ void LView::_Paint(LSurface *pDC, LPoint *Offset, LRect *Update) { /* uint64 StartTs = Update ? LCurrentTime() : 0; d->InPaint = true; */ // Create temp DC if needed... LAutoPtr Local; if (!pDC) { if (!Local.Reset(new LScreenDC(this))) return; pDC = Local; } #if 0 // This is useful for coverage checking pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif // Non-Client drawing LRect r; if (Offset) { r = Pos; r.Offset(Offset); } else { r = GetClient().ZeroTranslate(); } pDC->SetClient(&r); LRect zr1 = r.ZeroTranslate(), zr2 = zr1; OnNcPaint(pDC, zr1); pDC->SetClient(NULL); if (zr2 != zr1) { r.x1 -= zr2.x1 - zr1.x1; r.y1 -= zr2.y1 - zr1.y1; r.x2 -= zr2.x2 - zr1.x2; r.y2 -= zr2.y2 - zr1.y2; } LPoint o(r.x1, r.y1); // Origin of client // Paint this view's contents... pDC->SetClient(&r); #if 0 if (_Debug) { #if defined(__GTK_H__) Gtk::cairo_matrix_t matrix; cairo_get_matrix(pDC->Handle(), &matrix); double ex[4]; cairo_clip_extents(pDC->Handle(), ex+0, ex+1, ex+2, ex+3); ex[0] += matrix.x0; ex[1] += matrix.y0; ex[2] += matrix.x0; ex[3] += matrix.y0; LgiTrace("%s::_Paint, r=%s, clip=%g,%g,%g,%g - %g,%g\n", GetClass(), r.GetStr(), ex[0], ex[1], ex[2], ex[3], matrix.x0, matrix.y0); #elif LGI_COCOA auto Ctx = pDC->Handle(); CGAffineTransform t = CGContextGetCTM(Ctx); LRect cr = CGContextGetClipBoundingBox(Ctx); printf("%s::_Paint() pos=%s transform=%g,%g,%g,%g-%g,%g clip=%s r=%s\n", GetClass(), GetPos().GetStr(), t.a, t.b, t.c, t.d, t.tx, t.ty, cr.GetStr(), r.GetStr()); #endif } #endif OnPaint(pDC); pDC->SetClient(NULL); // Paint all the children... for (auto i : Children) { LView *w = i->GetGView(); if (w && w->Visible()) { if (!w->Pos.Valid()) continue; #if 0 if (w->_Debug) LgiTrace("%s::_Paint %i,%i\n", w->GetClass(), o.x, o.y); #endif w->_Paint(pDC, &o); } } } #else void LView::_Paint(LSurface *pDC, LPoint *Offset, LRect *Update) { // Create temp DC if needed... LAutoPtr Local; if (!pDC) { Local.Reset(new LScreenDC(this)); pDC = Local; } if (!pDC) { printf("%s:%i - No context to draw in.\n", _FL); return; } #if 0 // This is useful for coverage checking pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif bool HasClient = false; LRect r(0, 0, Pos.X()-1, Pos.Y()-1), Client; LPoint o; if (Offset) o = *Offset; #if WINNATIVE if (!_View) #endif { // Non-Client drawing Client = r; OnNcPaint(pDC, Client); HasClient = GetParent() && (Client != r); if (HasClient) { Client.Offset(o.x, o.y); pDC->SetClient(&Client); } } r.Offset(o.x, o.y); // Paint this view's contents if (Update) { LRect OldClip = pDC->ClipRgn(); pDC->ClipRgn(Update); OnPaint(pDC); pDC->ClipRgn(OldClip.Valid() ? &OldClip : NULL); } else { OnPaint(pDC); } #if PAINT_VIRTUAL_CHILDREN // Paint any virtual children for (auto i : Children) { LView *w = i->GetGView(); if (w && w->Visible()) { #if LGI_VIEW_HANDLE if (!w->Handle()) #endif { LRect p = w->GetPos(); p.Offset(o.x, o.y); if (HasClient) p.Offset(Client.x1 - r.x1, Client.y1 - r.y1); LPoint co(p.x1, p.y1); // LgiTrace("%s::_Paint %i,%i\n", w->GetClass(), p.x1, p.y1); pDC->SetClient(&p); w->_Paint(pDC, &co); pDC->SetClient(NULL); } } } #endif if (HasClient) pDC->SetClient(0); } #endif LViewI *LView::GetParent() { ThreadCheck(); return d ? d->Parent : NULL; } void LView::SetParent(LViewI *p) { ThreadCheck(); d->Parent = p ? p->GetGView() : NULL; d->ParentI = p; } void LView::SendNotify(LNotification note) { LViewI *n = d->Notify ? d->Notify : d->Parent; if (n) { if ( #if LGI_VIEW_HANDLE && !defined(HAIKU) !_View || #endif InThread()) { n->OnNotify(this, note); } else { // We are not in the same thread as the target window. So we post a message // across to the view. if (GetId() <= 0) { // We are going to generate a control Id to help the receiver of the // M_CHANGE message find out view later on. The reason for doing this // instead of sending a pointer to the object, is that the object // _could_ be deleted between the message being sent and being received. // Which would result in an invalid memory access on that object. LViewI *p = GetWindow(); if (!p) { // No window? Find the top most parent we can... p = this; while (p->GetParent()) p = p->GetParent(); } if (p) { // Give the control a valid ID int i; for (i=10; i<1000; i++) { if (!p->FindControl(i)) { printf("Giving the ctrl '%s' the id '%i' for SendNotify\n", GetClass(), i); SetId(i); break; } } } else { // Ok this is really bad... go random (better than nothing) SetId(5000 + LRand(2000)); } } LAssert(GetId() > 0); // We must have a valid ctrl ID at this point, otherwise // the receiver will never be able to find our object. // printf("Post M_CHANGE %i %i\n", GetId(), Data); n->PostEvent(M_CHANGE, (LMessage::Param) GetId(), (LMessage::Param) new LNotification(note)); } } } LViewI *LView::GetNotify() { ThreadCheck(); return d->Notify; } void LView::SetNotify(LViewI *p) { ThreadCheck(); d->Notify = p; } #define ADJ_LEFT 1 #define ADJ_RIGHT 2 #define ADJ_UP 3 #define ADJ_DOWN 4 int IsAdjacent(LRect &a, LRect &b) { if ( (a.x1 == b.x2 + 1) && !(a.y1 > b.y2 || a.y2 < b.y1)) { return ADJ_LEFT; } if ( (a.x2 == b.x1 - 1) && !(a.y1 > b.y2 || a.y2 < b.y1)) { return ADJ_RIGHT; } if ( (a.y1 == b.y2 + 1) && !(a.x1 > b.x2 || a.x2 < b.x1)) { return ADJ_UP; } if ( (a.y2 == b.y1 - 1) && !(a.x1 > b.x2 || a.x2 < b.x1)) { return ADJ_DOWN; } return 0; } LRect JoinAdjacent(LRect &a, LRect &b, int Adj) { LRect t; switch (Adj) { case ADJ_LEFT: case ADJ_RIGHT: { t.y1 = MAX(a.y1, b.y1); t.y2 = MIN(a.y2, b.y2); t.x1 = MIN(a.x1, b.x1); t.x2 = MAX(a.x2, b.x2); break; } case ADJ_UP: case ADJ_DOWN: { t.y1 = MIN(a.y1, b.y1); t.y2 = MAX(a.y2, b.y2); t.x1 = MAX(a.x1, b.x1); t.x2 = MIN(a.x2, b.x2); break; } } return t; } LRect *LView::FindLargest(LRegion &r) { ThreadCheck(); int Pixels = 0; LRect *Best = 0; static LRect Final; // do initial run through the list to find largest single region for (LRect *i = r.First(); i; i = r.Next()) { int Pix = i->X() * i->Y(); if (Pix > Pixels) { Pixels = Pix; Best = i; } } if (Best) { Final = *Best; Pixels = Final.X() * Final.Y(); int LastPixels = Pixels; LRect LastRgn = Final; int ThisPixels = Pixels; LRect ThisRgn = Final; LRegion TempList; for (LRect *i = r.First(); i; i = r.Next()) { TempList.Union(i); } TempList.Subtract(Best); do { LastPixels = ThisPixels; LastRgn = ThisRgn; // search for adjoining rectangles that maybe we can add for (LRect *i = TempList.First(); i; i = TempList.Next()) { int Adj = IsAdjacent(ThisRgn, *i); if (Adj) { LRect t = JoinAdjacent(ThisRgn, *i, Adj); int Pix = t.X() * t.Y(); if (Pix > ThisPixels) { ThisPixels = Pix; ThisRgn = t; TempList.Subtract(i); } } } } while (LastPixels < ThisPixels); Final = ThisRgn; } else return 0; return &Final; } LRect *LView::FindSmallestFit(LRegion &r, int Sx, int Sy) { ThreadCheck(); int X = 1000000; int Y = 1000000; LRect *Best = 0; for (LRect *i = r.First(); i; i = r.Next()) { if ((i->X() >= Sx && i->Y() >= Sy) && (i->X() < X || i->Y() < Y)) { X = i->X(); Y = i->Y(); Best = i; } } return Best; } LRect *LView::FindLargestEdge(LRegion &r, int Edge) { LRect *Best = 0; ThreadCheck(); for (LRect *i = r.First(); i; i = r.Next()) { if (!Best) { Best = i; } if ( ((Edge & GV_EDGE_TOP) && (i->y1 < Best->y1)) || ((Edge & GV_EDGE_RIGHT) && (i->x2 > Best->x2)) || ((Edge & GV_EDGE_BOTTOM) && (i->y2 > Best->y2)) || ((Edge & GV_EDGE_LEFT) && (i->x1 < Best->x1)) ) { Best = i; } if ( ( ((Edge & GV_EDGE_TOP) && (i->y1 == Best->y1)) || ((Edge & GV_EDGE_BOTTOM) && (i->y2 == Best->y2)) ) && ( i->X() > Best->X() ) ) { Best = i; } if ( ( ((Edge & GV_EDGE_RIGHT) && (i->x2 == Best->x2)) || ((Edge & GV_EDGE_LEFT) && (i->x1 == Best->x1)) ) && ( i->Y() > Best->Y() ) ) { Best = i; } } return Best; } LViewI *LView::FindReal(LPoint *Offset) { ThreadCheck(); if (Offset) { Offset->x = 0; Offset->y = 0; } #if !LGI_VIEW_HANDLE LViewI *w = GetWindow(); #endif LViewI *p = d->Parent; while (p && #if !LGI_VIEW_HANDLE p != w #else !p->Handle() #endif ) { if (Offset) { Offset->x += Pos.x1; Offset->y += Pos.y1; } p = p->GetParent(); } if (p && #if !LGI_VIEW_HANDLE p == w #else p->Handle() #endif ) { return p; } return NULL; } bool LView::HandleCapture(LView *Wnd, bool c) { ThreadCheck(); DEBUG_CAPTURE("%s::HandleCapture(%i)=%i\n", GetClass(), c, (int)(_Capturing == Wnd)); if (c) { if (_Capturing == Wnd) { DEBUG_CAPTURE(" %s already has capture\n", _Capturing?_Capturing->GetClass():0); } else { DEBUG_CAPTURE(" _Capturing=%s -> %s\n", _Capturing?_Capturing->GetClass():0, Wnd?Wnd->GetClass():0); _Capturing = Wnd; #if defined(__GTK_H__) if (d->CaptureThread) d->CaptureThread->Cancel(); d->CaptureThread = new LCaptureThread(this); #elif WINNATIVE LPoint Offset; LViewI *v = _Capturing->Handle() ? _Capturing : FindReal(&Offset); HWND h = v ? v->Handle() : NULL; if (h) SetCapture(h); else LAssert(0); #elif defined(LGI_SDL) #if SDL_VERSION_ATLEAST(2, 0, 4) SDL_CaptureMouse(SDL_TRUE); #else LAppInst->CaptureMouse(true); #endif #endif } } else if (_Capturing) { DEBUG_CAPTURE(" _Capturing=%s -> NULL\n", _Capturing?_Capturing->GetClass():0); _Capturing = NULL; #if defined(__GTK_H__) if (d->CaptureThread) { d->CaptureThread->Cancel(); d->CaptureThread = NULL; // It'll delete itself... } #elif WINNATIVE ReleaseCapture(); #elif defined(LGI_SDL) #if SDL_VERSION_ATLEAST(2, 0, 4) SDL_CaptureMouse(SDL_FALSE); #else LAppInst->CaptureMouse(false); #endif #endif } return true; } bool LView::IsCapturing() { // DEBUG_CAPTURE("%s::IsCapturing()=%p==%p==%i\n", GetClass(), _Capturing, this, (int)(_Capturing == this)); return _Capturing == this; } bool LView::Capture(bool c) { ThreadCheck(); DEBUG_CAPTURE("%s::Capture(%i)\n", GetClass(), c); return HandleCapture(this, c); } bool LView::Enabled() { ThreadCheck(); #if WINNATIVE if (_View) return IsWindowEnabled(_View) != 0; #endif return !TestFlag(GViewFlags, GWF_DISABLED); } void LView::Enabled(bool i) { ThreadCheck(); if (!i) SetFlag(GViewFlags, GWF_DISABLED); else ClearFlag(GViewFlags, GWF_DISABLED); #if LGI_VIEW_HANDLE && !defined(HAIKU) if (_View) { #if WINNATIVE EnableWindow(_View, i); #elif defined LGI_CARBON if (i) { OSStatus e = EnableControl(_View); if (e) printf("%s:%i - Error enabling control (%i)\n", _FL, (int)e); } else { OSStatus e = DisableControl(_View); if (e) printf("%s:%i - Error disabling control (%i)\n", _FL, (int)e); } #endif } #endif Invalidate(); } bool LView::Visible() { // This is a read only operation... which is kinda thread-safe... // ThreadCheck(); #if WINNATIVE if (_View) /* This takes into account all the parent windows as well... Which is kinda not what I want. I want this to reflect just this window. return IsWindowVisible(_View); */ return (GetWindowLong(_View, GWL_STYLE) & WS_VISIBLE) != 0; #endif return TestFlag(GViewFlags, GWF_VISIBLE); } void LView::Visible(bool v) { ThreadCheck(); if (v) SetFlag(GViewFlags, GWF_VISIBLE); else ClearFlag(GViewFlags, GWF_VISIBLE); #if defined(HAIKU) LLocker lck(d->Hnd, _FL); if (!IsAttached() || lck.Lock()) { const int attempts = 3; // printf("%s/%p:Visible(%i) hidden=%i\n", GetClass(), this, v, d->Hnd->IsHidden()); if (v) { bool parentHidden = false; for (auto p = d->Hnd->Parent(); p; p = p->Parent()) { if (p->IsHidden()) { parentHidden = true; break; } } if (!parentHidden) // Don't try and show if one of the parent's is hidden. { for (int i=0; iHnd->IsHidden(); i++) { // printf("\t%p Show\n", this); d->Hnd->Show(); } if (d->Hnd->IsHidden()) { printf("%s:%i - Failed to show %s.\n", _FL, GetClass()); for (auto p = d->Hnd->Parent(); p; p = p->Parent()) printf("\tparent: %s/%p ishidden=%i\n", p->Name(), p, p->IsHidden()); } } } else { for (int i=0; iHnd->IsHidden(); i++) { // printf("\t%p Hide\n", this); d->Hnd->Hide(); } if (!d->Hnd->IsHidden()) { printf("%s:%i - Failed to hide %s.\n", _FL, GetClass()); for (auto p = d->Hnd->Parent(); p; p = p->Parent()) printf("\tparent: %s/%p ishidden=%i\n", p->Name(), p, p->IsHidden()); } } // printf("\t%s/%p:Visible(%i) hidden=%i\n", GetClass(), this, v, d->Hnd->IsHidden()); } else LgiTrace("%s:%i - Can't lock.\n", _FL); #elif LGI_VIEW_HANDLE if (_View) { #if WINNATIVE ShowWindow(_View, (v) ? SW_SHOWNORMAL : SW_HIDE); #elif LGI_COCOA LAutoPool Pool; [_View.p setHidden:!v]; #elif LGI_CARBON Boolean is = HIViewIsVisible(_View); if (v != is) { OSErr e = HIViewSetVisible(_View, v); if (e) printf("%s:%i - HIViewSetVisible(%p,%i) failed with %i (class=%s)\n", _FL, _View, v, e, GetClass()); } #endif } else #endif { Invalidate(); } } bool LView::Focus() { ThreadCheck(); bool Has = false; #if defined(__GTK_H__) LWindow *w = GetWindow(); if (w) { bool Active = w->IsActive(); if (Active) Has = w->GetFocus() == static_cast(this); } #elif defined(HAIKU) LLocker lck(d->Hnd, _FL); if (lck.Lock()) { Has = d->Hnd->IsFocus(); lck.Unlock(); } #elif defined(WINNATIVE) if (_View) { HWND hFocus = GetFocus(); Has = hFocus == _View; } #elif LGI_COCOA Has = TestFlag(WndFlags, GWF_FOCUS); #elif LGI_CARBON LWindow *w = GetWindow(); if (w) { ControlRef Cur; OSErr e = GetKeyboardFocus(w->WindowHandle(), &Cur); if (e) LgiTrace("%s:%i - GetKeyboardFocus failed with %i\n", _FL, e); else Has = (Cur == _View); } #endif #if !LGI_CARBON if (Has) SetFlag(WndFlags, GWF_FOCUS); else ClearFlag(WndFlags, GWF_FOCUS); #endif return Has; } void LView::Focus(bool i) { ThreadCheck(); if (i) SetFlag(WndFlags, GWF_FOCUS); else ClearFlag(WndFlags, GWF_FOCUS); auto *Wnd = GetWindow(); if (Wnd) { Wnd->SetFocus(this, i ? LWindow::GainFocus : LWindow::LoseFocus); } #if LGI_VIEW_HANDLE && !defined(HAIKU) if (_View) #endif { #if defined(HAIKU) _Focus(i); #elif defined(LGI_SDL) || defined(__GTK_H__) // Nop: Focus is all handled by Lgi's LWindow class. #elif WINNATIVE if (i) { HWND hCur = GetFocus(); if (hCur != _View) { if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus %p (%-30s)\n", _FL, Handle(), Name()); } SetFocus(_View); } } else { if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus(%p)\n", _FL, GetDesktopWindow()); } SetFocus(GetDesktopWindow()); } #elif defined LGI_CARBON LViewI *Wnd = GetWindow(); if (Wnd && i) { OSErr e = SetKeyboardFocus(Wnd->WindowHandle(), _View, 1); if (e) { // e = SetKeyboardFocus(Wnd->WindowHandle(), _View, kControlFocusNextPart); // if (e) { HIViewRef p = HIViewGetSuperview(_View); // errCouldntSetFocus printf("%s:%i - SetKeyboardFocus failed: %i (%s, %p)\n", _FL, e, GetClass(), p); } } // else printf("%s:%i - SetFocus v=%p(%s)\n", _FL, _View, GetClass()); } else printf("%s:%i - no window?\n", _FL); #endif } } LDragDropSource *LView::DropSource(LDragDropSource *Set) { if (Set) d->DropSource = Set; return d->DropSource; } LDragDropTarget *LView::DropTarget(LDragDropTarget *Set) { if (Set) d->DropTarget = Set; return d->DropTarget; } #if defined LGI_CARBON extern pascal OSStatus LgiViewDndHandler(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData); #endif #if defined __GTK_H__ // Recursively add drag dest to all view and all children bool GtkAddDragDest(LViewI *v, bool IsTarget) { if (!v) return false; LWindow *w = v->GetWindow(); if (!w) return false; auto wid = GtkCast(w->WindowHandle(), gtk_widget, GtkWidget); if (IsTarget) { Gtk::gtk_drag_dest_set( wid, (Gtk::GtkDestDefaults)0, NULL, 0, Gtk::GDK_ACTION_DEFAULT); } else { Gtk::gtk_drag_dest_unset(wid); } for (LViewI *c: v->IterateViews()) GtkAddDragDest(c, IsTarget); return true; } #endif bool LView::DropTarget(bool t) { ThreadCheck(); bool Status = false; if (t) SetFlag(GViewFlags, GWF_DROP_TARGET); else ClearFlag(GViewFlags, GWF_DROP_TARGET); #if WINNATIVE if (_View) { if (t) { if (!d->DropTarget) DragAcceptFiles(_View, t); else Status = RegisterDragDrop(_View, (IDropTarget*) d->DropTarget) == S_OK; } else { if (_View && d->DropTarget) Status = RevokeDragDrop(_View) == S_OK; } } #elif defined MAC && !defined(LGI_SDL) LWindow *Wnd = dynamic_cast(GetWindow()); if (Wnd) { Wnd->SetDragHandlers(t); if (!d->DropTarget) d->DropTarget = t ? Wnd : 0; } #if LGI_COCOA LWindow *w = GetWindow(); if (w) { OsWindow h = w->WindowHandle(); if (h) { NSMutableArray *a = [[NSMutableArray alloc] init]; if (a) { [a addObject:(NSString*)kUTTypeItem]; for (id item in NSFilePromiseReceiver.readableDraggedTypes) [a addObject:item]; [h.p.contentView registerForDraggedTypes:a]; [a release]; } } } #elif LGI_CARBON if (t) { static EventTypeSpec DragEvents[] = { { kEventClassControl, kEventControlDragEnter }, { kEventClassControl, kEventControlDragWithin }, { kEventClassControl, kEventControlDragLeave }, { kEventClassControl, kEventControlDragReceive }, }; if (!d->DndHandler) { OSStatus e = ::InstallControlEventHandler( _View, NewEventHandlerUPP(LgiViewDndHandler), GetEventTypeCount(DragEvents), DragEvents, (void*)this, &d->DndHandler); if (e) LgiTrace("%s:%i - InstallEventHandler failed (%i)\n", _FL, e); } SetControlDragTrackingEnabled(_View, true); } else { SetControlDragTrackingEnabled(_View, false); } #endif #elif defined __GTK_H__ Status = GtkAddDragDest(this, t); if (Status && !d->DropTarget) d->DropTarget = t ? GetWindow() : 0; #endif return Status; } bool LView::Sunken() { // ThreadCheck(); #if WINNATIVE return TestFlag(d->WndExStyle, WS_EX_CLIENTEDGE); #else return TestFlag(GViewFlags, GWF_SUNKEN); #endif } void LView::Sunken(bool i) { ThreadCheck(); #if WINNATIVE if (i) SetFlag(d->WndExStyle, WS_EX_CLIENTEDGE); else ClearFlag(d->WndExStyle, WS_EX_CLIENTEDGE); if (_View) SetWindowLong(_View, GWL_EXSTYLE, d->WndExStyle); #else if (i) SetFlag(GViewFlags, GWF_SUNKEN); else ClearFlag(GViewFlags, GWF_SUNKEN); #endif if (i) { if (!_BorderSize) _BorderSize = 2; } else _BorderSize = 0; } bool LView::Flat() { // ThreadCheck(); #if WINNATIVE return !TestFlag(d->WndExStyle, WS_EX_CLIENTEDGE) && !TestFlag(d->WndExStyle, WS_EX_WINDOWEDGE); #else return !TestFlag(GViewFlags, GWF_SUNKEN) && !TestFlag(GViewFlags, GWF_RAISED); #endif } void LView::Flat(bool i) { ThreadCheck(); #if WINNATIVE ClearFlag(d->WndExStyle, (WS_EX_CLIENTEDGE|WS_EX_WINDOWEDGE)); #else ClearFlag(GViewFlags, (GWF_RAISED|GWF_SUNKEN)); #endif } bool LView::Raised() { // ThreadCheck(); #if WINNATIVE return TestFlag(d->WndExStyle, WS_EX_WINDOWEDGE); #else return TestFlag(GViewFlags, GWF_RAISED); #endif } void LView::Raised(bool i) { ThreadCheck(); #if WINNATIVE if (i) SetFlag(d->WndExStyle, WS_EX_WINDOWEDGE); else ClearFlag(d->WndExStyle, WS_EX_WINDOWEDGE); #else if (i) SetFlag(GViewFlags, GWF_RAISED); else ClearFlag(GViewFlags, GWF_RAISED); #endif if (i) { if (!!_BorderSize) _BorderSize = 2; } else _BorderSize = 0; } int LView::GetId() { // This is needed by SendNotify function which is thread safe. // So no thread safety check here. return d->CtrlId; } void LView::SetId(int i) { // This is needed by SendNotify function which is thread safe. // So no thread safety check here. d->CtrlId = i; #if WINNATIVE if (_View) SetWindowLong(_View, GWL_ID, d->CtrlId); #elif defined __GTK_H__ #elif defined MAC #endif } bool LView::GetTabStop() { ThreadCheck(); #if WINNATIVE return TestFlag(d->WndStyle, WS_TABSTOP); #else return d->TabStop; #endif } void LView::SetTabStop(bool b) { ThreadCheck(); #if WINNATIVE if (b) SetFlag(d->WndStyle, WS_TABSTOP); else ClearFlag(d->WndStyle, WS_TABSTOP); #else d->TabStop = b; #endif } int64 LView::GetCtrlValue(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); return (w) ? w->Value() : 0; } void LView::SetCtrlValue(int Id, int64 i) { ThreadCheck(); LViewI *w = FindControl(Id); if (w) w->Value(i); } const char *LView::GetCtrlName(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); return (w) ? w->Name() : 0; } void LView::SetCtrlName(int Id, const char *s) { if (!IsAttached() || InThread()) { if (auto w = FindControl(Id)) w->Name(s); } else { PostEvent( M_SET_CTRL_NAME, (LMessage::Param)Id, (LMessage::Param)new LString(s)); } } bool LView::GetCtrlEnabled(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); return (w) ? w->Enabled() : 0; } void LView::SetCtrlEnabled(int Id, bool Enabled) { if (!IsAttached() || InThread()) { if (auto w = FindControl(Id)) w->Enabled(Enabled); } else { PostEvent( M_SET_CTRL_ENABLE, (LMessage::Param)Id, (LMessage::Param)Enabled); } } bool LView::GetCtrlVisible(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); if (!w) LgiTrace("%s:%i - Ctrl %i not found.\n", _FL, Id); return (w) ? w->Visible() : 0; } void LView::SetCtrlVisible(int Id, bool v) { if (!IsAttached() || InThread()) { if (auto w = FindControl(Id)) w->Visible(v); } else { PostEvent( M_SET_CTRL_VISIBLE, (LMessage::Param)Id, (LMessage::Param)v); } } bool LView::AttachChildren() { for (auto c: Children) { bool a = c->IsAttached(); if (!a) { if (!c->Attach(this)) { LgiTrace("%s:%i - failed to attach %s\n", _FL, c->GetClass()); return false; } } } return true; } LFont *LView::GetFont() { if (!d->Font && d->Css && LResources::GetLoadStyles()) { LFontCache *fc = LAppInst->GetFontCache(); if (fc) { LFont *f = fc->GetFont(d->Css); if (f) { if (d->FontOwnType == GV_FontOwned) DeleteObj(d->Font); d->Font = f; d->FontOwnType = GV_FontCached; } } } return d->Font ? d->Font : LSysFont; } void LView::SetFont(LFont *Font, bool OwnIt) { bool Change = d->Font != Font; if (Change) { if (d->FontOwnType == GV_FontOwned) { LAssert(d->Font != LSysFont); DeleteObj(d->Font); } d->FontOwnType = OwnIt ? GV_FontOwned : GV_FontPtr; d->Font = Font; #if WINNATIVE if (_View) SendMessage(_View, WM_SETFONT, (WPARAM) (Font ? Font->Handle() : 0), 0); #endif for (LViewI *p = GetParent(); p; p = p->GetParent()) { LTableLayout *Tl = dynamic_cast(p); if (Tl) { Tl->InvalidateLayout(); break; } } Invalidate(); } } bool LView::IsOver(LMouse &m) { return (m.x >= 0) && (m.y >= 0) && (m.x < Pos.X()) && (m.y < Pos.Y()); } bool LView::WindowVirtualOffset(LPoint *Offset) { bool Status = false; if (Offset) { Offset->x = 0; Offset->y = 0; for (LViewI *Wnd = this; Wnd; Wnd = Wnd->GetParent()) { #if !LGI_VIEW_HANDLE auto IsWnd = dynamic_cast(Wnd); if (!IsWnd) #else if (!Wnd->Handle()) #endif { LRect r = Wnd->GetPos(); LViewI *Par = Wnd->GetParent(); if (Par) { LRect c = Par->GetClient(false); Offset->x += r.x1 + c.x1; Offset->y += r.y1 + c.y1; } else { Offset->x += r.x1; Offset->y += r.y1; } Status = true; } else break; } } return Status; } LString _ViewDesc(LViewI *v) { LString s; s.Printf("%s/%s/%i", v->GetClass(), v->Name(), v->GetId()); return s; } LViewI *LView::WindowFromPoint(int x, int y, int DebugDepth) { char Tabs[64]; if (DebugDepth) { memset(Tabs, 9, DebugDepth); Tabs[DebugDepth] = 0; LgiTrace("%s%s %i\n", Tabs, _ViewDesc(this).Get(), Children.Length()); } // We iterate over the child in reverse order because if they overlap the // end of the list is on "top". So they should get the click or whatever // before the the lower windows. auto it = Children.rbegin(); int n = (int)Children.Length() - 1; for (LViewI *c = *it; c; c = *--it) { LRect CPos = c->GetPos(); if (CPos.Overlap(x, y) && c->Visible()) { LRect CClient; CClient = c->GetClient(false); int Ox = CPos.x1 + CClient.x1; int Oy = CPos.y1 + CClient.y1; if (DebugDepth) { LgiTrace("%s[%i] %s Pos=%s Client=%s m(%i,%i)->(%i,%i)\n", Tabs, n--, _ViewDesc(c).Get(), CPos.GetStr(), CClient.GetStr(), x, y, x - Ox, y - Oy); } LViewI *Child = c->WindowFromPoint(x - Ox, y - Oy, DebugDepth ? DebugDepth + 1 : 0); if (Child) return Child; } else if (DebugDepth) { LgiTrace("%s[%i] MISSED %s Pos=%s m(%i,%i)\n", Tabs, n--, _ViewDesc(c).Get(), CPos.GetStr(), x, y); } } if (x >= 0 && y >= 0 && x < Pos.X() && y < Pos.Y()) { return this; } return NULL; } LColour LView::StyleColour(int CssPropType, LColour Default, int Depth) { LColour c = Default; if ((CssPropType >> 8) == LCss::TypeColor) { LViewI *v = this; for (int i=0; v && iGetParent()) { auto Style = v->GetCss(); if (Style) { auto Colour = (LCss::ColorDef*) Style->PropAddress((LCss::PropType)CssPropType); if (Colour) { if (Colour->Type == LCss::ColorRgb) { c.Set(Colour->Rgb32, 32); break; } else if (Colour->Type == LCss::ColorTransparent) { c.Empty(); break; } } } if (dynamic_cast(v) || dynamic_cast(v)) break; } } return c; } bool LView::InThread() { #if WINNATIVE HWND Hnd = _View; for (LViewI *p = GetParent(); p && !Hnd; p = p->GetParent()) { Hnd = p->Handle(); } auto CurThreadId = GetCurrentThreadId(); auto GuiThreadId = LAppInst->GetGuiThreadId(); DWORD ViewThread = Hnd ? GetWindowThreadProcessId(Hnd, NULL) : GuiThreadId; return CurThreadId == ViewThread; #elif defined(HAIKU) return true; #else OsThreadId Me = GetCurrentThreadId(); OsThreadId Gui = LAppInst ? LAppInst->GetGuiThreadId() : 0; #if 0 if (Gui != Me) LgiTrace("%s:%i - Out of thread:" #ifdef LGI_COCOA "%llx, %llx" #else "%x, %x" #endif "\n", _FL, Gui, Me); #endif return Gui == Me; #endif } bool LView::PostEvent(int Cmd, LMessage::Param a, LMessage::Param b, int64_t timeoutMs) { #ifdef LGI_SDL return LPostEvent(this, Cmd, a, b); #elif defined(HAIKU) if (!d || !d->Hnd) { // printf("%s:%i - Bad pointers %p %p\n", _FL, d, d ? d->Hnd : NULL); return false; } BWindow *bWnd = NULL; LWindow *wnd = dynamic_cast(this); if (wnd) { bWnd = wnd->WindowHandle(); } else { // Look for an attached view to lock... for (LViewI *v = this; v; v = v->GetParent()) { auto vhnd = v->Handle(); if (vhnd && ::IsAttached(vhnd)) { bWnd = vhnd->Window(); break; } } } BMessage m(Cmd); auto r = m.AddInt64(LMessage::PropA, a); if (r != B_OK) printf("%s:%i - AddUInt64 failed.\n", _FL); r = m.AddInt64(LMessage::PropB, b); if (r != B_OK) printf("%s:%i - AddUInt64 failed.\n", _FL); r = m.AddPointer(LMessage::PropView, this); if (r != B_OK) printf("%s:%i - AddPointer failed.\n", _FL); if (bWnd) { r = bWnd->PostMessage(&m); if (r != B_OK) printf("%s:%i - PostMessage failed.\n", _FL); return r == B_OK; } // Not attached yet... d->MsgQue.Add(new BMessage(m)); // printf("%s:%i - PostEvent.MsgQue=%i\n", _FL, (int)d->MsgQue.Length()); return true; #elif WINNATIVE if (!_View) return false; BOOL Res = ::PostMessage(_View, Cmd, a, b); if (!Res) { auto Err = GetLastError(); int asd=0; } return Res != 0; #elif !LGI_VIEW_HANDLE return LAppInst->PostEvent(this, Cmd, a, b); #else if (_View) return LPostEvent(_View, Cmd, a, b); LAssert(0); return false; #endif } bool LView::Invalidate(LRegion *r, bool Repaint, bool NonClient) { if (r) { for (int i=0; iLength(); i++) { bool Last = i == r->Length()-1; Invalidate((*r)[i], Last ? Repaint : false, NonClient); } return true; } return false; } LButton *FindDefault(LViewI *w) { LButton *But = 0; for (auto c: w->IterateViews()) { But = dynamic_cast(c); if (But && But->Default()) { break; } But = FindDefault(c); if (But) { break; } } return But; } bool LView::Name(const char *n) { LBase::Name(n); #if LGI_VIEW_HANDLE && !defined(HAIKU) if (_View) { #if WINNATIVE auto Temp = LBase::NameW(); SetWindowTextW(_View, Temp ? Temp : L""); #endif } #endif Invalidate(); return true; } const char *LView::Name() { #if WINNATIVE if (_View) { LView::NameW(); } #endif return LBase::Name(); } bool LView::NameW(const char16 *n) { LBase::NameW(n); #if WINNATIVE if (_View && n) { auto Txt = LBase::NameW(); SetWindowTextW(_View, Txt); } #endif Invalidate(); return true; } const char16 *LView::NameW() { #if WINNATIVE if (_View) { int Length = GetWindowTextLengthW(_View); if (Length > 0) { char16 *Buf = new char16[Length+1]; if (Buf) { Buf[0] = 0; int Chars = GetWindowTextW(_View, Buf, Length+1); Buf[Chars] = 0; LBase::NameW(Buf); } DeleteArray(Buf); } else { LBase::NameW(0); } } #endif return LBase::NameW(); } LViewI *LView::FindControl(int Id) { LAssert(Id != -1); if (GetId() == Id) { return this; } for (auto c : Children) { LViewI *Ctrl = c->FindControl(Id); if (Ctrl) { return Ctrl; } } return 0; } LPoint LView::GetMinimumSize() { return d->MinimumSize; } void LView::SetMinimumSize(LPoint Size) { d->MinimumSize = Size; bool Change = false; LRect p = Pos; if (X() < d->MinimumSize.x) { p.x2 = p.x1 + d->MinimumSize.x - 1; Change = true; } if (Y() < d->MinimumSize.y) { p.y2 = p.y1 + d->MinimumSize.y - 1; Change = true; } if (Change) { SetPos(p); } } bool LView::SetColour(LColour &c, bool Fore) { LCss *css = GetCss(true); if (!css) return false; if (Fore) { if (c.IsValid()) css->Color(LCss::ColorDef(LCss::ColorRgb, c.c32())); else css->DeleteProp(LCss::PropColor); } else { if (c.IsValid()) css->BackgroundColor(LCss::ColorDef(LCss::ColorRgb, c.c32())); else css->DeleteProp(LCss::PropBackgroundColor); } return true; } /* bool LView::SetCssStyle(const char *CssStyle) { if (!d->Css && !d->Css.Reset(new LCss)) return false; const char *Defs = CssStyle; bool b = d->Css->Parse(Defs, LCss::ParseRelaxed); if (b && d->FontOwnType == GV_FontCached) { d->Font = NULL; d->FontOwnType = GV_FontPtr; } return b; } */ void LView::SetCss(LCss *css) { d->Css.Reset(css); } LCss *LView::GetCss(bool Create) { if (Create && !d->Css) d->Css.Reset(new LCss); if (d->CssDirty && d->Css) { const char *Defs = d->Styles; if (d->Css->Parse(Defs, LCss::ParseRelaxed)) d->CssDirty = false; } return d->Css; } LPoint &LView::GetWindowBorderSize() { static LPoint s; ZeroObj(s); #if WINNATIVE if (_View) { RECT Wnd, Client; GetWindowRect(Handle(), &Wnd); GetClientRect(Handle(), &Client); s.x = (Wnd.right-Wnd.left) - (Client.right-Client.left); s.y = (Wnd.bottom-Wnd.top) - (Client.bottom-Client.top); } #elif defined __GTK_H__ #elif defined MAC s.x = 0; s.y = 22; #endif return s; } #ifdef _DEBUG #if defined(LGI_CARBON) void DumpHiview(HIViewRef v, int Depth = 0) { char Sp[256]; memset(Sp, ' ', Depth << 2); Sp[Depth<<2] = 0; printf("%sHIView=%p", Sp, v); if (v) { Boolean vis = HIViewIsVisible(v); Boolean en = HIViewIsEnabled(v, NULL); HIRect pos; HIViewGetFrame(v, &pos); char cls[128]; ZeroObj(cls); GetControlProperty(v, 'meme', 'clas', sizeof(cls), NULL, cls); printf(" vis=%i en=%i pos=%g,%g-%g,%g cls=%s", vis, en, pos.origin.x, pos.origin.y, pos.size.width, pos.size.height, cls); } printf("\n"); for (HIViewRef c = HIViewGetFirstSubview(v); c; c = HIViewGetNextView(c)) { DumpHiview(c, Depth + 1); } } #elif defined(__GTK_H__) void DumpGtk(Gtk::GtkWidget *w, Gtk::gpointer Depth = NULL) { using namespace Gtk; if (!w) return; char Sp[65] = {0}; if (Depth) memset(Sp, ' ', *((int*)Depth)*2); auto *Obj = G_OBJECT(w); LViewI *View = (LViewI*) g_object_get_data(Obj, "LViewI"); GtkAllocation a; gtk_widget_get_allocation(w, &a); LgiTrace("%s%p(%s) = %i,%i-%i,%i\n", Sp, w, View?View->GetClass():G_OBJECT_TYPE_NAME(Obj), a.x, a.y, a.width, a.height); if (GTK_IS_CONTAINER(w)) { auto *c = GTK_CONTAINER(w); if (c) { int Next = Depth ? *((int*)Depth)+1 : 1; gtk_container_foreach(c, DumpGtk, &Next); } } } #elif defined(HAIKU) || defined(WINDOWS) template void _Dump(int Depth, T v) { LString Sp, Name; Sp.Length(Depth<<1); memset(Sp.Get(), ' ', Depth<<1); Sp.Get()[Depth<<1] = 0; #if defined(HAIKU) LRect Frame = v->Frame(); Name = v->Name(); bool IsHidden = v->IsHidden(); #else RECT rc; GetWindowRect(v, &rc); LRect Frame = rc; wchar_t txt[256]; GetWindowTextW(v, txt, CountOf(txt)); Name = txt; bool IsHidden = !(GetWindowLong(v, GWL_STYLE) & WS_VISIBLE); #endif LgiTrace("%s%p::%s frame=%s vis=%i\n", Sp.Get(), v, Name.Get(), Frame.GetStr(), !IsHidden); #if defined(HAIKU) for (int32 i=0; iCountChildren(); i++) _Dump(Depth + 1, v->ChildAt(i)); #else for (auto h = GetWindow(v, GW_CHILD); h; h = GetWindow(h, GW_HWNDNEXT)) _Dump(Depth + 1, h); #endif } #endif void LView::_Dump(int Depth) { char Sp[65] = {0}; memset(Sp, ' ', Depth*2); #if 0 char s[256]; sprintf_s(s, sizeof(s), "%s%p::%s %s (_View=%p)\n", Sp, this, GetClass(), GetPos().GetStr(), _View); LgiTrace(s); List::I i = Children.Start(); for (LViewI *c = *i; c; c = *++i) { LView *v = c->GetGView(); if (v) v->_Dump(Depth+1); } #elif defined(LGI_CARBON) DumpHiview(_View); #elif defined(__GTK_H__) // DumpGtk(_View); #else #if defined(HAIKU) LLocker lck(WindowHandle(), _FL); if (lck.Lock()) #endif ::_Dump(0, WindowHandle()); #endif } #endif //////////////////////////////////////////////////////////////////////////////////////////////////// static LArray *AllFactories = NULL; #if defined(WIN32) static HANDLE FactoryEvent; #else static pthread_once_t FactoryOnce = PTHREAD_ONCE_INIT; static void GFactoryInitFactories() { AllFactories = new LArray; } #endif LViewFactory::LViewFactory() { #if defined(WIN32) char16 Name[64]; swprintf_s(Name, CountOf(Name), L"LgiFactoryEvent.%i", GetCurrentProcessId()); HANDLE h = CreateEventW(NULL, false, false, Name); DWORD err = GetLastError(); if (err != ERROR_ALREADY_EXISTS) { FactoryEvent = h; AllFactories = new LArray; } else { LAssert(AllFactories != NULL); } #else pthread_once(&FactoryOnce, GFactoryInitFactories); #endif if (AllFactories) AllFactories->Add(this); } LViewFactory::~LViewFactory() { if (AllFactories) { AllFactories->Delete(this); if (AllFactories->Length() == 0) { DeleteObj(AllFactories); #if defined(WIN32) CloseHandle(FactoryEvent); #endif } } } LView *LViewFactory::Create(const char *Class, LRect *Pos, const char *Text) { if (ValidStr(Class) && AllFactories) { for (int i=0; iLength(); i++) { LView *v = (*AllFactories)[i]->NewView(Class, Pos, Text); if (v) { return v; } } } return 0; } #ifdef _DEBUG #if defined(__GTK_H__) using namespace Gtk; #include "LgiWidget.h" #endif void LView::Debug() { _Debug = true; #if defined LGI_COCOA d->ClassName = GetClass(); #endif } #endif diff --git a/src/common/Net/Net.cpp b/src/common/Net/Net.cpp --- a/src/common/Net/Net.cpp +++ b/src/common/Net/Net.cpp @@ -1,1856 +1,1856 @@ /*hdr ** FILE: INet.cpp ** AUTHOR: Matthew Allen ** DATE: 28/5/98 ** DESCRIPTION: Internet sockets ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #define _WINSOCK_DEPRECATED_NO_WARNINGS 1 #if defined(LINUX) #include #include #include #include #elif defined(MAC) #include #include #include #include #include #endif #include #include "lgi/common/File.h" #include "lgi/common/Net.h" #include "lgi/common/LgiString.h" #include "lgi/common/LgiCommon.h" #include "LgiOsClasses.h" #include "lgi/common/RegKey.h" #define USE_BSD_SOCKETS 1 #define DEBUG_CONNECT 0 #define ETIMEOUT 400 #define PROTO_UDP 0x100 #define PROTO_BROADCAST 0x200 #if defined WIN32 #include #include #include #include typedef HOSTENT HostEnt; typedef int socklen_t; typedef unsigned long in_addr_t; typedef BOOL option_t; #define LPrintSock "%I64x" #define MSG_NOSIGNAL 0 #ifndef EWOULDBLOCK #define EWOULDBLOCK WSAEWOULDBLOCK #endif #ifndef EISCONN #define EISCONN WSAEISCONN #endif #define OsAddr S_un.S_addr #elif defined POSIX #include #include #include #include #include #include #include #include #include #include #define SOCKET_ERROR -1 #define LPrintSock "%x" typedef hostent HostEnt; typedef int option_t; #define OsAddr s_addr #endif #include "lgi/common/LgiNetInc.h" /////////////////////////////////////////////////////////////////////////////// #ifdef WIN32 static bool SocketsOpen = false; #endif bool StartNetworkStack() { #ifdef WIN32 #ifndef WINSOCK_VERSION #define WINSOCK_VERSION MAKEWORD(2,2) #endif if (!SocketsOpen) { // Start sockets WSADATA WsaData; int Result = WSAStartup(WINSOCK_VERSION, &WsaData); if (!Result) { if (WsaData.wVersion == WINSOCK_VERSION) { SocketsOpen = Result == 0; } else { WSACleanup(); return false; } } } #endif return true; } void StopNetworkStack() { #ifdef WIN32 if (SocketsOpen) { WSACleanup(); SocketsOpen = false; } #endif } #ifdef WIN32 #include "..\..\Win\INet\MibAccess.h" #include #pragma comment(lib, "iphlpapi.lib") #endif bool LSocket::EnumInterfaces(LArray &Out) { bool Status = false; StartNetworkStack(); #ifdef WIN32 #if 0 PMIB_IF_TABLE2 Tbl; SecureZeroMemory(&Tbl, sizeof(Tbl)); auto r = GetIfTable2(&Tbl); for (size_t i=0; iNumEntries; i++) { auto &e = Tbl->Table[i]; WCHAR w[256] = {0}; r = ConvertInterfaceLuidToNameW(&e.InterfaceLuid, w, 256); MIB_UNICASTIPADDRESS_ROW Addr; InitializeUnicastIpAddressEntry(&Addr); Addr.Address.si_family = AF_INET; Addr.InterfaceLuid = e.InterfaceLuid; Addr.InterfaceIndex = e.InterfaceIndex; r = GetUnicastIpAddressEntry(&Addr); if (r == NO_ERROR) { int asd=0; } } FreeMibTable(Tbl); #else MibII m; m.Init(); MibInterface Intf[16] = {0}; int Count = m.GetInterfaces(Intf, CountOf(Intf)); if (Count) { for (int i=0; iifa_next) { if (a->ifa_addr && a->ifa_addr->sa_family == AF_INET) { sockaddr_in *in = (sockaddr_in*)a->ifa_addr; sockaddr_in *mask = (sockaddr_in*)a->ifa_netmask; auto &Intf = Out.New(); Intf.Ip4 = ntohl(in->sin_addr.s_addr); Intf.Netmask4 = ntohl(mask->sin_addr.s_addr); Intf.Name = a->ifa_name; Status = true; } } freeifaddrs(addrs); } #endif return Status; } //////////////////////////////////////////////////////////////////////////////////////////////// class LSocketImplPrivate : public LCancel { public: // Data int Blocking : 1; int NoDelay : 1; int Udp : 1; int Broadcast : 1; int LogType = NET_LOG_NONE; LString LogFile; int Timeout = -1; OsSocket Socket = INVALID_SOCKET; int LastError = 0; LCancel *Cancel = NULL; LString ErrStr; LSocketImplPrivate() { Cancel = this; Blocking = true; NoDelay = false; Udp = false; Broadcast = false; } ~LSocketImplPrivate() { } bool Select(int TimeoutMs, bool Read) { // Assign to local var to avoid a thread changing it // on us between the validity check and the select. // Which is important because a socket value of -1 // (ie invalid) will crash the FD_SET macro. OsSocket s = Socket; if (ValidSocket(s) && !Cancel->IsCancelled()) { struct timeval t = {TimeoutMs / 1000, (TimeoutMs % 1000) * 1000}; fd_set set; FD_ZERO(&set); FD_SET(s, &set); int ret = select( (int)s+1, Read ? &set : NULL, !Read ? &set : NULL, NULL, TimeoutMs >= 0 ? &t : NULL); if (ret > 0 && FD_ISSET(s, &set)) { return true; } } return false; } // This is the timing granularity of the SelectWithCancel loop. Ie the // number of milliseconds between checking the Cancel object. constexpr static int CancelCheckMs = 50; bool SelectWithCancel(int TimeoutMs, bool Read) { if (!Cancel) // Regular select where we just wait the whole timeout... return Select(TimeoutMs, false); // Because select can't check out 'Cancel' value during the waiting we run the select // call in a much smaller timeout and a loop so that we can respond to Cancel being set // in a timely manner. auto Now = LCurrentTime(); auto End = Now + TimeoutMs; do { // Do the cancel check... if (Cancel->IsCancelled()) break; // How many ms to wait? Now = LCurrentTime(); auto Remain = MIN(CancelCheckMs, (int)(End - Now)); if (Remain <= 0) break; if (Select(Remain, Read)) return true; } while (Now < End); return false; } }; LSocket::LSocket(LStreamI *logger, void *unused_param) { StartNetworkStack(); BytesWritten = 0; BytesRead = 0; d = new LSocketImplPrivate; } LSocket::~LSocket() { Close(); DeleteObj(d); } bool LSocket::IsOK() { return #ifndef __llvm__ this != 0 && #endif d != 0; } LCancel *LSocket::GetCancel() { return d->Cancel; } void LSocket::SetCancel(LCancel *c) { d->Cancel = c; } void LSocket::OnDisconnect() { } OsSocket LSocket::ReleaseHandle() { auto h = d->Socket; d->Socket = INVALID_SOCKET; return h; } OsSocket LSocket::Handle(OsSocket Set) { if (Set != INVALID_SOCKET) { d->Socket = Set; } return d->Socket; } bool LSocket::IsOpen() { if (ValidSocket(d->Socket) && !d->Cancel->IsCancelled()) { return true; } return false; } int LSocket::GetTimeout() { return d->Timeout; } void LSocket::SetTimeout(int ms) { d->Timeout = ms; } bool LSocket::IsReadable(int TimeoutMs) { // Assign to local var to avoid a thread changing it // on us between the validity check and the select. // Which is important because a socket value of -1 // (ie invalid) will crash the FD_SET macro. OsSocket s = d->Socket; if (ValidSocket(s) && !d->Cancel->IsCancelled()) { #ifdef LINUX // Because Linux doesn't return from select() when the socket is // closed elsewhere we have to do something different... damn Linux, // why can't you just like do the right thing? struct pollfd fds; fds.fd = s; fds.events = POLLIN | POLLRDHUP | POLLERR; fds.revents = 0; int r = poll(&fds, 1, TimeoutMs); if (r > 0) { return fds.revents != 0; } else if (r < 0) { Error(); } #else struct timeval t = {TimeoutMs / 1000, (TimeoutMs % 1000) * 1000}; fd_set r; FD_ZERO(&r); FD_SET(s, &r); int v = select((int)s+1, &r, 0, 0, &t); if (v > 0 && FD_ISSET(s, &r)) { return true; } else if (v < 0) { Error(); } #endif } else LgiTrace("%s:%i - Not a valid socket.\n", _FL); return false; } bool LSocket::IsWritable(int TimeoutMs) { return d->SelectWithCancel(TimeoutMs, false); } bool LSocket::CanAccept(int TimeoutMs) { return IsReadable(TimeoutMs); } bool LSocket::IsBlocking() { return d->Blocking != 0; } void LSocket::IsBlocking(bool block) { if (d->Blocking ^ block) { d->Blocking = block; #if defined WIN32 ulong NonBlocking = !block; ioctlsocket(d->Socket, FIONBIO, &NonBlocking); #elif defined POSIX fcntl(d->Socket, F_SETFL, d->Blocking ? 0 : O_NONBLOCK); #else #error Impl me. #endif } } bool LSocket::IsDelayed() { return !d->NoDelay; } void LSocket::IsDelayed(bool Delay) { bool NoDelay = !Delay; if (d->NoDelay ^ NoDelay) { d->NoDelay = NoDelay; option_t i = d->NoDelay != 0; setsockopt(d->Socket, IPPROTO_TCP, TCP_NODELAY, (const char*)&i, sizeof(i)); } } int LSocket::GetLocalPort() { struct sockaddr_in addr; socklen_t size; ZeroObj(addr); size = sizeof(addr); if ((getsockname(Handle(), (sockaddr*)&addr, &size)) >= 0) { return ntohs(addr.sin_port); } return 0; } bool LSocket::GetLocalIp(char *IpAddr) { if (IpAddr) { struct sockaddr_in addr; socklen_t size; size = sizeof(addr); if ((getsockname(Handle(), (sockaddr*)&addr, &size)) < 0) return false; if (addr.sin_addr.s_addr == INADDR_ANY) return false; uchar *a = (uchar*)&addr.sin_addr.s_addr; sprintf_s( IpAddr, 16, "%i.%i.%i.%i", a[0], a[1], a[2], a[3]); return true; } return false; } bool LSocket::GetRemoteIp(uint32_t *IpAddr) { if (IpAddr) { struct sockaddr_in a; socklen_t addrlen = sizeof(a); if (!getpeername(Handle(), (sockaddr*)&a, &addrlen)) { *IpAddr = ntohl(a.sin_addr.s_addr); return true; } } return false; } bool LSocket::GetRemoteIp(char *IpAddr) { if (!IpAddr) return false; uint32_t Ip = 0; if (!GetRemoteIp(&Ip)) return false; sprintf_s( IpAddr, 16, "%u.%u.%u.%u", (Ip >> 24) & 0xff, (Ip >> 16) & 0xff, (Ip >> 8) & 0xff, (Ip) & 0xff); return false; } int LSocket::GetRemotePort() { struct sockaddr_in a; socklen_t addrlen = sizeof(a); if (!getpeername(Handle(), (sockaddr*)&a, &addrlen)) { return a.sin_port; } return 0; } int LSocket::Open(const char *HostAddr, int Port) { int Status = -1; Close(); if (HostAddr) { BytesWritten = 0; BytesRead = 0; sockaddr_in RemoteAddr; HostEnt *Host = 0; in_addr_t IpAddress = 0; ZeroObj(RemoteAddr); #ifdef WIN32 d->Socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, WSA_FLAG_OVERLAPPED); #else d->Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); #endif if (ValidSocket(d->Socket)) { LArray Buf(512); #if !defined(MAC) option_t i; socklen_t sz = sizeof(i); int r = getsockopt(d->Socket, IPPROTO_TCP, TCP_NODELAY, (char*)&i, &sz); if (d->NoDelay ^ i) { i = d->NoDelay != 0; setsockopt(d->Socket, IPPROTO_TCP, TCP_NODELAY, (char *) &i, sizeof(i)); } #endif if (IsDigit(*HostAddr) && strchr(HostAddr, '.')) { // Ip address // IpAddress = inet_addr(HostAddr); if (!inet_pton(AF_INET, HostAddr, &IpAddress)) { Error(); return 0; } /* This seems complete unnecessary? -fret Dec 2018 #if defined(WIN32) Host = c((const char*) &IpAddress, 4, AF_INET); if (!Host) Error(); #else Host = gethostbyaddr ( #ifdef MAC HostAddr, #else &IpAddress, #endif 4, AF_INET ); #endif */ } else { // Name address #ifdef LINUX Host = new HostEnt; if (Host) { memset(Host, 0, sizeof(*Host)); HostEnt *Result = 0; int Err = 0; int Ret; while ( ( !GetCancel() || !GetCancel()->IsCancelled() ) && ( Ret = gethostbyname_r(HostAddr, Host, &Buf[0], Buf.Length(), &Result, &Err) ) == ERANGE ) { Buf.Length(Buf.Length() << 1); } if (Ret) { - char *ErrStr = GetErrorName(Err); + auto ErrStr = LErrorCodeToString(Err); printf("%s:%i - gethostbyname_r('%s') returned %i, %i, %s\n", - _FL, HostAddr, Ret, Err, ErrStr); + _FL, HostAddr, Ret, Err, ErrStr.Get()); DeleteObj(Host); } } #if DEBUG_CONNECT printf("%s:%i - Host=%p\n", __FILE__, __LINE__, Host); #endif #else Host = gethostbyname(HostAddr); #endif if (!Host) { Error(); Close(); return false; } } if (1) { RemoteAddr.sin_family = AF_INET; RemoteAddr.sin_port = htons(Port); if (Host) { if (Host->h_addr_list && Host->h_addr_list[0]) { memcpy(&RemoteAddr.sin_addr, Host->h_addr_list[0], sizeof(in_addr) ); } else return false; } else { memcpy(&RemoteAddr.sin_addr, &IpAddress, sizeof(IpAddress) ); } #ifdef WIN32 if (d->Timeout < 0) { // Do blocking connect Status = connect(d->Socket, (sockaddr*) &RemoteAddr, sizeof(sockaddr_in)); } else #endif { #define CONNECT_LOGGING 0 // Setup the connect bool Block = IsBlocking(); if (Block) { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Setting non blocking\n", d->Socket); #endif IsBlocking(false); } // Do initial connect to kick things off.. #if CONNECT_LOGGING LgiTrace(LPrintSock " - Doing initial connect to %s:%i\n", d->Socket, HostAddr, Port); #endif Status = connect(d->Socket, (sockaddr*) &RemoteAddr, sizeof(sockaddr_in)); #if CONNECT_LOGGING LgiTrace(LPrintSock " - Initial connect=%i Block=%i\n", d->Socket, Status, Block); #endif // Wait for the connect to finish? if (Status && Block) { Error(Host); #ifdef WIN32 // yeah I know... wtf? (http://itamarst.org/writings/win32sockets.html) #define IsWouldBlock() (d->LastError == EWOULDBLOCK || d->LastError == WSAEINVAL || d->LastError == WSAEWOULDBLOCK) #else #define IsWouldBlock() (d->LastError == EWOULDBLOCK || d->LastError == EINPROGRESS) #endif #if CONNECT_LOGGING LgiTrace(LPrintSock " - IsWouldBlock()=%i d->LastError=%i\n", d->Socket, IsWouldBlock(), d->LastError); #endif int64 End = LCurrentTime() + (d->Timeout > 0 ? d->Timeout : 30000); while ( !d->Cancel->IsCancelled() && ValidSocket(d->Socket) && IsWouldBlock()) { int64 Remaining = End - LCurrentTime(); #if CONNECT_LOGGING LgiTrace(LPrintSock " - Remaining " LPrintfInt64 "\n", d->Socket, Remaining); #endif if (Remaining < 0) { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Leaving loop\n", d->Socket); #endif break; } if (IsWritable((int)MIN(Remaining, 1000))) { // Should be ready to connect now... #if CONNECT_LOGGING LgiTrace(LPrintSock " - Secondary connect...\n", d->Socket); #endif Status = connect(d->Socket, (sockaddr*) &RemoteAddr, sizeof(sockaddr_in)); #if CONNECT_LOGGING LgiTrace(LPrintSock " - Secondary connect=%i\n", d->Socket, Status); #endif if (Status != 0) { Error(Host); if (d->LastError == EISCONN #ifdef WIN32 || d->LastError == WSAEISCONN // OMG windows, really? #endif ) { Status = 0; } else { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Connect=%i Err=%i\n", d->Socket, Status, d->LastError); #endif if (IsWouldBlock()) continue; } break; } else { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Connected...\n", d->Socket); #endif break; } } else { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Timout...\n", d->Socket); #endif } } } if (Block) IsBlocking(true); } if (!Status) { char Info[256]; sprintf_s(Info, sizeof(Info), "[INET] Socket Connect: %s [%i.%i.%i.%i], port: %i", HostAddr, (RemoteAddr.sin_addr.s_addr) & 0xFF, (RemoteAddr.sin_addr.s_addr >> 8) & 0xFF, (RemoteAddr.sin_addr.s_addr >> 16) & 0xFF, (RemoteAddr.sin_addr.s_addr >> 24) & 0xFF, Port); OnInformation(Info); } } if (Status) { #ifdef WIN32 closesocket(d->Socket); #else close(d->Socket); #endif d->Socket = INVALID_SOCKET; } } else { Error(); } } return Status == 0; } bool LSocket::Bind(int Port, bool reuseAddr) { if (!ValidSocket(d->Socket)) { OnError(0, "Attempt to use invalid socket to bind."); return false; } if (reuseAddr) { int so_reuseaddr = 1; if (setsockopt(Handle(), SOL_SOCKET, SO_REUSEADDR, (const char *)&so_reuseaddr, sizeof so_reuseaddr)) OnError(0, "Attempt to set SO_REUSEADDR failed."); // This might not be fatal... so continue on. } sockaddr_in add; add.sin_family = AF_INET; add.sin_addr.s_addr = htonl(INADDR_ANY); add.sin_port = htons(Port); int ret = bind(Handle(), (sockaddr*)&add, sizeof(add)); if (ret) { Error(); } return ret == 0; } bool LSocket::Listen(int Port) { Close(); d->Socket = socket(AF_INET, SOCK_STREAM, 0); if (d->Socket >= 0) { BytesWritten = 0; BytesRead = 0; sockaddr Addr; sockaddr_in *a = (sockaddr_in*) &Addr; ZeroObj(Addr); a->sin_family = AF_INET; a->sin_port = htons(Port); a->sin_addr.OsAddr = INADDR_ANY; if (bind(d->Socket, &Addr, sizeof(Addr)) >= 0) { if (listen(d->Socket, SOMAXCONN) != SOCKET_ERROR) { return true; } else { Error(); } } else { Error(); } } else { Error(); } return false; } bool LSocket::Accept(LSocketI *c) { if (!c) { LAssert(0); return false; } OsSocket NewSocket = INVALID_SOCKET; sockaddr Address; /* int Length = sizeof(Address); NewSocket = accept(d->Socket, &Address, &Length); */ // int Loop = 0; socklen_t Length = sizeof(Address); uint64 Start = LCurrentTime(); while ( !d->Cancel->IsCancelled() && ValidSocket(d->Socket)) { if (IsReadable(100)) { NewSocket = accept(d->Socket, &Address, &Length); break; } else if (d->Timeout > 0) { uint64 Now = LCurrentTime(); if (Now - Start >= d->Timeout) { LString s; s.Printf("Accept timeout after %.1f seconds.", ((double)(Now-Start)) / 1000.0); OnInformation(s); return false; } } } if (!ValidSocket(NewSocket)) return false; return ValidSocket(c->Handle(NewSocket)); } int LSocket::Close() { if (ValidSocket(d->Socket)) { #if defined WIN32 closesocket(d->Socket); #else close(d->Socket); #endif d->Socket = INVALID_SOCKET; OnDisconnect(); } return true; } void LSocket::Log(const char *Msg, ssize_t Ret, const char *Buf, ssize_t Len) { if (d->LogFile) { LFile f; if (f.Open(d->LogFile, O_WRITE)) { f.Seek(f.GetSize(), SEEK_SET); switch (d->LogType) { case NET_LOG_HEX_DUMP: { char s[256]; f.Write(s, sprintf_s(s, sizeof(s), "%s = %i\r\n", Msg, (int)Ret)); for (int i=0; iSocket) || !Data || d->Cancel->IsCancelled()) return -1; int Status = 0; if (d->Timeout < 0 || IsWritable(d->Timeout)) { Status = (int)send ( d->Socket, (char*)Data, (int) Len, Flags #ifndef MAC | MSG_NOSIGNAL #endif ); } if (Status < 0) Error(); else if (Status == 0) OnDisconnect(); else { if (Status < Len) { // Just in case it's a string lets be safe ((char*)Data)[Status] = 0; } BytesWritten += Status; OnWrite((char*)Data, Status); } return Status; } ssize_t LSocket::Read(void *Data, ssize_t Len, int Flags) { if (!ValidSocket(d->Socket) || !Data || d->Cancel->IsCancelled()) return -1; ssize_t Status = -1; if (d->Timeout < 0 || IsReadable(d->Timeout)) { Status = recv(d->Socket, (char*)Data, (int) Len, Flags #ifdef MSG_NOSIGNAL | MSG_NOSIGNAL #endif ); } Log("Read", (int)Status, (char*)Data, Status>0 ? Status : 0); if (Status < 0) Error(); else if (Status == 0) OnDisconnect(); else { if (Status < Len) { // Just in case it's a string lets be safe ((char*)Data)[Status] = 0; } BytesRead += Status; OnRead((char*)Data, (int)Status); } return (int)Status; } void LSocket::OnError(int ErrorCode, const char *ErrorDescription) { d->ErrStr.Printf("Error(%i): %s", ErrorCode, ErrorDescription); } const char *LSocket::GetErrorString() { return d->ErrStr; } int LSocket::Error(void *Param) { // Get the most recent error. if (!(d->LastError = #ifdef WIN32 WSAGetLastError() #else errno #endif )) return 0; // These are not really errors... if (d->LastError == EWOULDBLOCK || d->LastError == EISCONN) return 0; static class ErrorMsg { public: int Code; const char *Msg; } ErrorCodes[] = { {0, "Socket disconnected."}, #if defined WIN32 {WSAEACCES, "Permission denied."}, {WSAEADDRINUSE, "Address already in use."}, {WSAEADDRNOTAVAIL, "Cannot assign requested address."}, {WSAEAFNOSUPPORT, "Address family not supported by protocol family."}, {WSAEALREADY, "Operation already in progress."}, {WSAECONNABORTED, "Software caused connection abort."}, {WSAECONNREFUSED, "Connection refused."}, {WSAECONNRESET, "Connection reset by peer."}, {WSAEDESTADDRREQ, "Destination address required."}, {WSAEFAULT, "Bad address."}, {WSAEHOSTDOWN, "Host is down."}, {WSAEHOSTUNREACH, "No route to host."}, {WSAEINPROGRESS, "Operation now in progress."}, {WSAEINTR, "Interrupted function call."}, {WSAEINVAL, "Invalid argument."}, {WSAEISCONN, "Socket is already connected."}, {WSAEMFILE, "Too many open files."}, {WSAEMSGSIZE, "Message too long."}, {WSAENETDOWN, "Network is down."}, {WSAENETRESET, "Network dropped connection on reset."}, {WSAENETUNREACH, "Network is unreachable."}, {WSAENOBUFS, "No buffer space available."}, {WSAENOPROTOOPT, "Bad protocol option."}, {WSAENOTCONN, "Socket is not connected."}, {WSAENOTSOCK, "Socket operation on non-socket."}, {WSAEOPNOTSUPP, "Operation not supported."}, {WSAEPFNOSUPPORT, "Protocol family not supported."}, {WSAEPROCLIM, "Too many processes."}, {WSAEPROTONOSUPPORT,"Protocol not supported."}, {WSAEPROTOTYPE, "Protocol wrong type for socket."}, {WSAESHUTDOWN, "Cannot send after socket shutdown."}, {WSAESOCKTNOSUPPORT,"Socket type not supported."}, {WSAETIMEDOUT, "Connection timed out."}, {WSAEWOULDBLOCK, "Operation would block."}, {WSAHOST_NOT_FOUND, "Host not found."}, {WSANOTINITIALISED, "Successful WSAStartup not yet performed."}, {WSANO_DATA, "Valid name, no data record of requested type."}, {WSANO_RECOVERY, "This is a non-recoverable error."}, {WSASYSNOTREADY, "Network subsystem is unavailable."}, {WSATRY_AGAIN, "Non-authoritative host not found."}, {WSAVERNOTSUPPORTED,"WINSOCK.DLL version out of range."}, {WSAEDISCON, "Graceful shutdown in progress."}, #else {EACCES, "Permission denied."}, {EADDRINUSE, "Address already in use."}, {EADDRNOTAVAIL, "Cannot assign requested address."}, {EAFNOSUPPORT, "Address family not supported by protocol family."}, {EALREADY, "Operation already in progress."}, {ECONNABORTED, "Software caused connection abort."}, {ECONNREFUSED, "Connection refused."}, {ECONNRESET, "Connection reset by peer."}, {EFAULT, "Bad address."}, {EHOSTUNREACH, "No route to host."}, {EINPROGRESS, "Operation now in progress."}, {EINTR, "Interrupted function call."}, {EINVAL, "Invalid argument."}, {EISCONN, "Socket is already connected."}, {EMFILE, "Too many open files."}, {EMSGSIZE, "Message too long."}, {ENETDOWN, "Network is down."}, {ENETRESET, "Network dropped connection on reset."}, {ENETUNREACH, "Network is unreachable."}, {ENOBUFS, "No buffer space available."}, {ENOPROTOOPT, "Bad protocol option."}, {ENOTCONN, "Socket is not connected."}, {ENOTSOCK, "Socket operation on non-socket."}, {EOPNOTSUPP, "Operation not supported."}, {EPFNOSUPPORT, "Protocol family not supported."}, {EPROTONOSUPPORT, "Protocol not supported."}, {EPROTOTYPE, "Protocol wrong type for socket."}, {ESHUTDOWN, "Cannot send after socket shutdown."}, {ETIMEDOUT, "Connection timed out."}, {EWOULDBLOCK, "Resource temporarily unavailable."}, {HOST_NOT_FOUND, "Host not found."}, {NO_DATA, "Valid name, no data record of requested type."}, {NO_RECOVERY, "This is a non-recoverable error."}, {TRY_AGAIN, "Non-authoritative host not found."}, {ETIMEOUT, "Operation timed out."}, {EDESTADDRREQ, "Destination address required."}, {EHOSTDOWN, "Host is down."}, #ifndef HAIKU {ESOCKTNOSUPPORT, "Socket type not supported."}, #endif #endif {-1, 0} }; ErrorMsg *Error = ErrorCodes; while (Error->Code >= 0 && Error->Code != d->LastError) { Error++; } if (d->LastError == 10060 && Param) { HostEnt *He = (HostEnt*)Param; char s[256]; sprintf_s(s, sizeof(s), "%s (gethostbyname returned '%s')", Error->Msg, He->h_name); OnError(d->LastError, s); } else #if defined(MAC) if (d->LastError != 36) #endif { OnError(d->LastError, (Error->Code >= 0) ? Error->Msg : ""); } switch (d->LastError) { case 0: #ifdef WIN32 case 183: // I think this is a XP 'disconnect' ??? case WSAECONNABORTED: case WSAECONNRESET: case WSAENETRESET: #else case ECONNABORTED: case ECONNRESET: case ENETRESET: #endif { Close(); break; } } return d->LastError; } bool LSocket::GetUdp() { return d->Udp != 0; } void LSocket::SetUdp(bool isUdp) { if (d->Udp ^ isUdp) { d->Udp = isUdp; if (!ValidSocket(d->Socket)) { if (d->Udp) d->Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); else d->Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); } if (d->Broadcast) { option_t enabled = d->Broadcast != 0; setsockopt(Handle(), SOL_SOCKET, SO_BROADCAST, (char*)&enabled, sizeof(enabled)); } } } void LSocket::SetBroadcast(bool isBroadcast) { d->Broadcast = isBroadcast; } bool LSocket::AddMulticastMember(uint32_t MulticastIp, uint32_t LocalInterface) { if (!MulticastIp) return false; struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = htonl(MulticastIp); // your multicast address mreq.imr_interface.s_addr = htonl(LocalInterface); // your incoming interface IP int r = setsockopt(Handle(), IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char*) &mreq, sizeof(mreq)); if (!r) { LgiTrace("AddMulticastMember(%s, %s)\n", LIpToStr(MulticastIp).Get(), LIpToStr(LocalInterface).Get()); return true; } Error(); return false; } bool LSocket::SetMulticastInterface(uint32_t Interface) { if (!Interface) return false; struct sockaddr_in addr; addr.sin_addr.s_addr = Interface; auto r = setsockopt(Handle(), IPPROTO_IP, IP_MULTICAST_IF, (const char*) &addr, sizeof(addr)); if (!r) return true; Error(); return false; } bool LSocket::CreateUdpSocket() { if (!ValidSocket(d->Socket)) { d->Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (ValidSocket(d->Socket)) { if (d->Broadcast) { option_t enabled = d->Broadcast != 0; auto r = setsockopt(Handle(), SOL_SOCKET, SO_BROADCAST, (char*)&enabled, sizeof(enabled)); if (r) Error(); } } } return ValidSocket(d->Socket); } int LSocket::ReadUdp(void *Buffer, int Size, int Flags, uint32_t *Ip, uint16_t *Port) { if (!Buffer || Size < 0) return -1; CreateUdpSocket(); sockaddr_in a; socklen_t AddrSize = sizeof(a); ZeroObj(a); a.sin_family = AF_INET; if (Port) a.sin_port = htons(*Port); #if defined(WINDOWS) a.sin_addr.S_un.S_addr = INADDR_ANY; #else a.sin_addr.s_addr = INADDR_ANY; #endif auto b = recvfrom(d->Socket, (char*)Buffer, Size, Flags, (sockaddr*)&a, &AddrSize); if (b > 0) { OnRead((char*)Buffer, (int)b); if (Ip) *Ip = ntohl(a.sin_addr.OsAddr); if (Port) *Port = ntohs(a.sin_port); } return (int)b; } int LSocket::WriteUdp(void *Buffer, int Size, int Flags, uint32_t Ip, uint16_t Port) { if (!Buffer || Size < 0) return -1; CreateUdpSocket(); sockaddr_in a; ZeroObj(a); a.sin_family = AF_INET; a.sin_port = htons(Port); a.sin_addr.OsAddr = htonl(Ip); ssize_t b = sendto(d->Socket, (char*)Buffer, Size, Flags, (sockaddr*)&a, sizeof(a)); if (b > 0) { OnWrite((char*)Buffer, (int)b); } else { printf("%s:%i - sendto failed with %i.\n", _FL, errno); } return (int)b; } ////////////////////////////////////////////////////////////////////////////// bool HaveNetConnection() { bool Status = false; // Check for dial up connection #if defined WIN32 typedef DWORD (__stdcall *RasEnumConnections_Proc)(LPRASCONN lprasconn, LPDWORD lpcb, LPDWORD lpcConnections); typedef DWORD (__stdcall *RasGetConnectStatus_Proc)(HRASCONN hrasconn, LPRASCONNSTATUS lprasconnstatus); HMODULE hRas = (HMODULE) LoadLibraryA("rasapi32.dll"); if (hRas) { RASCONN Con[10]; DWORD Connections = 0; DWORD Bytes = sizeof(Con); ZeroObj(Con); Con[0].dwSize = sizeof(Con[0]); RasEnumConnections_Proc pRasEnumConnections = (RasEnumConnections_Proc) GetProcAddress(hRas, "RasEnumConnectionsA"); RasGetConnectStatus_Proc pRasGetConnectStatus = (RasGetConnectStatus_Proc) GetProcAddress(hRas, "RasGetConnectStatusA"); if (pRasEnumConnections && pRasGetConnectStatus) { pRasEnumConnections(Con, &Bytes, &Connections); for (unsigned i=0; i>24)&0xff, (ip>>16)&0xff, (ip>>8)&0xff, (ip)&0xff); return s; } uint32_t LIpToInt(LString str) { auto p = str.Split("."); if (p.Length() != 4) return 0; uint32_t ip = 0; for (auto &s : p) { ip <<= 8; auto n = s.Int(); if (n > 255) { LAssert(0); return 0; } ip |= (uint8_t)s.Int(); } return ip; } uint32_t LHostnameToIp(const char *Host) { if (!Host) return 0; // struct addrinfo hints = {}; struct addrinfo *res = NULL; getaddrinfo(Host, NULL, NULL, &res); if (!res) return 0; uint32_t ip = 0; if (res->ai_addr) { auto fam = res->ai_addr->sa_family; if (fam == AF_INET) { auto a = (sockaddr_in*)res->ai_addr; ip = ntohl(a->sin_addr.s_addr); } } freeaddrinfo(res); return ip; } bool WhatsMyIp(LAutoString &Ip) { bool Status = false; StartNetworkStack(); #if defined WIN32 char hostname[256]; HostEnt *e = NULL; if (gethostname(hostname, sizeof(hostname)) != SOCKET_ERROR) { e = gethostbyname(hostname); } if (e) { int Which = 0; for (; e->h_addr_list[Which]; Which++); Which--; char IpAddr[32]; sprintf_s(IpAddr, sizeof(IpAddr), "%i.%i.%i.%i", (uchar)e->h_addr_list[Which][0], (uchar)e->h_addr_list[Which][1], (uchar)e->h_addr_list[Which][2], (uchar)e->h_addr_list[Which][3]); Status = Ip.Reset(NewStr(IpAddr)); } #endif return Status; } ////////////////////////////////////////////////////////////////////// #define SOCKS5_VER 5 #define SOCKS5_CMD_CONNECT 1 #define SOCKS5_CMD_BIND 2 #define SOCKS5_CMD_ASSOCIATE 3 #define SOCKS5_ADDR_IPV4 1 #define SOCKS5_ADDR_DOMAIN 3 #define SOCKS5_ADDR_IPV6 4 #define SOCKS5_AUTH_NONE 0 #define SOCKS5_AUTH_GSSAPI 1 #define SOCKS5_AUTH_USER_PASS 2 LSocks5Socket::LSocks5Socket() { Socks5Connected = false; } void LSocks5Socket::SetProxy(const LSocks5Socket *s) { Proxy.Reset(s ? NewStr(s->Proxy) : 0); Port = s ? s->Port : 0; UserName.Reset(s ? NewStr(s->UserName) : 0); Password.Reset(s ? NewStr(s->Password) : 0); } void LSocks5Socket::SetProxy(char *proxy, int port, char *username, char *password) { Proxy.Reset(NewStr(proxy)); Port = port; UserName.Reset(NewStr(username)); Password.Reset(NewStr(password)); } int LSocks5Socket::Open(const char *HostAddr, int port) { bool Status = false; if (HostAddr) { char Msg[256]; sprintf_s(Msg, sizeof(Msg), "[SOCKS5] Connecting to proxy server '%s'", HostAddr); OnInformation(Msg); Status = LSocket::Open(Proxy, Port) != 0; if (Status) { char Buf[1024]; Buf[0] = SOCKS5_VER; Buf[1] = 2; // methods Buf[2] = SOCKS5_AUTH_NONE; Buf[3] = SOCKS5_AUTH_USER_PASS; // No idea how to implement this. // AuthReq[3] = SOCKS5_AUTH_GSSAPI; OnInformation("[SOCKS5] Connected, Requesting authentication type."); LSocket::Write(Buf, 4, 0); if (LSocket::Read(Buf, 2, 0) == 2) { if (Buf[0] == SOCKS5_VER) { bool Authenticated = false; switch (Buf[1]) { case SOCKS5_AUTH_NONE: { Authenticated = true; OnInformation("[SOCKS5] No authentication needed."); break; } case SOCKS5_AUTH_USER_PASS: { OnInformation("[SOCKS5] User/Pass authentication needed."); if (UserName && Password) { char *b = Buf; *b++ = 1; // ver of sub-negotiation ?? size_t NameLen = strlen(UserName); LAssert(NameLen < 0x80); *b++ = (char)NameLen; b += sprintf_s(b, NameLen+1, "%s", UserName.Get()); size_t PassLen = strlen(Password); LAssert(PassLen < 0x80); *b++ = (char)PassLen; b += sprintf_s(b, PassLen+1, "%s", Password.Get()); LSocket::Write(Buf, (int)(3 + NameLen + PassLen)); if (LSocket::Read(Buf, 2, 0) == 2) { Authenticated = (Buf[0] == 1 && Buf[1] == 0); } if (!Authenticated) { OnInformation("[SOCKS5] User/Pass authentication failed."); } } break; } } if (Authenticated) { OnInformation("[SOCKS5] Authentication successful."); int HostPort = htons(port); // Header char *b = Buf; *b++ = SOCKS5_VER; *b++ = SOCKS5_CMD_CONNECT; *b++ = 0; // reserved long IpAddr = inet_addr(HostAddr); if (IpAddr != -1) { // Ip *b++ = SOCKS5_ADDR_IPV4; memcpy(b, &IpAddr, 4); b += 4; } else { // Domain Name *b++ = SOCKS5_ADDR_DOMAIN; size_t Len = strlen(HostAddr); LAssert(Len < 0x80); *b++ = (char)Len; strcpy_s(b, Buf+sizeof(Buf)-b, HostAddr); b += Len; } // Port memcpy(b, &HostPort, 2); b += 2; LSocket::Write(Buf, (int)(b - Buf), 0); if (LSocket::Read(Buf, 10, 0) == 10) { if (Buf[0] == SOCKS5_VER) { switch (Buf[1]) { case 0: Socks5Connected = true; OnInformation("[SOCKS5] Connected!"); break; case 1: OnInformation("[SOCKS5] General SOCKS server failure"); break; case 2: OnInformation("[SOCKS5] Connection not allowed by ruleset"); break; case 3: OnInformation("[SOCKS5] Network unreachable"); break; case 4: OnInformation("[SOCKS5] Host unreachable"); break; case 5: OnInformation("[SOCKS5] Connection refused"); break; case 6: OnInformation("[SOCKS5] TTL expired"); break; case 7: OnInformation("[SOCKS5] Command not supported"); break; case 8: OnInformation("[SOCKS5] Address type not supported"); break; default: OnInformation("[SOCKS5] Unknown SOCKS server failure"); break; } } else { OnInformation("[SOCKS5] Wrong socks version."); } } else { OnInformation("[SOCKS5] Connection request read failed."); } } else { OnInformation("[SOCKS5] Not authenticated."); } } else { OnInformation("[SOCKS5] Wrong socks version."); } } else { OnInformation("[SOCKS5] Authentication type read failed."); } Status = Socks5Connected; if (!Status) { LSocket::Close(); OnInformation("[SOCKS5] Failure: Disconnecting."); } } } return Status; } diff --git a/src/linux/Lgi/App.cpp b/src/linux/Lgi/App.cpp --- a/src/linux/Lgi/App.cpp +++ b/src/linux/Lgi/App.cpp @@ -1,1476 +1,1476 @@ #include #include #include #include #ifndef WIN32 #include #include #endif #include #define _GNU_SOURCE #include #include "lgi/common/Lgi.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/Array.h" #include "lgi/common/Variant.h" #include "lgi/common/Token.h" #include "lgi/common/FontCache.h" #include "AppPriv.h" #define DEBUG_MSG_TYPES 0 #define DEBUG_HND_WARNINGS 0 #define IDLE_ALWAYS 0 using namespace Gtk; bool GlibWidgetSearch(GtkWidget *p, GtkWidget *w, bool Debug, int depth = 0); //////////////////////////////////////////////////////////////// struct OsAppArgumentsPriv { ::LArray Ptr; ~OsAppArgumentsPriv() { Ptr.DeleteArrays(); } }; OsAppArguments::OsAppArguments(int args, const char **arg) { d = new OsAppArgumentsPriv; for (int i=0; iPtr.Add(NewStr(arg[i])); } Args = d->Ptr.Length(); Arg = &d->Ptr[0]; } OsAppArguments::~OsAppArguments() { DeleteObj(d); } bool OsAppArguments::Get(const char *Name, const char **Val) { if (!Name) return false; for (int i=0; iPtr.DeleteArrays(); if (!CmdLine) return; for (char *s = CmdLine; *s; ) { while (*s && strchr(WhiteSpace, *s)) s++; if (*s == '\'' || *s == '\"') { char delim = *s++; char *e = strchr(s, delim); if (e) d->Ptr.Add(NewStr(s, e - s)); else break; s = e + 1; } else { char *e = s; while (*e && !strchr(WhiteSpace, *e)) e++; d->Ptr.Add(NewStr(s, e-s)); s = e; } } Args = d->Ptr.Length(); Arg = &d->Ptr[0]; } OsAppArguments &OsAppArguments::operator =(OsAppArguments &a) { d->Ptr.DeleteArrays(); for (int i=0; iPtr.Add(NewStr(a.Arg[i])); } Args = d->Ptr.Length(); Arg = &d->Ptr[0]; return *this; } //////////////////////////////////////////////////////////////// #if HAS_SHARED_MIME #include "GFilterUtils.h" #include "mime-types.h" class LSharedMime : public LLibrary { public: LSharedMime() : #ifdef _DEBUG LLibrary("libsharedmime1d") #else LLibrary("libsharedmime1") #endif { } DynFunc0(int, mimetypes_init); DynFunc1(const char*, mimetypes_set_default_type, const char *, default_type); DynFunc2(const char*, mimetypes_get_file_type, const char*, pathname, mimetypes_flags, flags); DynFunc2(const char*, mimetypes_get_data_type, const void*, data, int, length); DynFunc3(bool, mimetypes_decode, const char *, type, char **, media_type, char **, sub_type); DynFunc2(char *, mimetypes_convert_filename, const char *, pathname, const char *, mime_type); DynFunc3(bool, mimetypes_add_mime_dir, const char *, path, bool, high_priority, bool, rescan); DynFunc2(const char *, mimetypes_get_type_info, const char *, mime_type, const char *, lang); }; #endif ///////////////////////////////////////////////////////////////////////////// // // Attempts to cleanup and call drkonqi to process the crash // static LString CrashHandlerApp; void LgiCrashHandler(int Sig) { // Don't get into an infinite loop signal(SIGSEGV, SIG_DFL); #ifndef _MSC_VER // Our pid LString pid; pid.Printf("%i", getpid()); LgiTrace("LgiCrashHandler trigger pid=%s\n", pid.Get()); auto child = fork(); if (!child) { LFile::Path workingDir = CrashHandlerApp; workingDir--; chdir(workingDir); const char *args[] = { CrashHandlerApp, "--pid", pid, NULL }; execvp(CrashHandlerApp, args); exit(0); } if (LAppInst->InThread()) { LgiTrace("LgiCrashHandler showing dlg\n"); LgiMsg(NULL, "Application crashed... dumping details.", "Crash"); } else { LgiTrace("LgiCrashHandler called from worker thread.\n"); LSleep(10000); // Wait for the crash handler to do it's thing... } #endif exit(-1); } ///////////////////////////////////////////////////////////////////////////// #ifndef XK_Num_Lock #define XK_Num_Lock 0xff7f #endif #ifndef XK_Shift_Lock #define XK_Shift_Lock 0xffe6 #endif #ifndef XK_Caps_Lock #define XK_Caps_Lock 0xffe5 #endif struct Msg { LViewI *v; int m; LMessage::Param a, b; void Set(LViewI *V, int M, LMessage::Param A, LMessage::Param B) { v = V; m = M; a = A; b = B; } }; // Out of thread messages... must lock before access. class LMessageQue : public LMutex { public: typedef LArray MsgArray; LMessageQue() : LMutex("LMessageQue") { } MsgArray *Lock(const char *file, int line) { if (!LMutex::Lock(file, line)) return NULL; return &q; } operator bool() { return q.Length() > 0; } private: MsgArray q; } MsgQue; ///////////////////////////////////////////////////////////////////////////// LSkinEngine *LApp::SkinEngine = 0; LApp *TheApp = 0; LMouseHook *LApp::MouseHook = 0; LApp::LApp(OsAppArguments &AppArgs, const char *name, LAppArguments *Args) : OsApplication(AppArgs.Args, AppArgs.Arg) { TheApp = this; d = new LAppPrivate(this); Name(name); LgiArgsAppPath = AppArgs.Arg[0]; if (LIsRelativePath(LgiArgsAppPath)) { char Cwd[MAX_PATH_LEN]; getcwd(Cwd, sizeof(Cwd)); LMakePath(Cwd, sizeof(Cwd), Cwd, LgiArgsAppPath); LgiArgsAppPath = Cwd; } int WCharSz = sizeof(wchar_t); #if defined(_MSC_VER) LAssert(WCharSz == 2); ::LFile::Path Dlls(LgiArgsAppPath); Dlls--; SetDllDirectoryA(Dlls); #else LAssert(WCharSz == 4); #endif #ifdef _MSC_VER SetEnvironmentVariable(_T("GTK_CSD"), _T("0")); #else setenv("GTK_CSD", "0", true); #endif // We want our printf's NOW! setvbuf(stdout, (char*)NULL,_IONBF, 0); // print mesgs immediately. // Setup the file and graphics sub-systems d->FileSystem = new LFileSystem; d->GdcSystem = new GdcDevice; SetAppArgs(AppArgs); srand(LCurrentTime()); LColour::OnChange(); Gtk::gchar id[256]; sprintf_s(id, sizeof(id), "com.memecode.%s", name); d->App = Gtk::gtk_application_new(id, Gtk::G_APPLICATION_FLAGS_NONE); LAssert(d->App != NULL); MouseHook = new LMouseHook; // Setup the SIGSEGV signal to call the crash handler if (!GetOption("nch")) { auto programName = "crash-handler"; LFile::Path p(LSP_APP_INSTALL); p += programName; if (!p.Exists()) { // Check alternate location for development builds Dl_info dlInfo; dladdr(LgiCrashHandler, &dlInfo); if (dlInfo.dli_sname != NULL && dlInfo.dli_saddr != NULL) { p = dlInfo.dli_fname; p += "../../src/linux/CrashHandler"; p += programName; printf("Alternative path %s: %s\n", p.Exists() ? "found" : "missing", p.GetFull().Get()); } } if (p.Exists()) { CrashHandlerApp = p; signal(SIGSEGV, LgiCrashHandler); LgiTrace("Crash handler: '%s' installed.\n", CrashHandlerApp.Get()); } else { LgiTrace("Crash handler: No crash handler '%s' found, SIGSEGV handler not installed.\n", p.GetFull().Get()); } } else { LgiTrace("Crash handler: disabled.\n"); } d->GetConfig(); // System font setup LFontType SysFontType; Gtk::PangoFontMap *fm = Gtk::pango_cairo_font_map_get_default(); if (fm) { using namespace Gtk; auto cfm = PANGO_CAIRO_FONT_MAP(fm); #ifdef MAC double Dpi = 80.0; #else double Dpi = 96.0; #endif ::LFile::Path p(LSP_APP_ROOT); p += "lgi-conf.json"; if (p.IsFile()) { ::LFile f(p, O_READ); LJson j(f.Read()); auto sDpi = j.Get("DPI"); if (sDpi) Dpi = sDpi.Float(); } pango_cairo_font_map_set_resolution(cfm, Dpi); } if (SysFontType.GetSystemFont("System")) { SystemNormal = SysFontType.Create(); if (SystemNormal) SystemNormal->Transparent(true); SystemBold = SysFontType.Create(); if (SystemBold) { SystemBold->Bold(true); SystemBold->Transparent(true); SystemBold->Create(); } } else { printf("%s:%i - Couldn't get system font setting.\n", __FILE__, __LINE__); } if (!SystemNormal) { LgiMsg(0, "Error: Couldn't create system font.", "Lgi Error: LApp::LApp", MB_OK); LExitApp(); } if (!GetOption("noskin")) { extern LSkinEngine *CreateSkinEngine(LApp *App); SkinEngine = CreateSkinEngine(this); } } LApp::~LApp() { DeleteObj(AppWnd); DeleteObj(SystemNormal); DeleteObj(SystemBold); DeleteObj(SkinEngine); DeleteObj(MouseHook); DeleteObj(d->FileSystem); DeleteObj(d->GdcSystem); DeleteObj(LFontSystem::Me); DeleteObj(d); TheApp = 0; } LApp *LApp::ObjInstance() { return TheApp; } bool LApp::IsOk() { bool Status = #ifndef __clang__ (this != 0) && #endif (d != 0); LAssert(Status); return Status; } LMouseHook *LApp::GetMouseHook() { return MouseHook; } int LApp::GetMetric(LSystemMetric Metric) { switch (Metric) { case LGI_MET_DECOR_X: return 8; case LGI_MET_DECOR_Y: return 8 + 19; case LGI_MET_DECOR_CAPTION: return 19; default: break; } return 0; } LViewI *LApp::GetFocus() { // GtkWidget *w = gtk_window_get_focus(GtkWindow *window); return NULL; } OsThread LApp::_GetGuiThread() { return d->GuiThread; } OsThreadId LApp::GetGuiThreadId() { return d->GuiThreadId; } OsProcessId LApp::GetProcessId() { #ifdef WIN32 return GetCurrentProcessId(); #else return getpid(); #endif } OsAppArguments *LApp::GetAppArgs() { return IsOk() ? &d->Args : 0; } void LApp::SetAppArgs(OsAppArguments &AppArgs) { if (IsOk()) { d->Args = AppArgs; } } bool LApp::InThread() { OsThreadId Me = GetCurrentThreadId(); OsThreadId Gui = GetGuiThreadId(); // printf("Me=%i Gui=%i\n", Me, Gui); return Gui == Me; } struct GtkIdle { LAppPrivate *d; LAppI::OnIdleProc cb; void *param; }; Gtk::gboolean IdleWrapper(Gtk::gpointer data) { #if 0 static int64 ts = LCurrentTime(); static int count = 0; int64 now = LCurrentTime(); if (now - ts > 300) { printf("IdleWrapper = %i\n", count); count = 0; ts = now; } else count++; #endif GtkIdle *i = (GtkIdle*) data; if (i->cb) i->cb(i->param); LMessageQue::MsgArray *Msgs; if (MsgQue && (Msgs = MsgQue.Lock(_FL))) { // printf("IdleWrapper start %i\n", (int)Msgs->Length()); // Copy the messages out of the locked structure.. // This allows new messages to arrive independent // of us processing them here... LMessageQue::MsgArray q = *Msgs; Msgs->Empty(); MsgQue.Unlock(); for (auto m : q) { if (!LView::LockHandler(m.v, LView::OpExists)) { // LgiTrace("%s:%i - Invalid view: %p.\n", _FL, m.v); } else { LMessage Msg(m.m, m.a, m.b); // LgiTrace("%s::OnEvent %i,%i,%i\n", m.v->GetClass(), m.m, m.a, m.b); m.v->OnEvent(&Msg); } } } else { // printf("IdleWrapper start no lock\n"); } // printf("IdleWrapper end\n"); return i->cb != NULL; // return false; } static GtkIdle idle = {0}; bool LApp::Run(OnIdleProc IdleCallback, void *IdleParam) { if (!InThread()) { printf("%s:%i - Error: Out of thread.\n", _FL); return false; } if (!idle.d) { idle.d = d; idle.cb = IdleCallback; idle.param = IdleParam; } static bool CmdLineDone = false; if (!CmdLineDone) { CmdLineDone = true; OnCommandLine(); } Gtk::gtk_main(); return true; } bool LApp::Yield() { Gtk::gtk_main_iteration_do(false); return true; } void LApp::Exit(int Code) { if (Code) { // hard exit ::exit(Code); } else { // soft exit Gtk::gtk_main_quit(); if (d->IdleId.Length() > 0) { size_t Last = d->IdleId.Length() - 1; Gtk::g_source_remove(d->IdleId[Last]); d->IdleId.Length(Last); } } } bool LApp::PostEvent(LViewI *View, int Msg, LMessage::Param a, LMessage::Param b) { auto q = MsgQue.Lock(_FL); if (!q) { printf("%s:%i - Couldn't lock app.\n", _FL); return false; } q->New().Set(View, Msg, a, b); #if 1 // defined(_DEBUG) if (q->Length() > 200 && (q->Length()%20)==0) { static uint64 prev = 0; auto now = LCurrentTime(); if (now - prev >= 1000) { prev = now; #if defined(WIN32) char s[256]; sprintf_s(s, sizeof(s), #else printf( #endif "PostEvent Que=" LPrintfSizeT " (msg=%i)\n", q->Length(), Msg); #if defined(WIN32) OutputDebugStringA(s); #endif #ifdef LINUX LHashTbl, size_t> MsgCounts; for (auto &msg: *q) MsgCounts.Add(msg.m, MsgCounts.Find(msg.m) + 1); for (auto c: MsgCounts) printf(" %i->%i\n", c.key, c.value); if (Msg == 916) { int asd=0; } #endif } } #endif MsgQue.Unlock(); // g_idle_add((GSourceFunc)IdleWrapper, &idle); g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)IdleWrapper, &idle, NULL); return true; } void LApp::OnUrl(const char *Url) { if (AppWnd) AppWnd->OnUrl(Url); } void LApp::OnReceiveFiles(::LArray &Files) { if (AppWnd) AppWnd->OnReceiveFiles(Files); else LAssert(!"You probably want to set 'AppWnd' before calling LApp::Run... maybe."); } const char *LApp::KeyModFlags::FlagName(int Flag) { #define CHECK(f) if (Flag & f) return #f; CHECK(GDK_SHIFT_MASK) CHECK(GDK_LOCK_MASK) CHECK(GDK_CONTROL_MASK) CHECK(GDK_MOD1_MASK) CHECK(GDK_MOD2_MASK) CHECK(GDK_MOD3_MASK) CHECK(GDK_MOD4_MASK) CHECK(GDK_MOD5_MASK) CHECK(GDK_BUTTON1_MASK) CHECK(GDK_BUTTON2_MASK) CHECK(GDK_BUTTON3_MASK) CHECK(GDK_BUTTON4_MASK) CHECK(GDK_BUTTON5_MASK) CHECK(GDK_SUPER_MASK) CHECK(GDK_HYPER_MASK) CHECK(GDK_META_MASK) CHECK(GDK_RELEASE_MASK) #undef CHECK return NULL; } int LApp::KeyModFlags::FlagValue(const char *Name) { #define CHECK(f) if (!Stricmp(Name, #f)) return f; CHECK(GDK_SHIFT_MASK) CHECK(GDK_LOCK_MASK) CHECK(GDK_CONTROL_MASK) CHECK(GDK_MOD1_MASK) CHECK(GDK_MOD2_MASK) CHECK(GDK_MOD3_MASK) CHECK(GDK_MOD4_MASK) CHECK(GDK_MOD5_MASK) CHECK(GDK_BUTTON1_MASK) CHECK(GDK_BUTTON2_MASK) CHECK(GDK_BUTTON3_MASK) CHECK(GDK_BUTTON4_MASK) CHECK(GDK_BUTTON5_MASK) CHECK(GDK_SUPER_MASK) CHECK(GDK_HYPER_MASK) CHECK(GDK_META_MASK) CHECK(GDK_RELEASE_MASK) #undef CHECK return 0; } ::LString LApp::KeyModFlags::FlagsToString(int s) { ::LString::Array a; for (int i=0; i<32; i++) { if (((1 << i) & s) != 0) a.New() = FlagName(1 << i); } return ::LString(",").Join(a); } LApp::KeyModFlags *LApp::GetKeyModFlags() { return d->GetModFlags(); } const char *LApp::GetArgumentAt(int n) { return n >= 0 && n < d->Args.Args ? NewStr(d->Args.Arg[n]) : 0; } bool LApp::GetOption(const char *Option, char *Dest, int DestLen) { ::LString Buf; if (GetOption(Option, Buf)) { if (Dest) { if (DestLen > 0) { strcpy_s(Dest, DestLen, Buf); } else return false; } return true; } return false; } bool LApp::GetOption(const char *Option, ::LString &Buf) { if (IsOk() && Option) { int OptLen = strlen(Option); for (int i=1; iArgs.Args; i++) { char *a = d->Args.Arg[i]; if (!a) continue; if (strchr("-/\\", a[0])) { if (strnicmp(a+1, Option, OptLen) == 0) { char *Arg = 0; if (strlen(a+1+OptLen) > 0) { Arg = a + 1 + OptLen; } else if (i < d->Args.Args - 1) { Arg = d->Args.Arg[i + 1]; } if (Arg) { if (strchr("\'\"", *Arg)) { char Delim = *Arg++; char *End = strchr(Arg, Delim); if (End) { auto Len = End-Arg; if (Len > 0) Buf.Set(Arg, Len); else return false; } else return false; } else { Buf = Arg; } } return true; } } } } return false; } void LApp::OnCommandLine() { ::LArray Files; for (int i=1; iArgs; i++) { char *a = GetAppArgs()->Arg[i]; if (LFileExists(a)) { Files.Add(NewStr(a)); } } // call app if (Files.Length() > 0) { OnReceiveFiles(Files); } // clear up Files.DeleteArrays(); } ::LString LApp::GetFileMimeType(const char *File) { ::LString Status; char Full[MAX_PATH_LEN] = ""; if (!LFileExists(File)) { // Look in the path LToken p(getenv("PATH"), LGI_PATH_SEPARATOR); for (int i=0; i 0) Status = s.SplitDelimit(":").Last().Strip(); } return Status; } } #endif #if HAS_LIB_MAGIC static bool MagicError = false; if (d->MagicLock.Lock(_FL)) { if (!d->hMagic && !MagicError) { d->hMagic = magic_open(MAGIC_MIME_TYPE); if (d->hMagic) { if (magic_load(d->hMagic, NULL) != 0) { printf("%s:%i - magic_load failed - %s\n", _FL, magic_error(d->hMagic)); magic_close(d->hMagic); d->hMagic = NULL; MagicError = true; } } else { printf("%s:%i - magic_open failed.\n", _FL); MagicError = true; } } if (d->hMagic && !MagicError) { const char *mt = magic_file(d->hMagic, File); if (mt) { Status = mt; } } d->MagicLock.Unlock(); if (Status) return Status; } #endif #if HAS_SHARED_MIME // freedesktop.org rocks... if (!d->Sm) { // Not loaded, go and try to load it... d->Sm = new LSharedMime; if (d->Sm && d->Sm->IsLoaded()) { d->Sm->mimetypes_init(); } } if (d->Sm && d->Sm->IsLoaded()) { // Loaded... char *m = (char*)d->Sm->mimetypes_get_file_type(File, MIMETYPES_CHECK_ALL); if (m) { #if HAS_FILE_CMD if (stricmp(m, "application/x-not-recognised") != 0) #endif { strcpy(Mime, m); return true; } } else { printf("%s:%i - mimetypes_get_file_type failed for '%s'\n", __FILE__, __LINE__, File); } } else { printf("%s:%i - Shared Mime not loaded!!!\n", __FILE__, __LINE__); } #endif #if HAS_FILE_CMD // doh! not installed... :( LStringPipe Output; char Args[256]; sprintf(Args, "-i \"%s\"", File); LSubProcess p("file", Args); if (p.Start()) { p.Communicate(&Output); char *Out = Output.NewStr(); if (Out) { char *s = strchr(Out, ':'); if (s && strchr(Out, '/')) { s += 2; char *e = s; while ( *e && ( IsAlpha(*e) || IsDigit(*e) || strchr(".-_/", *e) ) ) e++; *e = 0; Status.Reset(NewStr(s)); } DeleteArray(Out); } } #endif return Status; } -bool LApp::GetAppsForMimeType(char *Mime, ::LArray<::LAppInfo*> &Apps) +bool LApp::GetAppsForMimeType(const char *Mime, LArray<::LAppInfo> &Apps) { // Find alternative version of the MIME type (e.g. x-type and type). char AltMime[256]; strcpy(AltMime, Mime); char *s = strchr(AltMime, '/'); if (s) { s++; int Len = strlen(s) + 1; if (strnicmp(s, "x-", 2) == 0) { memmove(s, s+2, Len - 2); } else { memmove(s+2, s, Len); s[0] = 'x'; s[1] = '-'; } } LGetAppsForMimeType(Mime, Apps); LGetAppsForMimeType(AltMime, Apps); return Apps.Length() > 0; } #if defined(LINUX) LLibrary *LApp::GetWindowManagerLib() { if (this != NULL && !d->WmLib) { char Lib[32]; WindowManager Wm = LGetWindowManager(); switch (Wm) { case WM_Kde: strcpy(Lib, "liblgikde3"); break; case WM_Gnome: strcpy(Lib, "liblgignome2"); break; default: strcpy(Lib, "liblgiother"); break; } #ifdef _DEBUG strcat(Lib, "d"); #endif d->WmLib = new LLibrary(Lib, true); if (d->WmLib) { if (d->WmLib->IsLoaded()) { Proc_LgiWmInit WmInit = (Proc_LgiWmInit) d->WmLib->GetAddress("LgiWmInit"); if (WmInit) { WmInitParams Params; // Params.Dsp = XObject::XDisplay(); Params.Args = d->Args.Args; Params.Arg = d->Args.Arg; WmInit(&Params); } // else printf("%s:%i - Failed to find method 'LgiWmInit' in WmLib.\n", __FILE__, __LINE__); } // else printf("%s:%i - couldn't load '%s.so'\n", __FILE__, __LINE__, Lib); } // else printf("%s:%i - alloc error\n", __FILE__, __LINE__); } return d->WmLib && d->WmLib->IsLoaded() ? d->WmLib : 0; } void LApp::DeleteMeLater(LViewI *v) { d->DeleteLater.Add(v); } void LApp::SetClipBoardContent(OsView Hnd, ::LVariant &v) { // Store the clipboard data we will serve d->ClipData = v; } bool LApp::GetClipBoardContent(OsView Hnd, ::LVariant &v, ::LArray &Types) { return false; } #endif LSymLookup *LApp::GetSymLookup() { return d; } bool LApp::IsElevated() { #ifdef WIN32 LAssert(!"What API works here?"); return false; #else return geteuid() == 0; #endif } int LApp::GetCpuCount() { return 1; } LFontCache *LApp::GetFontCache() { if (!d->FontCache) d->FontCache.Reset(new LFontCache(SystemNormal)); return d->FontCache; } #ifdef LINUX LApp::DesktopInfo::DesktopInfo(const char *file) { File = file; Dirty = false; if (File) Serialize(false); } bool LApp::DesktopInfo::Serialize(bool Write) { ::LFile f; if (Write) { ::LFile::Path p(File); p--; if (!p.Exists()) return false; } else if (!LFileExists(File)) return false; if (!f.Open(File, Write?O_WRITE:O_READ)) { LgiTrace("%s:%i - Failed to open '%s'\n", _FL, File.Get()); return false; } if (Write) { f.SetSize(0); for (unsigned i=0; i= 0) { int e = l.Find("]", ++s); if (e >= 0) { Cur = &Data.New(); Cur->Name = l(s, e - s + 1); } } else if ((s = l.Find("=")) >= 0) { if (!Cur) Cur = &Data.New(); KeyPair &kp = Cur->Values.New(); kp.Key = l(0, s).Strip(); kp.Value = l(++s, -1).Strip(); // printf("Read '%s': '%s'='%s'\n", Cur->Name.Get(), kp.Key.Get(), kp.Value.Get()); } } } return true; } LApp::DesktopInfo::Section *LApp::DesktopInfo::GetSection(const char *Name, bool Create) { for (unsigned i=0; iGet(Field, false, Dirty); if (kp) { return kp->Value; } } } return ::LString(); } bool LApp::DesktopInfo::Set(const char *Field, const char *Value, const char *Sect) { if (!Field) return false; Section *s = GetSection(Sect ? Sect : DefaultSection, true); if (!s) return false; KeyPair *kp = s->Get(Field, true, Dirty); if (!kp) return false; if (kp->Value != Value) { kp->Value = Value; Dirty = true; } return true; } LApp::DesktopInfo *LApp::GetDesktopInfo() { auto sExe = LGetExeFile(); ::LFile::Path Exe(sExe); ::LFile::Path Desktop(LSP_HOME); ::LString Leaf; Leaf.Printf("%s.desktop", Exe.Last().Get()); Desktop += ".local/share/applications"; Desktop += Leaf; const char *Ex = Exe; const char *Fn = Desktop; if (d->DesktopInfo.Reset(new DesktopInfo(Desktop))) { // Do a sanity check... ::LString s = d->DesktopInfo->Get("Name"); if (!s && Name()) d->DesktopInfo->Set("Name", Name()); s = d->DesktopInfo->Get("Exec"); if (!s || s != (const char*)sExe) d->DesktopInfo->Set("Exec", sExe); s = d->DesktopInfo->Get("Type"); if (!s) d->DesktopInfo->Set("Type", "Application"); s = d->DesktopInfo->Get("Categories"); if (!s) d->DesktopInfo->Set("Categories", "Application;"); s = d->DesktopInfo->Get("Terminal"); if (!s) d->DesktopInfo->Set("Terminal", "false"); d->DesktopInfo->Update(); } return d->DesktopInfo; } bool LApp::SetApplicationIcon(const char *FileName) { DesktopInfo *di = GetDesktopInfo(); if (!di) return false; ::LString IcoPath = di->Get("Icon"); if (IcoPath == FileName) return true; di->Set("Icon", FileName); return di->Update(); } #endif //////////////////////////////////////////////////////////////// OsApplication *OsApplication::Inst = 0; class OsApplicationPriv { public: OsApplicationPriv() { } }; OsApplication::OsApplication(int Args, char **Arg) { Inst = this; d = new OsApplicationPriv; } OsApplication::~OsApplication() { DeleteObj(d); Inst = 0; } //////////////////////////////////////////////////////////////// void LMessage::Set(int Msg, Param ParamA, Param ParamB) { m = Msg; a = ParamA; b = ParamB; } struct GlibEventParams : public LMessage { GtkWidget *w; }; bool GlibWidgetSearch(GtkWidget *p, GtkWidget *w, bool Debug, int depth) { char indent[256] = ""; if (Debug) { int ch = depth * 2; memset(indent, ' ', ch); indent[ch] = 0; printf("%sGlibWidgetSearch: %p, %p\n", indent, p, w); } if (p == w) return true; if (GTK_IS_CONTAINER(p)) { int n = 0; Gtk::GList *top = gtk_container_get_children(GTK_CONTAINER(p)); Gtk::GList *i = top; while (i) { if (Debug) printf("%s[%i]=%s\n", indent, n, gtk_widget_get_name((GtkWidget*)i->data)); if (i->data == w) return true; else if (GlibWidgetSearch((GtkWidget*)i->data, w, Debug, depth + 1)) return true; i = i->next; n++; } g_list_free(top); } else if (GTK_IS_BIN(p)) { GtkWidget *child = gtk_bin_get_child(GTK_BIN(p)); if (child) { if (Debug) printf("%schild=%s\n", indent, gtk_widget_get_name(child)); if (child == w) return true; else if (GlibWidgetSearch(child, w, Debug, depth + 1)) return true; } } else if (Debug) { printf("%sUnknown=%s\n", indent, gtk_widget_get_name(p)); } return false; } void LApp::OnDetach(LViewI *View) { LMessageQue::MsgArray *q = MsgQue.Lock(_FL); if (!q) { printf("%s:%i - Couldn't lock app.\n", _FL); return; } MsgQue.Unlock(); } bool LMessage::Send(LViewI *View) { if (!View) { LAssert(!"No view"); return false; } return LAppInst->PostEvent(View, m, a, b); } diff --git a/src/linux/Lgi/General.cpp b/src/linux/Lgi/General.cpp --- a/src/linux/Lgi/General.cpp +++ b/src/linux/Lgi/General.cpp @@ -1,644 +1,652 @@ // Linux Implementation of General LGI functions #include #include #include #include #include #include #define _POSIX_TIMERS #include #include "lgi/common/Lgi.h" #include "lgi/common/SubProcess.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Button.h" #ifndef LGI_SDL #include "LgiWinManGlue.h" #endif #include "lgi/common/Token.h" #include #define DEBUG_GET_APPS_FOR_MIMETYPE 0 //////////////////////////////////////////////////////////////// // Local helper functions bool _lgi_check_file(char *Path) { if (Path) { if (LFileExists(Path)) { // file is there return true; } else { // shortcut? char *e = Path + strlen(Path); strcpy(e, ".lnk"); if (LFileExists(Path)) { // resolve shortcut char Link[256]; if (LResolveShortcut(Path, Link, sizeof(Link))) { // check destination of link if (LFileExists(Link)) { strcpy(Path, Link); return true; } } } *e = 0; } } return false; } LString LCurrentUserName() { struct passwd *pw = getpwuid(geteuid()); if (pw) return pw->pw_name; return ""; } void LSleep(uint32_t i) { struct timespec request, remain; ZeroObj(request); ZeroObj(remain); request.tv_sec = i / 1000; request.tv_nsec = (i % 1000) * 1000000; //printf("%i LSleep(%i)\n", LGetCurrentThread(), i); while (nanosleep(&request, &remain) == -1) { request = remain; //printf("\t%i Resleeping=%i\n", LGetCurrentThread(), request.tv_sec*1000 + request.tv_nsec/1000); } } int GtkAssertDlg(const char *File, int Line, const char *Msg) { Gtk::GtkWidget *dialog = Gtk::gtk_message_dialog_new ( NULL, Gtk::GTK_DIALOG_DESTROY_WITH_PARENT, Gtk::GTK_MESSAGE_ERROR, Gtk::GTK_BUTTONS_NONE, "%s:%i - Assert failed:\n%s", File, Line, Msg ); Gtk::GtkDialog *dlg = GtkCast(dialog, gtk_dialog, GtkDialog); Gtk::gtk_dialog_add_buttons(dlg, "Break", 1, "Quit", 2, "Ignore", 3, NULL); int Result = Gtk::gtk_dialog_run(dlg); Gtk::gtk_widget_destroy(dialog); return Result; } void _lgi_assert(bool b, const char *test, const char *file, int line) { static bool Asserting = false; if (!b && !Asserting) { Asserting = true; printf("%s:%i - Assert failed:\n%s\n", file, line, test); #ifdef LGI_SDL exit(-1); #else Gtk::gint Result = Gtk::GTK_RESPONSE_NO; #if 1 if (LAppInst->InThread()) Result = GtkAssertDlg(file, line, test); else Result = 1; #endif switch (Result) { case 1: { int *i = NULL; *i = 0; break; } case 2: { exit(-1); break; } } #endif Asserting = false; } } //////////////////////////////////////////////////////////////////////// // Implementations LMessage CreateMsg(int m, int a, int b) { static LMessage Msg(0); Msg.Set(m, a, b); return Msg; } bool LGetMimeTypeExtensions(const char *Mime, LArray &Ext) { int Start = Ext.Length(); char *e; #define HardCodeExtention(Mime, Ext1, Ext2) \ else if (!stricmp(Mime, Mime)) \ { if (Ext1) Ext.Add(Ext1); \ if (Ext2) Ext.Add(Ext2); } const char *Null = NULL; if (!Mime); HardCodeExtention("text/calendar", "ics", Null) HardCodeExtention("text/x-vcard", "vcf", Null) HardCodeExtention("text/mbox", "mbx", "mbox"); return Ext.Length() > Start; } LString LGetFileMimeType(const char *File) { return LAppInst->GetFileMimeType(File); } bool _GetSystemFont(char *FontType, char *Font, int FontBufSize, int &PointSize) { bool Status = false; #ifndef LGI_SDL LLibrary *WmLib = LAppInst->GetWindowManagerLib(); if (WmLib) { Proc_LgiWmGetSysFont GetSysFont = (Proc_LgiWmGetSysFont) WmLib->GetAddress("LgiWmGetSysFont"); if (GetSysFont) { Status = GetSysFont(FontType, Font, FontBufSize, PointSize); if (!Status) { printf("%s:%i - GetSysFont failed\n", _FL); } } else { printf("%s:%i - Entry point doesn't exist\n", _FL); } } else #endif { static bool Warn = true; if (Warn) { printf("%s:%i - GetWindowManagerLib failed\n", _FL); Warn = false; } } return Status; } -bool LGetAppsForMimeType(const char *Mime, LArray &Apps, int Limit) +bool LGetAppsForMimeType(const char *Mime, LArray &Apps, int Limit) { bool Status = false; char Args[MAX_PATH_LEN]; sprintf(Args, "query default %s", Mime); LStringPipe Output; LLanguage *CurLang = LGetLanguageId(); char LangName[64]; sprintf_s(LangName, sizeof(LangName), "Name[%s]", CurLang ? CurLang->Id : "en"); #if DEBUG_GET_APPS_FOR_MIMETYPE printf("LGetAppsForMimeType('%s', ..., %i)\nRunning 'xdg-mime %s'\n", Mime, Limit, Args); #endif - LSubProcess p("xdg-mime", Args); - if (p.Start()) + LSubProcess proc("xdg-mime", Args); + if (proc.Start()) + { + LgiTrace("%s:%i - Failed to execute xdg-mime\n", _FL); + return Status; + } + + proc.Communicate(&Output); + LAutoString o(Output.NewStr()); + + #if DEBUG_GET_APPS_FOR_MIMETYPE + printf("Output:\n%s\n", o.Get()); + #endif + if (o) + { + LgiTrace("%s:%i - No output from xdg-mime\n", _FL); + return Status; + } + + char *e = o + strlen(o); + while (e > o && strchr(" \t\r\n", e[-1])) + *(--e) = 0; + + char p[MAX_PATH_LEN]; + if (LMakePath(p, sizeof(p), "/usr/share/applications", o)) { - p.Communicate(&Output); - LAutoString o(Output.NewStr()); + LgiTrace("%s:%i - Failed to create path.\n", _FL); + return Status; + } + + if (LFileExists(p)) + { + LgiTrace("%s:%i - '%s' doesn't exist.", _FL, p); + return Status; + } + + LAutoString txt(LReadTextFile(p)); + LAutoString Section; + + #if DEBUG_GET_APPS_FOR_MIMETYPE + printf("Reading '%s', got %i bytes\n", p, strlen(txt)); + #endif + + if (txt) + { + LgiTrace("%s:%i - Can't read from '%s'\n", _FL, p); + return Status; + } + + auto &ai = Apps.New(); + + LToken t(txt, "\n"); + for (int i=0; i o && strchr(" \t\r\n", e[-1])) - *(--e) = 0; - - char p[MAX_PATH_LEN]; - if (LMakePath(p, sizeof(p), "/usr/share/applications", o)) + Var++; + char *End = strchr(Var, ']'); + if (End) { - if (LFileExists(p)) + Section.Reset(NewStr(Var, End - Var)); + } + } + else if (!Section || !stricmp(Section, "Desktop Entry")) + { + char *Value = strchr(Var, '='); + if (Value) + { + *Value++ = 0; + if (!stricmp(Var, "Exec")) { - LAutoString txt(LReadTextFile(p)); - LAutoString Section; - - #if DEBUG_GET_APPS_FOR_MIMETYPE - printf("Reading '%s', got %i bytes\n", p, strlen(txt)); - #endif - - if (txt) - { - LAppInfo *ai = new LAppInfo; - Apps.Add(ai); - - LToken t(txt, "\n"); - for (int i=0; iPath.Set(exe, sp-exe); - else - ai->Path = exe; - - Status = true; - } - else if (!stricmp(Var, "Name") || - !stricmp(Var, LangName)) - { - ai->Name = Value; - } - } - } - } - - #if DEBUG_GET_APPS_FOR_MIMETYPE - printf(" ai='%s' '%s'\n", ai->Name.Get(), ai->Path.Get()); - #endif - } - else LgiTrace("%s:%i - Can't read from '%s'\n", _FL, p); - } - else LgiTrace("%s:%i - '%s' doesn't exist.", _FL, p); - } - else LgiTrace("%s:%i - Failed to create path.\n", _FL); - } - else LgiTrace("%s:%i - No output from xdg-mime\n", _FL); - } - else LgiTrace("%s:%i - Failed to execute xdg-mime\n", _FL); + #if DEBUG_GET_APPS_FOR_MIMETYPE + printf(" ai='%s' '%s'\n", ai.Name.Get(), ai.Path.Get()); + #endif return Status; } LString LGetAppForMimeType(const char *Mime) { LString App; - LArray Apps; + LArray Apps; if (LGetAppsForMimeType(Mime, Apps, 1)) - App = Apps[0]->Path.Get(); - Apps.DeleteObjects(); + App = Apps[0].Path.Get(); return App; } int LRand(int Limit) { return rand() % Limit; } LString LErrorCodeToString(uint32_t ErrorCode) { LString e = strerror(ErrorCode); if (!e) e.Printf("UnknownError(%i)", ErrorCode); return e; } bool LExecute(const char *File, const char *Args, const char *Dir, LString *Error) { if (File) { bool IsUrl = false; char App[MAX_PATH_LEN] = ""; if (strnicmp(File, "http://", 7) == 0 || strnicmp(File, "https://", 8) == 0) { IsUrl = true; LGetAppForMimeType("text/html", App, sizeof(App)); } else { struct stat f; char Path[MAX_PATH_LEN]; // see if the file is executable bool InPath = false; bool Ok = stat(File, &f) == 0; if (Ok) { strcpy_s(Path, sizeof(Path), File); } else { // look in the path InPath = true; LToken p(getenv("PATH"), LGI_PATH_SEPARATOR); for (int i=0; iPrintf("'%s' doesn't exist.\n", File); } if (App[0]) { bool FileAdded = false; LString AppPath; LString EscFile = LString::Escape(File, -1, " &"); LString a = App; int Pos; while ((Pos = a.Find("%")) >= 0) { char *s = a.Get() + Pos; printf("a='%s'\n", a.Get()); switch (s[1]) { case 'f': case 'F': { a = a(0,Pos) + EscFile + a(Pos+2,-1); FileAdded = true; break; } case 'u': case 'U': { if (IsUrl) a = a(0,Pos) + EscFile + a(Pos+2,-1); else a = a(0,Pos) + LString("file:") + EscFile + a(Pos+2,-1); FileAdded = true; break; } default: { // we don't understand this command a = a(0,Pos) + a(Pos+2,-1); break; } } printf("a='%s'\n", a.Get()); } if (!FileAdded) { a += " "; a += EscFile; } a += " > /dev/null 2> /dev/null &"; int e; if (Dir) chdir(Dir); printf("a=\n%s\n", a.Get()); if (e = system(a)) { if (Error) *Error = LErrorCodeToString(errno); return false; } return true; } } return false; } ////////////////////////////////////////////////////////////////////////// WindowManager LGetWindowManager() { static WindowManager Status = WM_Unknown; if (Status == WM_Unknown) { LDirectory d; for (bool b=d.First("/proc"); b && Status == WM_Unknown; b=d.Next()) { if (d.IsDir() && isdigit(d.GetName()[0])) { char Path[256]; d.Path(Path, sizeof(Path)); LMakePath(Path, sizeof(Path), Path, "status"); LFile s; if (s.Open(Path, O_READ)) { char Buf[256]; Buf[sizeof(Buf)-1] = 0; s.Read(Buf, sizeof(Buf)-1); char *n = strchr(Buf, '\n'); if (n) { *n = 0; if (stristr(Buf, "gnome-settings") != 0 || stristr(Buf, "gnome-session") != 0 || stristr(Buf, "gnome-panel") != 0) { Status = WM_Gnome; } else if (stristr(Buf, "startkde") != 0 || stristr(Buf, "kdesktop") != 0) { Status = WM_Kde; } } } else printf("%s:%i - error\n", __FILE__, __LINE__); } } } return Status; } void LFinishXWindowsStartup(LViewI *Wnd) { // Get the startup ID const char *EnvStartId = "DESKTOP_STARTUP_ID"; char *DesktopStartupId = getenv(EnvStartId); if (ValidStr(DesktopStartupId)) { LStringPipe oss; // Create remove string oss.Push("remove: ID="); // Quote the id string for (char *c = DesktopStartupId; *c; c++) { if (*c == ' ' || *c == '"' || *c == '\\') { oss.Write((char*)"\\", 1); } oss.Write(c, 1); } char *Str = oss.NewStr(); // Get the window and display /* XWidget *View = Wnd->Handle(); if (!View) return; Display *display = View->XDisplay(); Window xroot_window = DefaultRootWindow(display); XSetWindowAttributes attrs; attrs.override_redirect = True; attrs.event_mask = PropertyChangeMask | StructureNotifyMask; // Get the atoms Atom type_atom = XInternAtom(display, "_NET_STARTUP_INFO", false); Atom type_atom_begin = XInternAtom(display, "_NET_STARTUP_INFO_BEGIN", false); // Create the event we will send XEvent xevent; xevent.xclient.type = ClientMessage; xevent.xclient.message_type = type_atom_begin; xevent.xclient.display = display; xevent.xclient.window = View->handle(); xevent.xclient.format = 8; const char* src = Str; const char* src_end = src + strlen(Str) + 1; // Include trailing NUL. // Loop over the string and send it. while (src != src_end) { char* dest = &xevent.xclient.data.b[0]; char* dest_end = dest + 20; while (dest != dest_end && src != src_end) { *dest++ = *src++; } while (dest != dest_end) { *dest++ = 0; } printf("%s:%i - XSendEvent\n", __FILE__, __LINE__); XSendEvent(display, xroot_window, False, PropertyChangeMask, &xevent); xevent.xclient.message_type = type_atom; } // Clear the event ID so it's not inherited by child processes. unsetenv(EnvStartId); */ } } #if HAS_GSTREAMER // sudo apt-get install libgstreamer1.0-dev using namespace Gtk; #include #endif bool LPlaySound(const char *FileName, int ASync) { #if HAS_GSTREAMER #else return LExecute(FileName); #endif }