ardour
keyboard.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2001 Paul Davis
3 
4  This program is free software; you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or
7  (at your option) any later version.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU General Public License for more details.
13 
14  You should have received a copy of the GNU General Public License
15  along with this program; if not, write to the Free Software
16  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 
18 */
19 
20 #include <vector>
21 
22 #include <algorithm>
23 #include <fstream>
24 #include <iostream>
25 
26 #include <cerrno>
27 #include <ctype.h>
28 
29 #include <glib/gstdio.h>
30 
31 #include <gtkmm/widget.h>
32 #include <gtkmm/window.h>
33 #include <gtkmm/accelmap.h>
34 #include <gdk/gdkkeysyms.h>
35 
36 #include "pbd/error.h"
37 #include "pbd/convert.h"
38 #include "pbd/file_utils.h"
39 #include "pbd/search_path.h"
40 #include "pbd/xml++.h"
41 #include "pbd/debug.h"
42 #include "pbd/unwind.h"
43 
44 #include "gtkmm2ext/keyboard.h"
45 #include "gtkmm2ext/actions.h"
46 #include "gtkmm2ext/debug.h"
47 
48 #include "i18n.h"
49 
50 using namespace PBD;
51 using namespace Gtk;
52 using namespace Gtkmm2ext;
53 using namespace std;
54 
55 guint Keyboard::edit_but = 3;
56 guint Keyboard::edit_mod = GDK_CONTROL_MASK;
57 guint Keyboard::delete_but = 3;
58 guint Keyboard::delete_mod = GDK_SHIFT_MASK;
59 guint Keyboard::insert_note_but = 1;
60 guint Keyboard::insert_note_mod = GDK_CONTROL_MASK;
61 guint Keyboard::snap_mod = GDK_MOD3_MASK;
62 
63 #ifdef GTKOSX
64 
65 uint Keyboard::PrimaryModifier = GDK_MOD2_MASK; // Command
66 guint Keyboard::SecondaryModifier = GDK_CONTROL_MASK; // Control
67 guint Keyboard::TertiaryModifier = GDK_SHIFT_MASK; // Shift
68 guint Keyboard::Level4Modifier = GDK_MOD1_MASK; // Alt/Option
69 guint Keyboard::CopyModifier = GDK_CONTROL_MASK; // Control
70 guint Keyboard::RangeSelectModifier = GDK_SHIFT_MASK;
71 guint Keyboard::button2_modifiers = Keyboard::SecondaryModifier|Keyboard::Level4Modifier;
72 
73 const char* Keyboard::primary_modifier_name() { return _("Command"); }
74 const char* Keyboard::secondary_modifier_name() { return _("Control"); }
75 const char* Keyboard::tertiary_modifier_name() { return S_("Key|Shift"); }
76 const char* Keyboard::level4_modifier_name() { return _("Option"); }
77 const char* Keyboard::copy_modifier_name() { return _("Control"); }
78 const char* Keyboard::rangeselect_modifier_name() { return S_("Key|Shift"); }
79 
80 #else
81 
82 guint Keyboard::PrimaryModifier = GDK_CONTROL_MASK; // Control
83 guint Keyboard::SecondaryModifier = GDK_MOD1_MASK; // Alt/Option
84 guint Keyboard::TertiaryModifier = GDK_SHIFT_MASK; // Shift
85 guint Keyboard::Level4Modifier = GDK_MOD4_MASK; // Mod4/Windows
86 guint Keyboard::CopyModifier = GDK_CONTROL_MASK;
87 guint Keyboard::RangeSelectModifier = GDK_SHIFT_MASK;
88 guint Keyboard::button2_modifiers = 0; /* not used */
89 
90 const char* Keyboard::primary_modifier_name() { return _("Control"); }
91 const char* Keyboard::secondary_modifier_name() { return _("Alt"); }
92 const char* Keyboard::tertiary_modifier_name() { return S_("Key|Shift"); }
93 const char* Keyboard::level4_modifier_name() { return _("Meta"); }
94 const char* Keyboard::copy_modifier_name() { return _("Control"); }
95 const char* Keyboard::rangeselect_modifier_name() { return S_("Key|Shift"); }
96 
97 #endif
98 
99 guint Keyboard::GainFineScaleModifier = Keyboard::PrimaryModifier;
100 guint Keyboard::GainExtraFineScaleModifier = Keyboard::SecondaryModifier;
101 
102 guint Keyboard::ScrollZoomVerticalModifier = Keyboard::SecondaryModifier;
103 guint Keyboard::ScrollZoomHorizontalModifier = Keyboard::PrimaryModifier;
104 guint Keyboard::ScrollHorizontalModifier = Keyboard::TertiaryModifier;
105 
106 
107 Keyboard* Keyboard::_the_keyboard = 0;
108 Gtk::Window* Keyboard::current_window = 0;
109 bool Keyboard::_some_magic_widget_has_focus = false;
110 
111 std::string Keyboard::user_keybindings_path;
112 bool Keyboard::can_save_keybindings = false;
113 bool Keyboard::bindings_changed_after_save_became_legal = false;
114 map<string,string> Keyboard::binding_files;
115 string Keyboard::_current_binding_name;
116 map<AccelKey,pair<string,string>,Keyboard::AccelKeyLess> Keyboard::release_keys;
117 
118 /* set this to initially contain the modifiers we care about, then track changes in ::set_edit_modifier() etc. */
119 
120 GdkModifierType Keyboard::RelevantModifierKeyMask;
121 
122 void
123 Keyboard::magic_widget_grab_focus ()
124 {
125  _some_magic_widget_has_focus = true;
126 }
127 
128 void
129 Keyboard::magic_widget_drop_focus ()
130 {
131  _some_magic_widget_has_focus = false;
132 }
133 
134 bool
135 Keyboard::some_magic_widget_has_focus ()
136 {
137  return _some_magic_widget_has_focus;
138 }
139 
141 {
142  if (_the_keyboard == 0) {
143  _the_keyboard = this;
144  _current_binding_name = _("Unknown");
145  }
146 
147  RelevantModifierKeyMask = (GdkModifierType) gtk_accelerator_get_default_mod_mask ();
148 
149  RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | PrimaryModifier);
150  RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | SecondaryModifier);
151  RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | TertiaryModifier);
152  RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | Level4Modifier);
153  RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | CopyModifier);
154  RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | RangeSelectModifier);
155 
156  gtk_accelerator_set_default_mod_mask (RelevantModifierKeyMask);
157 
158  snooper_id = gtk_key_snooper_install (_snooper, (gpointer) this);
159 }
160 
161 Keyboard::~Keyboard ()
162 {
163  gtk_key_snooper_remove (snooper_id);
164 }
165 
166 XMLNode&
167 Keyboard::get_state (void)
168 {
169  XMLNode* node = new XMLNode ("Keyboard");
170  char buf[32];
171 
172  snprintf (buf, sizeof (buf), "%d", edit_but);
173  node->add_property ("edit-button", buf);
174  snprintf (buf, sizeof (buf), "%d", edit_mod);
175  node->add_property ("edit-modifier", buf);
176  snprintf (buf, sizeof (buf), "%d", delete_but);
177  node->add_property ("delete-button", buf);
178  snprintf (buf, sizeof (buf), "%d", delete_mod);
179  node->add_property ("delete-modifier", buf);
180  snprintf (buf, sizeof (buf), "%d", snap_mod);
181  node->add_property ("snap-modifier", buf);
182  snprintf (buf, sizeof (buf), "%d", insert_note_but);
183  node->add_property ("insert-note-button", buf);
184  snprintf (buf, sizeof (buf), "%d", insert_note_mod);
185  node->add_property ("insert-note-modifier", buf);
186 
187  return *node;
188 }
189 
190 int
191 Keyboard::set_state (const XMLNode& node, int /*version*/)
192 {
193  const XMLProperty* prop;
194 
195  if ((prop = node.property ("edit-button")) != 0) {
196  sscanf (prop->value().c_str(), "%d", &edit_but);
197  }
198 
199  if ((prop = node.property ("edit-modifier")) != 0) {
200  sscanf (prop->value().c_str(), "%d", &edit_mod);
201  }
202 
203  if ((prop = node.property ("delete-button")) != 0) {
204  sscanf (prop->value().c_str(), "%d", &delete_but);
205  }
206 
207  if ((prop = node.property ("delete-modifier")) != 0) {
208  sscanf (prop->value().c_str(), "%d", &delete_mod);
209  }
210 
211  if ((prop = node.property ("snap-modifier")) != 0) {
212  sscanf (prop->value().c_str(), "%d", &snap_mod);
213  }
214 
215  if ((prop = node.property ("insert-note-button")) != 0) {
216  sscanf (prop->value().c_str(), "%d", &insert_note_but);
217  }
218 
219  if ((prop = node.property ("insert-note-modifier")) != 0) {
220  sscanf (prop->value().c_str(), "%d", &insert_note_mod);
221  }
222 
223  return 0;
224 }
225 
226 gint
227 Keyboard::_snooper (GtkWidget *widget, GdkEventKey *event, gpointer data)
228 {
229  return ((Keyboard *) data)->snooper (widget, event);
230 }
231 
232 gint
233 Keyboard::snooper (GtkWidget *widget, GdkEventKey *event)
234 {
235  uint32_t keyval;
236  bool ret = false;
237 
238  DEBUG_TRACE (
241  "Snoop widget %1 name: [%6] key %2 type %3 state %4 magic %5\n",
242  widget, event->keyval, event->type, event->state, _some_magic_widget_has_focus,
243  gtk_widget_get_name (widget)
244  )
245  );
246 
247  if (event->keyval == GDK_Shift_R) {
248  keyval = GDK_Shift_L;
249 
250  } else if (event->keyval == GDK_Control_R) {
251  keyval = GDK_Control_L;
252 
253  } else {
254  keyval = event->keyval;
255  }
256 
257  if (event->state & ScrollZoomVerticalModifier) {
258  /* There is a special and rather hacky situation in Editor which makes
259  it useful to know when the modifier key for vertical zoom has been
260  released, so emit a signal here (see Editor::_stepping_axis_view).
261  Note that the state bit for the modifier key is set for the key-up
262  event when the modifier is released, but not the key-down when it
263  is pressed, so we get here on key-up, which is what we want.
264  */
265  ZoomVerticalModifierReleased (); /* EMIT SIGNAL */
266  }
267 
268  if (event->type == GDK_KEY_PRESS) {
269 
270  if (find (state.begin(), state.end(), keyval) == state.end()) {
271  state.push_back (keyval);
272  sort (state.begin(), state.end());
273 
274  } else {
275 
276  /* key is already down. if its also used for release,
277  prevent auto-repeat events.
278  */
279 
280  for (map<AccelKey,two_strings,AccelKeyLess>::iterator k = release_keys.begin(); k != release_keys.end(); ++k) {
281 
282  const AccelKey& ak (k->first);
283 
284  if (keyval == ak.get_key() && (Gdk::ModifierType)((event->state & Keyboard::RelevantModifierKeyMask) | Gdk::RELEASE_MASK) == ak.get_mod()) {
285  DEBUG_TRACE (DEBUG::Keyboard, "Suppress auto repeat\n");
286  ret = true;
287  break;
288  }
289  }
290  }
291 
292  } else if (event->type == GDK_KEY_RELEASE) {
293 
294  State::iterator i;
295 
296  if ((i = find (state.begin(), state.end(), keyval)) != state.end()) {
297  state.erase (i);
298  sort (state.begin(), state.end());
299  }
300 
301  for (map<AccelKey,two_strings,AccelKeyLess>::iterator k = release_keys.begin(); k != release_keys.end(); ++k) {
302 
303  const AccelKey& ak (k->first);
304  two_strings ts (k->second);
305 
306  if (keyval == ak.get_key() && (Gdk::ModifierType)((event->state & Keyboard::RelevantModifierKeyMask) | Gdk::RELEASE_MASK) == ak.get_mod()) {
307  Glib::RefPtr<Gtk::Action> act = ActionManager::get_action (ts.first.c_str(), ts.second.c_str());
308  if (act) {
309  DEBUG_TRACE (DEBUG::Keyboard, string_compose ("Activate %1 %2\n", ts.first, ts.second));
310  act->activate();
311  DEBUG_TRACE (DEBUG::Keyboard, string_compose ("Use repeat, suppress other\n", ts.first, ts.second));
312  ret = true;
313  }
314  break;
315  }
316  }
317  }
318 
319  /* Special keys that we want to handle in
320  any dialog, no matter whether it uses
321  the regular set of accelerators or not
322  */
323 
324  if (event->type == GDK_KEY_RELEASE && modifier_state_equals (event->state, PrimaryModifier)) {
325  switch (event->keyval) {
326  case GDK_w:
327  close_current_dialog ();
328  ret = true;
329  break;
330  }
331  }
332 
333  DEBUG_TRACE (DEBUG::Keyboard, string_compose ("snooper returns %1\n", ret));
334 
335  return ret;
336 }
337 
338 void
339 Keyboard::close_current_dialog ()
340 {
341  if (current_window) {
342  current_window->hide ();
343  current_window = 0;
344  }
345 }
346 
347 bool
348 Keyboard::key_is_down (uint32_t keyval)
349 {
350  return find (state.begin(), state.end(), keyval) != state.end();
351 }
352 
353 bool
354 Keyboard::enter_window (GdkEventCrossing *, Gtk::Window* win)
355 {
356  current_window = win;
357  DEBUG_TRACE (DEBUG::Keyboard, string_compose ("Entering window, title = %1\n", win->get_title()));
358  return false;
359 }
360 
361 bool
362 Keyboard::leave_window (GdkEventCrossing *ev, Gtk::Window* /*win*/)
363 {
364  if (ev) {
365  switch (ev->detail) {
366  case GDK_NOTIFY_INFERIOR:
367  DEBUG_TRACE (DEBUG::Keyboard, "INFERIOR crossing ... out\n");
368  break;
369 
370  case GDK_NOTIFY_VIRTUAL:
371  DEBUG_TRACE (DEBUG::Keyboard, "VIRTUAL crossing ... out\n");
372  /* fallthru */
373 
374  default:
375  DEBUG_TRACE (DEBUG::Keyboard, "REAL crossing ... out\n");
376  DEBUG_TRACE (DEBUG::Keyboard, "Clearing current target\n");
377  state.clear ();
378  current_window = 0;
379  }
380  } else {
381  current_window = 0;
382  }
383 
384  return false;
385 }
386 
387 bool
388 Keyboard::focus_in_window (GdkEventFocus *, Gtk::Window* win)
389 {
390  current_window = win;
391  DEBUG_TRACE (DEBUG::Keyboard, string_compose ("Focusing in window, title = %1\n", win->get_title()));
392  return false;
393 }
394 
395 bool
396 Keyboard::focus_out_window (GdkEventFocus * ev, Gtk::Window* win)
397 {
398  if (ev) {
399  state.clear ();
400  current_window = 0;
401  } else {
402  current_window = 0;
403  }
404 
405  DEBUG_TRACE (DEBUG::Keyboard, string_compose ("Foucusing out window, title = %1\n", win->get_title()));
406 
407  return false;
408 }
409 
410 void
411 Keyboard::set_edit_button (guint but)
412 {
413  edit_but = but;
414 }
415 
416 void
417 Keyboard::set_edit_modifier (guint mod)
418 {
419  RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask & ~edit_mod);
420  edit_mod = mod;
421  RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | edit_mod);
422 }
423 
424 void
425 Keyboard::set_delete_button (guint but)
426 {
427  delete_but = but;
428 }
429 
430 void
431 Keyboard::set_delete_modifier (guint mod)
432 {
433  RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask & ~delete_mod);
434  delete_mod = mod;
435  RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | delete_mod);
436 }
437 
438 void
439 Keyboard::set_insert_note_button (guint but)
440 {
441  insert_note_but = but;
442 }
443 
444 void
445 Keyboard::set_insert_note_modifier (guint mod)
446 {
447  RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask & ~insert_note_mod);
448  insert_note_mod = mod;
449  RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | insert_note_mod);
450 }
451 
452 
453 void
454 Keyboard::set_modifier (uint32_t newval, uint32_t& var)
455 {
456  RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask & ~var);
457  var = newval;
458  RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | var);
459 }
460 
461 void
462 Keyboard::set_snap_modifier (guint mod)
463 {
464  RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask & ~snap_mod);
465  snap_mod = mod;
466  RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | snap_mod);
467 }
468 
469 bool
470 Keyboard::is_edit_event (GdkEventButton *ev)
471 {
472  return (ev->type == GDK_BUTTON_PRESS || ev->type == GDK_BUTTON_RELEASE) &&
473  (ev->button == Keyboard::edit_button()) &&
474  ((ev->state & RelevantModifierKeyMask) == Keyboard::edit_modifier());
475 }
476 
477 bool
478 Keyboard::is_insert_note_event (GdkEventButton *ev)
479 {
480  return (ev->type == GDK_BUTTON_PRESS || ev->type == GDK_BUTTON_RELEASE) &&
481  (ev->button == Keyboard::insert_note_button()) &&
482  ((ev->state & RelevantModifierKeyMask) == Keyboard::insert_note_modifier());
483 }
484 
485 bool
486 Keyboard::is_button2_event (GdkEventButton* ev)
487 {
488 #ifdef GTKOSX
489  return (ev->button == 2) ||
490  ((ev->button == 1) &&
491  ((ev->state & Keyboard::button2_modifiers) == Keyboard::button2_modifiers));
492 #else
493  return ev->button == 2;
494 #endif
495 }
496 
497 bool
498 Keyboard::is_delete_event (GdkEventButton *ev)
499 {
500  return (ev->type == GDK_BUTTON_PRESS || ev->type == GDK_BUTTON_RELEASE) &&
501  (ev->button == Keyboard::delete_button()) &&
502  ((ev->state & RelevantModifierKeyMask) == Keyboard::delete_modifier());
503 }
504 
505 bool
506 Keyboard::is_context_menu_event (GdkEventButton *ev)
507 {
508  return (ev->type == GDK_BUTTON_PRESS || ev->type == GDK_BUTTON_RELEASE) &&
509  (ev->button == 3) &&
510  ((ev->state & RelevantModifierKeyMask) == 0);
511 }
512 
513 bool
514 Keyboard::no_modifiers_active (guint state)
515 {
516  return (state & RelevantModifierKeyMask) == 0;
517 }
518 
519 bool
520 Keyboard::modifier_state_contains (guint state, ModifierMask mask)
521 {
522  return (state & mask) == (guint) mask;
523 }
524 
525 bool
526 Keyboard::modifier_state_equals (guint state, ModifierMask mask)
527 {
528  return (state & RelevantModifierKeyMask) == (guint) mask;
529 }
530 
531 void
532 Keyboard::keybindings_changed ()
533 {
534  if (Keyboard::can_save_keybindings) {
535  Keyboard::bindings_changed_after_save_became_legal = true;
536  }
537 
538  Keyboard::save_keybindings ();
539 }
540 
541 void
542 Keyboard::set_can_save_keybindings (bool yn)
543 {
544  can_save_keybindings = yn;
545 }
546 
547 void
548 Keyboard::save_keybindings ()
549 {
550  if (can_save_keybindings && bindings_changed_after_save_became_legal) {
551  Gtk::AccelMap::save (user_keybindings_path);
552  }
553 }
554 
555 bool
556 Keyboard::load_keybindings (string path)
557 {
558  try {
559  info << "Loading bindings from " << path << endl;
560 
561  Gtk::AccelMap::load (path);
562 
563  _current_binding_name = _("Unknown");
564 
565  for (map<string,string>::iterator x = binding_files.begin(); x != binding_files.end(); ++x) {
566  if (path == x->second) {
567  _current_binding_name = x->first;
568  break;
569  }
570  }
571 
572 
573  } catch (...) {
574  error << string_compose (_("key bindings file not found at \"%2\" or contains errors."), path)
575  << endmsg;
576  return false;
577  }
578 
579  /* now find all release-driven bindings */
580 
581  vector<string> groups;
582  vector<string> names;
583  vector<string> tooltips;
584  vector<AccelKey> bindings;
585 
586  ActionManager::get_all_actions (groups, names, tooltips, bindings);
587 
588  vector<string>::iterator g;
589  vector<AccelKey>::iterator b;
590  vector<string>::iterator n;
591 
592  release_keys.clear ();
593 
594  for (n = names.begin(), b = bindings.begin(), g = groups.begin(); n != names.end(); ++n, ++b, ++g) {
595  stringstream s;
596  s << "Action: " << *n << " Group: " << *g << " Binding: ";
597 
598  if ((*b).get_key() != GDK_VoidSymbol) {
599  s << b->get_key() << " w/mod " << hex << b->get_mod() << dec << " = " << b->get_abbrev () << "\n";
600  } else {
601  s << "unbound\n";
602  }
603 
604  DEBUG_TRACE (DEBUG::Bindings, s.str ());
605  }
606 
607  for (n = names.begin(), b = bindings.begin(), g = groups.begin(); n != names.end(); ++n, ++b, ++g) {
608  if ((*b).get_mod() & Gdk::RELEASE_MASK) {
609  release_keys.insert (pair<AccelKey,two_strings> (*b, two_strings (*g, *n)));
610  }
611  }
612 
613  return true;
614 }
615 
616 int
617 Keyboard::reset_bindings ()
618 {
619  if (Glib::file_test (user_keybindings_path, Glib::FILE_TEST_EXISTS)) {
620 
621  string new_path = user_keybindings_path;
622  new_path += ".old";
623 
624  if (::g_rename (user_keybindings_path.c_str(), new_path.c_str())) {
625  error << string_compose (_("Cannot rename your own keybinding file (%1)"), strerror (errno)) << endmsg;
626  return -1;
627  }
628  }
629 
630  {
631  PBD::Unwinder<bool> uw (can_save_keybindings, false);
632  setup_keybindings ();
633  }
634 
635  return 0;
636 }
const std::string & value() const
Definition: xml++.h:159
Definition: ardour_ui.h:130
LIBGTKMM2EXT_API Glib::RefPtr< Gtk::Action > get_action(const char *group, const char *name)
Definition: actions.cc:406
std::pair< std::string, std::string > two_strings
Definition: keyboard.h:196
Definition: Beats.hpp:239
LIBPBD_API Transmitter error
std::ostream & endmsg(std::ostream &ostr)
Definition: transmitter.h:71
#define _(Text)
Definition: i18n.h:11
LIBGTKMM2EXT_API uint64_t Keyboard
Definition: debug.cc:23
XMLProperty * property(const char *)
Definition: xml++.cc:413
LIBGTKMM2EXT_API uint64_t Bindings
Definition: debug.cc:24
#define DEBUG_TRACE(bits, str)
Definition: debug.h:55
LIBPBD_API Transmitter info
XMLProperty * add_property(const char *name, const std::string &value)
Definition: xml++.h:95
Definition: debug.h:30
uint32_t ModifierMask
Definition: keyboard.h:53
#define S_(Text)
Definition: i18n.h:18
LIBGTKMM2EXT_API void get_all_actions(std::vector< std::string > &names, std::vector< std::string > &paths, std::vector< std::string > &tooltips, std::vector< std::string > &keys, std::vector< Gtk::AccelKey > &bindings)
std::string string_compose(const std::string &fmt, const T1 &o1)
Definition: compose.h:208