1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
|
#if defined(Hiro_Application)
#include <nall/terminal.hpp>
namespace hiro {
auto Log_Ignore(const char* logDomain, GLogLevelFlags logLevel, const char* message, void* userData) -> void {
}
auto Log_Filter(const char* logDomain, GLogLevelFlags logLevel, const char* message, void* userData) -> void {
//FreeBSD 12.0: caused by gtk_combo_box_size_allocate() internal function being defective
if(string{message}.find("gtk_widget_size_allocate():")) return;
//print all other messages
print(terminal::color::yellow("hiro: "), logDomain, "::", message, "\n");
}
auto pApplication::exit() -> void {
quit();
::exit(EXIT_SUCCESS);
}
auto pApplication::modal() -> bool {
return Application::state().modal > 0;
}
auto pApplication::run() -> void {
while(!Application::state().quit) {
Application::doMain();
processEvents();
//avoid spinlooping the thread when there is no main loop ...
//when there is one, Application::onMain() is expected to sleep when possible instead
if(!Application::state().onMain) usleep(2000);
}
}
auto pApplication::pendingEvents() -> bool {
return gtk_events_pending();
}
auto pApplication::processEvents() -> void {
//GTK can sometimes return gtk_pending_events() == true forever,
//no matter how many times gtk_main_iteration_do() is called.
//implement a timeout to prevent hiro from hanging forever in this case.
auto time = chrono::millisecond();
while(pendingEvents() && chrono::millisecond() - time < 50) {
gtk_main_iteration_do(false);
}
for(auto& window : state().windows) window->_synchronizeGeometry();
}
auto pApplication::quit() -> void {
//if gtk_main() was invoked, call gtk_main_quit()
if(gtk_main_level()) gtk_main_quit();
#if defined(DISPLAY_XORG)
if(state().display) {
if(state().screenSaverXDG && state().screenSaverWindow) {
//this needs to run synchronously, so that XUnmapWindow() won't happen before xdg-screensaver is finished
execute("xdg-screensaver", "resume", string{"0x", hex(state().screenSaverWindow)});
XUnmapWindow(state().display, state().screenSaverWindow);
state().screenSaverWindow = 0;
}
XCloseDisplay(state().display);
state().display = nullptr;
}
#endif
}
auto pApplication::setScreenSaver(bool screenSaver) -> void {
#if defined(DISPLAY_XORG)
if(state().screenSaverXDG && state().screenSaverWindow) {
//when invoking this command on Linux under Xfce, the follow message is written to the terminal:
//"org.freedesktop.DBus.Error.ServiceUnknown: The name org.gnome.SessionManager was not provided by any .service files"
//to silence this message, stdout and stderr are redirected to /dev/null while invoking this command.
auto fd = open("/dev/null", O_NONBLOCK);
auto fo = dup(STDOUT_FILENO);
auto fe = dup(STDERR_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
invoke("xdg-screensaver", screenSaver ? "resume" : "suspend", string{"0x", hex(state().screenSaverWindow)});
dup2(fo, STDOUT_FILENO);
dup2(fe, STDERR_FILENO);
close(fd);
close(fo);
close(fe);
}
#endif
}
auto pApplication::state() -> State& {
static State state;
return state;
}
auto pApplication::initialize() -> void {
#if defined(DISPLAY_XORG)
// If running on Wayland, force usage of XWayland
setenv("GDK_BACKEND", "x11", 1);
XInitThreads();
state().display = XOpenDisplay(nullptr);
state().screenSaverXDG = (bool)execute("xdg-screensaver", "--version").output.find("xdg-screensaver");
if(state().screenSaverXDG) {
auto screen = DefaultScreen(state().display);
auto rootWindow = RootWindow(state().display, screen);
XSetWindowAttributes attributes{};
attributes.background_pixel = BlackPixel(state().display, screen);
attributes.border_pixel = 0;
attributes.override_redirect = true;
state().screenSaverWindow = XCreateWindow(state().display, rootWindow,
0, 0, 1, 1, 0, DefaultDepth(state().display, screen),
InputOutput, DefaultVisual(state().display, screen),
CWBackPixel | CWBorderPixel | CWOverrideRedirect, &attributes
);
XStoreName(state().display, state().screenSaverWindow, "hiro-screen-saver-window");
XFlush(state().display);
}
#endif
//prevent useless terminal messages:
//GVFS-RemoteVolumeMonitor: "invoking List() failed for type GProxyVolumeMonitorHal: method not implemented"
g_log_set_handler("GVFS-RemoteVolumeMonitor", G_LOG_LEVEL_MASK, Log_Ignore, nullptr);
//GLib-GIO: "excluding {path} from kernel notification, falling back to poll
g_log_set_handler("GLib-GIO", G_LOG_LEVEL_MASK, Log_Ignore, nullptr);
//Gtk: "gtk_widget_size_allocate(): attempt to allocate widget with (width or height < 1)"
g_log_set_handler("Gtk", G_LOG_LEVEL_MASK, Log_Filter, nullptr);
//set WM_CLASS to Application::name()
auto name = Application::state().name ? Application::state().name : string{"hiro"};
gdk_set_program_class(name);
#if 0 && defined(BUILD_DEBUG)
//force a trap on Gtk-CRITICAL and Gtk-WARNING messages
//this allows gdb to perform a backtrace to find an error's origin point
int argc = 3;
char* argv[] = {name.get(), new char[7], new char[19], nullptr};
strcpy(argv[1], "--sync");
strcpy(argv[2], "--g-fatal-warnings");
g_log_set_always_fatal(GLogLevelFlags(G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING));
#else
int argc = 1;
char* argv[] = {name.get(), nullptr};
#endif
char** argvp = argv;
gtk_init(&argc, &argvp);
GtkSettings* gtkSettings = gtk_settings_get_default();
//allow buttons to show icons
g_type_class_unref(g_type_class_ref(GTK_TYPE_BUTTON));
g_object_set(gtkSettings, "gtk-button-images", true, nullptr);
#if defined(DISPLAY_WINDOWS)
//there is a serious bug in GTK 2.24 for Windows with the "ime" (Windows IME) input method:
//by default, it will be impossible to type in text fields at all.
//there are various tricks to get around this; but they are unintuitive and unreliable.
//the "ime" method is chosen when various international system locales (eg Japanese) are selected.
//here, we override the default input method to use the "Simple" type instead to avoid the bug.
//obviously, this has a drawback: in-place editing for IMEs will not work in this mode.
g_object_set(gtkSettings, "gtk-im-module", "gtk-im-context-simple", nullptr);
#endif
#if HIRO_GTK==2
gtk_rc_parse_string(R"(
style "HiroWindow"
{
GtkWindow::resize-grip-width = 0
GtkWindow::resize-grip-height = 0
}
class "GtkWindow" style "HiroWindow"
style "HiroTreeView"
{
GtkTreeView::vertical-separator = 0
}
class "GtkTreeView" style "HiroTreeView"
style "HiroTabFrameCloseButton"
{
GtkWidget::focus-line-width = 0
GtkWidget::focus-padding = 0
GtkButton::default-border = {0, 0, 0, 0}
GtkButton::default-outer-border = {0, 0, 0, 0}
GtkButton::inner-border = {0, 1, 0, 0}
}
widget_class "*.<GtkNotebook>.<GtkHBox>.<GtkButton>" style "HiroTabFrameCloseButton"
)");
#elif HIRO_GTK==3
GtkCssProvider* provider = gtk_css_provider_new();
gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(provider), R"(
* { font-size: 8pt; padding-top: 0px; padding-bottom: 0px; }
menu { border-width: 0px; }
menubar { background-color: gray; }
menuitem { padding: 3px 5px; }
entry { min-height: 0px; }
)", -1, nullptr);
GdkScreen* screen = gdk_screen_get_default();
gtk_style_context_add_provider_for_screen(screen, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
#endif
pKeyboard::initialize();
}
}
#endif
|