File: application.cpp

package info (click to toggle)
ares 126-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 32,600 kB
  • sloc: cpp: 356,508; ansic: 20,394; makefile: 16; sh: 2
file content (207 lines) | stat: -rw-r--r-- 7,652 bytes parent folder | download | duplicates (2)
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