Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
What's new
10
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Open sidebar
GStreamer
gst-editing-services
Commits
6046a515
Commit
6046a515
authored
Sep 04, 2020
by
Thibault Saunier
🌵
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
launch: Add an interactive mode where we can seek etc...
Part-of: <
!209
>
parent
51c51710
Pipeline
#238864
waiting for manual action with stages
in 27 seconds
Changes
5
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
660 additions
and
1 deletion
+660
-1
tools/ges-launcher-kb.c
tools/ges-launcher-kb.c
+293
-0
tools/ges-launcher-kb.h
tools/ges-launcher-kb.h
+35
-0
tools/ges-launcher.c
tools/ges-launcher.c
+330
-0
tools/ges-launcher.h
tools/ges-launcher.h
+1
-0
tools/meson.build
tools/meson.build
+1
-1
No files found.
tools/ges-launcher-kb.c
0 → 100644
View file @
6046a515
/* GStreamer command line playback testing utility - keyboard handling helpers
*
* Copyright (C) 2013 Tim-Philipp Müller <tim centricular net>
* Copyright (C) 2013 Centricular Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "ges-launcher-kb.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef G_OS_UNIX
#include <unistd.h>
#include <termios.h>
#endif
#ifdef G_OS_WIN32
#include <windows.h>
#include <io.h>
#endif
#include <gst/gst.h>
/* This is all not thread-safe, but doesn't have to be really */
static
GstPlayKbFunc
kb_callback
;
static
gpointer
kb_callback_data
;
#ifdef G_OS_UNIX
static
struct
termios
term_settings
;
static
gboolean
term_settings_saved
=
FALSE
;
static
gulong
io_watch_id
;
static
gboolean
gst_play_kb_io_cb
(
GIOChannel
*
ioc
,
GIOCondition
cond
,
gpointer
user_data
)
{
GIOStatus
status
;
if
(
cond
&
G_IO_IN
)
{
gchar
buf
[
16
]
=
{
0
,
};
gsize
read
;
status
=
g_io_channel_read_chars
(
ioc
,
buf
,
sizeof
(
buf
)
-
1
,
&
read
,
NULL
);
if
(
status
==
G_IO_STATUS_ERROR
)
return
FALSE
;
if
(
status
==
G_IO_STATUS_NORMAL
)
{
if
(
kb_callback
)
kb_callback
(
buf
,
kb_callback_data
);
}
}
return
TRUE
;
/* call us again */
}
gboolean
gst_play_kb_set_key_handler
(
GstPlayKbFunc
kb_func
,
gpointer
user_data
)
{
GIOChannel
*
ioc
;
if
(
!
isatty
(
STDIN_FILENO
))
{
GST_INFO
(
"stdin is not connected to a terminal"
);
return
FALSE
;
}
if
(
io_watch_id
>
0
)
{
g_source_remove
(
io_watch_id
);
io_watch_id
=
0
;
}
if
(
kb_func
==
NULL
&&
term_settings_saved
)
{
/* restore terminal settings */
if
(
tcsetattr
(
STDIN_FILENO
,
TCSAFLUSH
,
&
term_settings
)
==
0
)
term_settings_saved
=
FALSE
;
else
g_warning
(
"could not restore terminal attributes"
);
setvbuf
(
stdin
,
NULL
,
_IOLBF
,
0
);
}
if
(
kb_func
!=
NULL
)
{
struct
termios
new_settings
;
if
(
!
term_settings_saved
)
{
if
(
tcgetattr
(
STDIN_FILENO
,
&
term_settings
)
!=
0
)
{
g_warning
(
"could not save terminal attributes"
);
return
FALSE
;
}
term_settings_saved
=
TRUE
;
/* Echo off, canonical mode off, extended input processing off */
new_settings
=
term_settings
;
new_settings
.
c_lflag
&=
~
(
ECHO
|
ICANON
|
IEXTEN
);
new_settings
.
c_cc
[
VMIN
]
=
0
;
new_settings
.
c_cc
[
VTIME
]
=
0
;
if
(
tcsetattr
(
STDIN_FILENO
,
TCSAFLUSH
,
&
new_settings
)
!=
0
)
{
g_warning
(
"Could not set terminal state"
);
return
FALSE
;
}
setvbuf
(
stdin
,
NULL
,
_IONBF
,
0
);
}
}
ioc
=
g_io_channel_unix_new
(
STDIN_FILENO
);
io_watch_id
=
g_io_add_watch_full
(
ioc
,
G_PRIORITY_DEFAULT
,
G_IO_IN
,
(
GIOFunc
)
gst_play_kb_io_cb
,
user_data
,
NULL
);
g_io_channel_unref
(
ioc
);
kb_callback
=
kb_func
;
kb_callback_data
=
user_data
;
return
TRUE
;
}
#elif defined(G_OS_WIN32)
typedef
struct
{
GThread
*
thread
;
HANDLE
event_handle
;
HANDLE
console_handle
;
gboolean
closing
;
GMutex
lock
;
}
Win32KeyHandler
;
static
Win32KeyHandler
*
win32_handler
=
NULL
;
static
gboolean
gst_play_kb_source_cb
(
Win32KeyHandler
*
handler
)
{
HANDLE
h_input
=
handler
->
console_handle
;
INPUT_RECORD
buffer
;
DWORD
n
;
if
(
PeekConsoleInput
(
h_input
,
&
buffer
,
1
,
&
n
)
&&
n
==
1
)
{
ReadConsoleInput
(
h_input
,
&
buffer
,
1
,
&
n
);
if
(
buffer
.
EventType
==
KEY_EVENT
&&
buffer
.
Event
.
KeyEvent
.
bKeyDown
)
{
gchar
key_val
[
2
]
=
{
0
};
switch
(
buffer
.
Event
.
KeyEvent
.
wVirtualKeyCode
)
{
case
VK_RIGHT
:
kb_callback
(
GST_PLAY_KB_ARROW_RIGHT
,
kb_callback_data
);
break
;
case
VK_LEFT
:
kb_callback
(
GST_PLAY_KB_ARROW_LEFT
,
kb_callback_data
);
break
;
case
VK_UP
:
kb_callback
(
GST_PLAY_KB_ARROW_UP
,
kb_callback_data
);
break
;
case
VK_DOWN
:
kb_callback
(
GST_PLAY_KB_ARROW_DOWN
,
kb_callback_data
);
break
;
default:
key_val
[
0
]
=
buffer
.
Event
.
KeyEvent
.
uChar
.
AsciiChar
;
kb_callback
(
key_val
,
kb_callback_data
);
break
;
}
}
}
return
G_SOURCE_REMOVE
;
}
static
gpointer
gst_play_kb_win32_thread
(
gpointer
user_data
)
{
Win32KeyHandler
*
handler
=
(
Win32KeyHandler
*
)
user_data
;
HANDLE
handles
[
2
];
handles
[
0
]
=
handler
->
event_handle
;
handles
[
1
]
=
handler
->
console_handle
;
if
(
!
kb_callback
)
return
NULL
;
while
(
TRUE
)
{
DWORD
ret
=
WaitForMultipleObjects
(
2
,
handles
,
FALSE
,
INFINITE
);
if
(
ret
==
WAIT_FAILED
)
{
GST_WARNING
(
"WaitForMultipleObject Failed"
);
return
NULL
;
}
g_mutex_lock
(
&
handler
->
lock
);
if
(
handler
->
closing
)
{
g_mutex_unlock
(
&
handler
->
lock
);
return
NULL
;
}
g_mutex_unlock
(
&
handler
->
lock
);
g_idle_add
((
GSourceFunc
)
gst_play_kb_source_cb
,
handler
);
}
return
NULL
;
}
gboolean
gst_play_kb_set_key_handler
(
GstPlayKbFunc
kb_func
,
gpointer
user_data
)
{
gint
fd
=
_fileno
(
stdin
);
if
(
!
_isatty
(
fd
))
{
GST_INFO
(
"stdin is not connected to a terminal"
);
return
FALSE
;
}
if
(
win32_handler
)
{
g_mutex_lock
(
&
win32_handler
->
lock
);
win32_handler
->
closing
=
TRUE
;
g_mutex_unlock
(
&
win32_handler
->
lock
);
SetEvent
(
win32_handler
->
event_handle
);
g_thread_join
(
win32_handler
->
thread
);
CloseHandle
(
win32_handler
->
event_handle
);
g_mutex_clear
(
&
win32_handler
->
lock
);
g_free
(
win32_handler
);
win32_handler
=
NULL
;
}
if
(
kb_func
)
{
SECURITY_ATTRIBUTES
sec_attrs
;
sec_attrs
.
nLength
=
sizeof
(
SECURITY_ATTRIBUTES
);
sec_attrs
.
lpSecurityDescriptor
=
NULL
;
sec_attrs
.
bInheritHandle
=
FALSE
;
win32_handler
=
g_new0
(
Win32KeyHandler
,
1
);
/* create cancellable event handle */
win32_handler
->
event_handle
=
CreateEvent
(
&
sec_attrs
,
TRUE
,
FALSE
,
NULL
);
if
(
!
win32_handler
->
event_handle
)
{
GST_WARNING
(
"Couldn't create event handle"
);
g_free
(
win32_handler
);
win32_handler
=
NULL
;
return
FALSE
;
}
win32_handler
->
console_handle
=
GetStdHandle
(
STD_INPUT_HANDLE
);
if
(
!
win32_handler
->
console_handle
)
{
GST_WARNING
(
"Couldn't get console handle"
);
CloseHandle
(
win32_handler
->
event_handle
);
g_free
(
win32_handler
);
win32_handler
=
NULL
;
return
FALSE
;
}
g_mutex_init
(
&
win32_handler
->
lock
);
win32_handler
->
thread
=
g_thread_new
(
"gst-play-kb"
,
gst_play_kb_win32_thread
,
win32_handler
);
}
kb_callback
=
kb_func
;
kb_callback_data
=
user_data
;
return
TRUE
;
}
#else
gboolean
gst_play_kb_set_key_handler
(
GstPlayKbFunc
key_func
,
gpointer
user_data
)
{
GST_FIXME
(
"Keyboard handling for this OS needs to be implemented"
);
return
FALSE
;
}
#endif
/* !G_OS_UNIX */
tools/ges-launcher-kb.h
0 → 100644
View file @
6046a515
/* GStreamer command line playback testing utility - keyboard handling helpers
*
* Copyright (C) 2013 Tim-Philipp Müller <tim centricular net>
* Copyright (C) 2013 Centricular Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __GST_PLAY_KB_INCLUDED__
#define __GST_PLAY_KB_INCLUDED__
#include <glib.h>
#define GST_PLAY_KB_ARROW_UP "\033[A"
#define GST_PLAY_KB_ARROW_DOWN "\033[B"
#define GST_PLAY_KB_ARROW_RIGHT "\033[C"
#define GST_PLAY_KB_ARROW_LEFT "\033[D"
typedef
void
(
*
GstPlayKbFunc
)
(
const
gchar
*
kb_input
,
gpointer
user_data
);
gboolean
gst_play_kb_set_key_handler
(
GstPlayKbFunc
kb_func
,
gpointer
user_data
);
#endif
/* __GST_PLAY_KB_INCLUDED__ */
tools/ges-launcher.c
View file @
6046a515
...
...
@@ -31,6 +31,18 @@
#include "ges-launcher.h"
#include "ges-validate.h"
#include "utils.h"
#include "ges-launcher-kb.h"
typedef
enum
{
GST_PLAY_TRICK_MODE_NONE
=
0
,
GST_PLAY_TRICK_MODE_DEFAULT
,
GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO
,
GST_PLAY_TRICK_MODE_KEY_UNITS
,
GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO
,
GST_PLAY_TRICK_MODE_INSTANT_RATE
,
GST_PLAY_TRICK_MODE_LAST
}
GstPlayTrickMode
;
struct
_GESLauncherPrivate
{
...
...
@@ -41,6 +53,11 @@ struct _GESLauncherPrivate
guint
signal_watch_id
;
#endif
GESLauncherParsedOptions
parsed_options
;
GstPlayTrickMode
trick_mode
;
gdouble
rate
;
GstState
desired_state
;
/* as per user interaction, PAUSED or PLAYING */
};
G_DEFINE_TYPE_WITH_PRIVATE
(
GESLauncher
,
ges_launcher
,
G_TYPE_APPLICATION
);
...
...
@@ -56,6 +73,240 @@ static const gchar *HELP_SUMMARY =
" `ges-launch-1.0 --inspect-action-type` for the available commands.
\n\n
"
" By default, ges-launch-1.0 is in
\"
playback-mode
\"
."
;
static
gboolean
play_do_seek
(
GESLauncher
*
self
,
gint64
pos
,
gdouble
rate
,
GstPlayTrickMode
mode
)
{
GstSeekFlags
seek_flags
;
GstEvent
*
seek
;
seek_flags
=
0
;
switch
(
mode
)
{
case
GST_PLAY_TRICK_MODE_DEFAULT
:
seek_flags
|=
GST_SEEK_FLAG_TRICKMODE
;
break
;
case
GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO
:
seek_flags
|=
GST_SEEK_FLAG_TRICKMODE
|
GST_SEEK_FLAG_TRICKMODE_NO_AUDIO
;
break
;
case
GST_PLAY_TRICK_MODE_KEY_UNITS
:
seek_flags
|=
GST_SEEK_FLAG_TRICKMODE_KEY_UNITS
;
break
;
case
GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO
:
seek_flags
|=
GST_SEEK_FLAG_TRICKMODE_KEY_UNITS
|
GST_SEEK_FLAG_TRICKMODE_NO_AUDIO
;
break
;
case
GST_PLAY_TRICK_MODE_NONE
:
default:
break
;
}
/* See if we can do an instant rate change (not changing dir) */
if
(
mode
&
GST_PLAY_TRICK_MODE_INSTANT_RATE
&&
rate
*
self
->
priv
->
rate
>
0
)
{
seek
=
gst_event_new_seek
(
rate
,
GST_FORMAT_TIME
,
seek_flags
|
GST_SEEK_FLAG_INSTANT_RATE_CHANGE
,
GST_SEEK_TYPE_NONE
,
GST_CLOCK_TIME_NONE
,
GST_SEEK_TYPE_NONE
,
GST_CLOCK_TIME_NONE
);
if
(
gst_element_send_event
(
GST_ELEMENT
(
self
->
priv
->
pipeline
),
seek
))
{
goto
done
;
}
}
/* No instant rate change, need to do a flushing seek */
seek_flags
|=
GST_SEEK_FLAG_FLUSH
;
if
(
rate
>=
0
)
seek
=
gst_event_new_seek
(
rate
,
GST_FORMAT_TIME
,
seek_flags
|
GST_SEEK_FLAG_ACCURATE
,
/* start */
GST_SEEK_TYPE_SET
,
pos
,
/* stop */
GST_SEEK_TYPE_SET
,
GST_CLOCK_TIME_NONE
);
else
seek
=
gst_event_new_seek
(
rate
,
GST_FORMAT_TIME
,
seek_flags
|
GST_SEEK_FLAG_ACCURATE
,
/* start */
GST_SEEK_TYPE_SET
,
0
,
/* stop */
GST_SEEK_TYPE_SET
,
pos
);
if
(
!
gst_element_send_event
(
GST_ELEMENT
(
self
->
priv
->
pipeline
),
seek
))
return
FALSE
;
done:
self
->
priv
->
rate
=
rate
;
self
->
priv
->
trick_mode
=
mode
&
~
GST_PLAY_TRICK_MODE_INSTANT_RATE
;
return
TRUE
;
}
static
void
restore_terminal
(
void
)
{
gst_play_kb_set_key_handler
(
NULL
,
NULL
);
}
static
void
toggle_paused
(
GESLauncher
*
self
)
{
if
(
self
->
priv
->
desired_state
==
GST_STATE_PLAYING
)
self
->
priv
->
desired_state
=
GST_STATE_PAUSED
;
else
self
->
priv
->
desired_state
=
GST_STATE_PLAYING
;
gst_element_set_state
(
GST_ELEMENT
(
self
->
priv
->
pipeline
),
self
->
priv
->
desired_state
);
}
static
void
relative_seek
(
GESLauncher
*
self
,
gdouble
percent
)
{
gint64
pos
=
-
1
,
step
,
dur
;
g_return_if_fail
(
percent
>=
-
1
.
0
&&
percent
<=
1
.
0
);
if
(
!
gst_element_query_position
(
GST_ELEMENT
(
self
->
priv
->
pipeline
),
GST_FORMAT_TIME
,
&
pos
))
goto
seek_failed
;
if
(
!
gst_element_query_duration
(
GST_ELEMENT
(
self
->
priv
->
pipeline
),
GST_FORMAT_TIME
,
&
dur
))
{
goto
seek_failed
;
}
step
=
dur
*
percent
;
if
(
ABS
(
step
)
<
GST_SECOND
)
step
=
(
percent
<
0
)
?
-
GST_SECOND
:
GST_SECOND
;
pos
=
pos
+
step
;
if
(
pos
>
dur
)
{
gst_print
(
"
\n
%s
\n
"
,
"Reached end of self list."
);
g_application_quit
(
G_APPLICATION
(
self
));
}
else
{
if
(
pos
<
0
)
pos
=
0
;
play_do_seek
(
self
,
pos
,
self
->
priv
->
rate
,
self
->
priv
->
trick_mode
);
}
return
;
seek_failed:
{
gst_print
(
"
\n
Could not seek.
\n
"
);
}
}
static
gboolean
play_set_rate_and_trick_mode
(
GESLauncher
*
self
,
gdouble
rate
,
GstPlayTrickMode
mode
)
{
gint64
pos
=
-
1
;
g_return_val_if_fail
(
rate
!=
0
,
FALSE
);
if
(
!
gst_element_query_position
(
GST_ELEMENT
(
self
->
priv
->
pipeline
),
GST_FORMAT_TIME
,
&
pos
))
return
FALSE
;
return
play_do_seek
(
self
,
pos
,
rate
,
mode
);
}
static
void
play_set_playback_rate
(
GESLauncher
*
self
,
gdouble
rate
)
{
GstPlayTrickMode
mode
=
self
->
priv
->
trick_mode
;
if
(
play_set_rate_and_trick_mode
(
self
,
rate
,
mode
))
{
gst_print
(
"Playback rate: %.2f"
,
rate
);
gst_print
(
"
\n
"
);
}
else
{
gst_print
(
"
\n
"
);
gst_print
(
"Could not change playback rate to %.2f"
,
rate
);
gst_print
(
".
\n
"
);
}
}
static
void
play_set_relative_playback_rate
(
GESLauncher
*
self
,
gdouble
rate_step
,
gboolean
reverse_direction
)
{
gdouble
new_rate
=
self
->
priv
->
rate
+
rate_step
;
play_set_playback_rate
(
self
,
new_rate
);
}
static
const
gchar
*
trick_mode_get_description
(
GstPlayTrickMode
mode
)
{
switch
(
mode
)
{
case
GST_PLAY_TRICK_MODE_NONE
:
return
"normal playback, trick modes disabled"
;
case
GST_PLAY_TRICK_MODE_DEFAULT
:
return
"trick mode: default"
;
case
GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO
:
return
"trick mode: default, no audio"
;
case
GST_PLAY_TRICK_MODE_KEY_UNITS
:
return
"trick mode: key frames only"
;
case
GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO
:
return
"trick mode: key frames only, no audio"
;
default:
break
;
}
return
"unknown trick mode"
;
}
static
void
play_switch_trick_mode
(
GESLauncher
*
self
)
{
GstPlayTrickMode
new_mode
=
++
self
->
priv
->
trick_mode
;
const
gchar
*
mode_desc
;
if
(
new_mode
==
GST_PLAY_TRICK_MODE_LAST
)
new_mode
=
GST_PLAY_TRICK_MODE_NONE
;
mode_desc
=
trick_mode_get_description
(
new_mode
);
if
(
play_set_rate_and_trick_mode
(
self
,
self
->
priv
->
rate
,
new_mode
))
{
gst_print
(
"Rate: %.2f (%s)
\n
"
,
self
->
priv
->
rate
,
mode_desc
);
}
else
{
gst_print
(
"
\n
Could not change trick mode to %s.
\n
"
,
mode_desc
);
}
}
static
void
print_keyboard_help
(
void
)
{
static
struct
{
const
gchar
*
key_desc
;
const
gchar
*
key_help
;
}
key_controls
[]
=
{
{
"space"
,
"pause/unpause"
},
{
"q or ESC"
,
"quit"
},
{
"
\342\206\222
"
,
"seek forward"
},
{
"
\342\206\220
"
,
"seek backward"
},
{
"+"
,
"increase playback rate"
},
{
"-"
,
"decrease playback rate"
},
{
"t"
,
"enable/disable trick modes"
},
{
"s"
,
"change subtitle track"
},
{
"0"
,
"seek to beginning"
},
{
"k"
,
"show keyboard shortcuts"
},};
guint
i
,
chars_to_pad
,
desc_len
,
max_desc_len
=
0
;
gst_print
(
"
\n\n
%s
\n\n
"
,
"Interactive mode - keyboard controls:"
);
for
(
i
=
0
;
i
<
G_N_ELEMENTS
(
key_controls
);
++
i
)
{
desc_len
=
g_utf8_strlen
(
key_controls
[
i
].
key_desc
,
-
1
);
max_desc_len
=
MAX
(
max_desc_len
,
desc_len
);
}
++
max_desc_len
;
for
(
i
=
0
;
i
<
G_N_ELEMENTS
(
key_controls
);
++
i
)
{
chars_to_pad
=
max_desc_len
-
g_utf8_strlen
(
key_controls
[
i
].
key_desc
,
-
1
);
gst_print
(
"
\t
%s"
,
key_controls
[
i
].
key_desc
);
gst_print
(
"%-*s: "
,
chars_to_pad
,
""
);
gst_print
(
"%s
\n
"
,
key_controls
[
i
].
key_help
);
}
gst_print
(
"
\n
"
);
}
static
gboolean
_parse_track_type
(
const
gchar
*
option_name
,
const
gchar
*
value
,
GESLauncherParsedOptions
*
opts
,
GError
**
error
)
...
...
@@ -971,6 +1222,10 @@ ges_launcher_parse_options (GESLauncher * self,
"Embed nested timelines when saving."
,
}
,
{
"no-interactive"
,
0
,
G_OPTION_FLAG_REVERSE
,
G_OPTION_ARG_NONE
,
&
opts
->
interactive
,
"Disable interactive control via the keyboard"
,
NULL
}
,
{
NULL
}
};
...
...
@@ -1090,6 +1345,68 @@ done:
return
res
;
}
static
void
keyboard_cb
(
const
gchar
*
key_input
,
gpointer
user_data
)
{
GESLauncher
*
self
=
(
GESLauncher
*
)
user_data
;
gchar
key
=
'\0'
;
/* only want to switch/case on single char, not first char of string */
if
(
key_input
[
0
]
!=
'\0'
&&
key_input
[
1
]
==
'\0'
)
key
=
g_ascii_tolower
(
key_input
[
0
]);
switch
(
key
)
{