layouts: Change type based on shape
This commit is contained in:
@ -31,9 +31,10 @@
|
|||||||
|
|
||||||
LevelKeyboard *
|
LevelKeyboard *
|
||||||
eek_xml_layout_real_create_keyboard (const char *keyboard_type,
|
eek_xml_layout_real_create_keyboard (const char *keyboard_type,
|
||||||
EekboardContextService *manager)
|
EekboardContextService *manager,
|
||||||
|
enum layout_type t)
|
||||||
{
|
{
|
||||||
struct squeek_layout *layout = squeek_load_layout(keyboard_type);
|
struct squeek_layout *layout = squeek_load_layout(keyboard_type, t);
|
||||||
squeek_layout_place_contents(layout);
|
squeek_layout_place_contents(layout);
|
||||||
return level_keyboard_new(manager, layout);
|
return level_keyboard_new(manager, layout);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,10 +24,13 @@
|
|||||||
#define EEK_XML_LAYOUT_H 1
|
#define EEK_XML_LAYOUT_H 1
|
||||||
|
|
||||||
#include "eek-types.h"
|
#include "eek-types.h"
|
||||||
|
#include "src/layout.h"
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
LevelKeyboard *
|
LevelKeyboard *
|
||||||
eek_xml_layout_real_create_keyboard (const char *keyboard_type,
|
eek_xml_layout_real_create_keyboard (const char *keyboard_type,
|
||||||
EekboardContextService *manager);
|
EekboardContextService *manager,
|
||||||
|
enum layout_type t);
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
#endif /* EEK_XML_LAYOUT_H */
|
#endif /* EEK_XML_LAYOUT_H */
|
||||||
|
|||||||
@ -41,12 +41,10 @@
|
|||||||
#include "wayland.h"
|
#include "wayland.h"
|
||||||
|
|
||||||
#include "eek/eek-xml-layout.h"
|
#include "eek/eek-xml-layout.h"
|
||||||
|
#include "src/server-context-service.h"
|
||||||
|
|
||||||
#include "eekboard/eekboard-context-service.h"
|
#include "eekboard/eekboard-context-service.h"
|
||||||
|
|
||||||
#define CSW 640
|
|
||||||
#define CSH 480
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
PROP_0, // Magic: without this, keyboard is not useable in g_object_notify
|
PROP_0, // Magic: without this, keyboard is not useable in g_object_notify
|
||||||
PROP_KEYBOARD,
|
PROP_KEYBOARD,
|
||||||
@ -73,10 +71,6 @@ struct _EekboardContextServicePrivate {
|
|||||||
LevelKeyboard *keyboard; // currently used keyboard
|
LevelKeyboard *keyboard; // currently used keyboard
|
||||||
GHashTable *keyboard_hash; // a table of available keyboards, per layout
|
GHashTable *keyboard_hash; // a table of available keyboards, per layout
|
||||||
|
|
||||||
// TODO: make use of repeating buttons
|
|
||||||
guint repeat_timeout_id;
|
|
||||||
gboolean repeat_triggered;
|
|
||||||
|
|
||||||
GSettings *settings;
|
GSettings *settings;
|
||||||
uint32_t hint;
|
uint32_t hint;
|
||||||
uint32_t purpose;
|
uint32_t purpose;
|
||||||
@ -86,9 +80,10 @@ G_DEFINE_TYPE_WITH_PRIVATE (EekboardContextService, eekboard_context_service, G_
|
|||||||
|
|
||||||
static LevelKeyboard *
|
static LevelKeyboard *
|
||||||
eekboard_context_service_real_create_keyboard (EekboardContextService *self,
|
eekboard_context_service_real_create_keyboard (EekboardContextService *self,
|
||||||
const gchar *keyboard_type)
|
const gchar *keyboard_type,
|
||||||
|
enum layout_type t)
|
||||||
{
|
{
|
||||||
LevelKeyboard *keyboard = eek_xml_layout_real_create_keyboard(keyboard_type, self);
|
LevelKeyboard *keyboard = eek_xml_layout_real_create_keyboard(keyboard_type, self, t);
|
||||||
if (!keyboard) {
|
if (!keyboard) {
|
||||||
g_error("Failed to create a keyboard");
|
g_error("Failed to create a keyboard");
|
||||||
}
|
}
|
||||||
@ -231,8 +226,8 @@ settings_get_layout(GSettings *settings, char **type, char **layout)
|
|||||||
g_variant_unref(inputs);
|
g_variant_unref(inputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
void
|
||||||
settings_update_layout(EekboardContextService *context)
|
eekboard_context_service_update_layout(EekboardContextService *context, enum layout_type t)
|
||||||
{
|
{
|
||||||
g_autofree gchar *keyboard_type = NULL;
|
g_autofree gchar *keyboard_type = NULL;
|
||||||
g_autofree gchar *keyboard_layout = NULL;
|
g_autofree gchar *keyboard_layout = NULL;
|
||||||
@ -262,7 +257,7 @@ settings_update_layout(EekboardContextService *context)
|
|||||||
GUINT_TO_POINTER(keyboard_id));
|
GUINT_TO_POINTER(keyboard_id));
|
||||||
// create a keyboard
|
// create a keyboard
|
||||||
if (!keyboard) {
|
if (!keyboard) {
|
||||||
keyboard = eekboard_context_service_real_create_keyboard(context, keyboard_layout);
|
keyboard = eekboard_context_service_real_create_keyboard(context, keyboard_layout, t);
|
||||||
|
|
||||||
g_hash_table_insert (context->priv->keyboard_hash,
|
g_hash_table_insert (context->priv->keyboard_hash,
|
||||||
GUINT_TO_POINTER(keyboard_id),
|
GUINT_TO_POINTER(keyboard_id),
|
||||||
@ -272,11 +267,14 @@ settings_update_layout(EekboardContextService *context)
|
|||||||
}
|
}
|
||||||
// set as current
|
// set as current
|
||||||
context->priv->keyboard = keyboard;
|
context->priv->keyboard = keyboard;
|
||||||
// TODO: this used to save the group, why?
|
|
||||||
//group = eek_element_get_group (EEK_ELEMENT(context->priv->keyboard));
|
|
||||||
g_object_notify (G_OBJECT(context), "keyboard");
|
g_object_notify (G_OBJECT(context), "keyboard");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void update_layout_and_type(EekboardContextService *context) {
|
||||||
|
eekboard_context_service_update_layout(context, server_context_service_get_layout_type(context));
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
settings_handle_layout_changed(GSettings *s,
|
settings_handle_layout_changed(GSettings *s,
|
||||||
gpointer keys, gint n_keys,
|
gpointer keys, gint n_keys,
|
||||||
@ -285,7 +283,7 @@ settings_handle_layout_changed(GSettings *s,
|
|||||||
(void)keys;
|
(void)keys;
|
||||||
(void)n_keys;
|
(void)n_keys;
|
||||||
EekboardContextService *context = user_data;
|
EekboardContextService *context = user_data;
|
||||||
settings_update_layout(context);
|
update_layout_and_type(context);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,7 +297,7 @@ eekboard_context_service_constructed (GObject *object)
|
|||||||
if (!context->virtual_keyboard) {
|
if (!context->virtual_keyboard) {
|
||||||
g_error("Programmer error: Failed to receive a virtual keyboard instance");
|
g_error("Programmer error: Failed to receive a virtual keyboard instance");
|
||||||
}
|
}
|
||||||
settings_update_layout(context);
|
update_layout_and_type(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -518,6 +516,6 @@ void eekboard_context_service_set_hint_purpose(EekboardContextService *context,
|
|||||||
if (priv->hint != hint || priv->purpose != purpose) {
|
if (priv->hint != hint || priv->purpose != purpose) {
|
||||||
priv->hint = hint;
|
priv->hint = hint;
|
||||||
priv->purpose = purpose;
|
priv->purpose = purpose;
|
||||||
settings_update_layout(context);
|
update_layout_and_type(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,6 +105,7 @@ void eekboard_context_service_set_keymap(EekboardContextService *context,
|
|||||||
void eekboard_context_service_set_hint_purpose(EekboardContextService *context,
|
void eekboard_context_service_set_hint_purpose(EekboardContextService *context,
|
||||||
uint32_t hint,
|
uint32_t hint,
|
||||||
uint32_t purpose);
|
uint32_t purpose);
|
||||||
|
void
|
||||||
|
eekboard_context_service_update_layout(EekboardContextService *context, enum layout_type t);
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
#endif /* EEKBOARD_CONTEXT_SERVICE_H */
|
#endif /* EEKBOARD_CONTEXT_SERVICE_H */
|
||||||
|
|||||||
68
src/data.rs
68
src/data.rs
@ -36,12 +36,20 @@ pub mod c {
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C"
|
pub extern "C"
|
||||||
fn squeek_load_layout(name: *const c_char) -> *mut ::layout::Layout {
|
fn squeek_load_layout(
|
||||||
|
name: *const c_char,
|
||||||
|
type_: u32,
|
||||||
|
) -> *mut ::layout::Layout {
|
||||||
|
let type_ = match type_ {
|
||||||
|
0 => LayoutType::Base,
|
||||||
|
1 => LayoutType::Wide,
|
||||||
|
_ => panic!("Bad enum value"),
|
||||||
|
};
|
||||||
let name = as_str(&name)
|
let name = as_str(&name)
|
||||||
.expect("Bad layout name")
|
.expect("Bad layout name")
|
||||||
.expect("Empty layout name");
|
.expect("Empty layout name");
|
||||||
|
|
||||||
let layout = load_layout_with_fallback(name);
|
let layout = load_layout_with_fallback(&name, type_);
|
||||||
Box::into_raw(Box::new(layout))
|
Box::into_raw(Box::new(layout))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,6 +76,20 @@ impl fmt::Display for LoadError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum LayoutType {
|
||||||
|
Base = 0,
|
||||||
|
Wide = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutType {
|
||||||
|
fn apply_to_name(&self, name: String) -> String {
|
||||||
|
match self {
|
||||||
|
LayoutType::Base => name,
|
||||||
|
LayoutType::Wide => name + "_wide",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
enum DataSource {
|
enum DataSource {
|
||||||
File(PathBuf),
|
File(PathBuf),
|
||||||
@ -86,22 +108,35 @@ impl fmt::Display for DataSource {
|
|||||||
/// Lists possible sources, with 0 as the most preferred one
|
/// Lists possible sources, with 0 as the most preferred one
|
||||||
fn list_layout_sources(
|
fn list_layout_sources(
|
||||||
name: &str,
|
name: &str,
|
||||||
keyboards_path: Option<PathBuf>
|
type_: LayoutType,
|
||||||
|
keyboards_path: Option<PathBuf>,
|
||||||
) -> Vec<DataSource> {
|
) -> Vec<DataSource> {
|
||||||
let mut ret = Vec::new();
|
let mut ret = Vec::new();
|
||||||
if let Some(path) = keyboards_path.clone() {
|
|
||||||
ret.push(DataSource::File(path.join(name).with_extension("yaml")))
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.push(DataSource::Resource(name.to_owned()));
|
let mut add_by_name = |name: &str| {
|
||||||
|
if let Some(path) = keyboards_path.clone() {
|
||||||
|
ret.push(DataSource::File(
|
||||||
|
path.join(name.to_owned()).with_extension("yaml")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(path) = keyboards_path.clone() {
|
ret.push(DataSource::Resource(name.into()));
|
||||||
ret.push(DataSource::File(
|
};
|
||||||
path.join(FALLBACK_LAYOUT_NAME).with_extension("yaml")
|
|
||||||
))
|
match &type_ {
|
||||||
}
|
LayoutType::Base => {},
|
||||||
|
type_ => add_by_name(&type_.apply_to_name(name.into())),
|
||||||
|
};
|
||||||
|
|
||||||
|
add_by_name(name);
|
||||||
|
|
||||||
|
match &type_ {
|
||||||
|
LayoutType::Base => {},
|
||||||
|
type_ => add_by_name(&type_.apply_to_name(FALLBACK_LAYOUT_NAME.into())),
|
||||||
|
};
|
||||||
|
|
||||||
|
add_by_name(FALLBACK_LAYOUT_NAME);
|
||||||
|
|
||||||
ret.push(DataSource::Resource(FALLBACK_LAYOUT_NAME.to_owned()));
|
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,13 +159,14 @@ fn load_layout(source: DataSource) -> Result<::layout::Layout, LoadError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn load_layout_with_fallback(
|
fn load_layout_with_fallback(
|
||||||
name: &str
|
name: &str,
|
||||||
|
type_: LayoutType,
|
||||||
) -> ::layout::Layout {
|
) -> ::layout::Layout {
|
||||||
let path = env::var_os("SQUEEKBOARD_KEYBOARDSDIR")
|
let path = env::var_os("SQUEEKBOARD_KEYBOARDSDIR")
|
||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
.or_else(|| xdg::data_path("squeekboard/keyboards"));
|
.or_else(|| xdg::data_path("squeekboard/keyboards"));
|
||||||
|
|
||||||
for source in list_layout_sources(name, path) {
|
for source in list_layout_sources(name, type_, path) {
|
||||||
let layout = load_layout(source.clone());
|
let layout = load_layout(source.clone());
|
||||||
match layout {
|
match layout {
|
||||||
Err(e) => match (e, source) {
|
Err(e) => match (e, source) {
|
||||||
@ -665,7 +701,7 @@ mod tests {
|
|||||||
/// First fallback should be to builtin, not to FALLBACK_LAYOUT_NAME
|
/// First fallback should be to builtin, not to FALLBACK_LAYOUT_NAME
|
||||||
#[test]
|
#[test]
|
||||||
fn fallbacks_order() {
|
fn fallbacks_order() {
|
||||||
let sources = list_layout_sources("nb", None);
|
let sources = list_layout_sources("nb", LayoutType::Base, None);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sources,
|
sources,
|
||||||
|
|||||||
@ -9,6 +9,11 @@
|
|||||||
#include "src/keyboard.h"
|
#include "src/keyboard.h"
|
||||||
#include "virtual-keyboard-unstable-v1-client-protocol.h"
|
#include "virtual-keyboard-unstable-v1-client-protocol.h"
|
||||||
|
|
||||||
|
enum layout_type {
|
||||||
|
LAYOUT_TYPE_BASE = 0,
|
||||||
|
LAYOUT_TYPE_WIDE = 1,
|
||||||
|
};
|
||||||
|
|
||||||
struct squeek_button;
|
struct squeek_button;
|
||||||
struct squeek_row;
|
struct squeek_row;
|
||||||
struct squeek_view;
|
struct squeek_view;
|
||||||
@ -52,7 +57,7 @@ void
|
|||||||
squeek_layout_place_contents(struct squeek_layout*);
|
squeek_layout_place_contents(struct squeek_layout*);
|
||||||
struct squeek_view *squeek_layout_get_current_view(struct squeek_layout*);
|
struct squeek_view *squeek_layout_get_current_view(struct squeek_layout*);
|
||||||
|
|
||||||
struct squeek_layout *squeek_load_layout(const char *type);
|
struct squeek_layout *squeek_load_layout(const char *name, uint32_t type);
|
||||||
const char *squeek_layout_get_keymap(const struct squeek_layout*);
|
const char *squeek_layout_get_keymap(const struct squeek_layout*);
|
||||||
void squeek_layout_free(struct squeek_layout*);
|
void squeek_layout_free(struct squeek_layout*);
|
||||||
|
|
||||||
|
|||||||
@ -210,7 +210,7 @@ pub mod c {
|
|||||||
match output_state {
|
match output_state {
|
||||||
Some(OutputState {
|
Some(OutputState {
|
||||||
current_mode: Some(super::Mode { width, height: _ } ),
|
current_mode: Some(super::Mode { width, height: _ } ),
|
||||||
scale: scale,
|
scale,
|
||||||
}) => width / scale,
|
}) => width / scale,
|
||||||
_ => {
|
_ => {
|
||||||
eprintln!("No width registered on output");
|
eprintln!("No width registered on output");
|
||||||
|
|||||||
@ -39,10 +39,11 @@ typedef struct _ServerContextServiceClass ServerContextServiceClass;
|
|||||||
struct _ServerContextService {
|
struct _ServerContextService {
|
||||||
EekboardContextService parent;
|
EekboardContextService parent;
|
||||||
|
|
||||||
GtkWidget *window;
|
PhoshLayerSurface *window;
|
||||||
GtkWidget *widget;
|
GtkWidget *widget;
|
||||||
guint hiding;
|
guint hiding;
|
||||||
guint last_requested_height;
|
guint last_requested_height;
|
||||||
|
enum layout_type last_type;
|
||||||
|
|
||||||
gdouble size_constraint_landscape[2];
|
gdouble size_constraint_landscape[2];
|
||||||
gdouble size_constraint_portrait[2];
|
gdouble size_constraint_portrait[2];
|
||||||
@ -59,7 +60,7 @@ on_destroy (GtkWidget *widget, gpointer user_data)
|
|||||||
{
|
{
|
||||||
ServerContextService *context = user_data;
|
ServerContextService *context = user_data;
|
||||||
|
|
||||||
g_assert (widget == context->window);
|
g_assert (widget == GTK_WIDGET(context->window));
|
||||||
|
|
||||||
context->window = NULL;
|
context->window = NULL;
|
||||||
context->widget = NULL;
|
context->widget = NULL;
|
||||||
@ -127,15 +128,30 @@ calculate_height(int32_t width)
|
|||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum layout_type get_type(uint32_t width, uint32_t height) {
|
||||||
|
(void)height;
|
||||||
|
if (width < 540) {
|
||||||
|
return LAYOUT_TYPE_BASE;
|
||||||
|
}
|
||||||
|
return LAYOUT_TYPE_WIDE;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
on_surface_configure(PhoshLayerSurface *surface, ServerContextService *context)
|
on_surface_configure(PhoshLayerSurface *surface, ServerContextService *context)
|
||||||
{
|
{
|
||||||
gint width;
|
gint width;
|
||||||
gint height;
|
gint height;
|
||||||
g_object_get(G_OBJECT(surface),
|
g_object_get(G_OBJECT(surface),
|
||||||
"width", &width,
|
"configured-width", &width,
|
||||||
"height", &height,
|
"configured-height", &height,
|
||||||
NULL);
|
NULL);
|
||||||
|
// check if the change would switch types
|
||||||
|
enum layout_type new_type = get_type((uint32_t)width, (uint32_t)height);
|
||||||
|
if (context->last_type != new_type) {
|
||||||
|
context->last_type = new_type;
|
||||||
|
eekboard_context_service_update_layout(EEKBOARD_CONTEXT_SERVICE(context), context->last_type);
|
||||||
|
}
|
||||||
|
|
||||||
guint desired_height = calculate_height(width);
|
guint desired_height = calculate_height(width);
|
||||||
guint configured_height = (guint)height;
|
guint configured_height = (guint)height;
|
||||||
// if height was already requested once but a different one was given
|
// if height was already requested once but a different one was given
|
||||||
@ -190,7 +206,7 @@ make_window (ServerContextService *context)
|
|||||||
// and there's no space in the protocol for others.
|
// and there's no space in the protocol for others.
|
||||||
// Those may still be useful in the future,
|
// Those may still be useful in the future,
|
||||||
// or for hacks with regular windows.
|
// or for hacks with regular windows.
|
||||||
gtk_widget_set_can_focus (context->window, FALSE);
|
gtk_widget_set_can_focus (GTK_WIDGET(context->window), FALSE);
|
||||||
g_object_set (G_OBJECT(context->window), "accept_focus", FALSE, NULL);
|
g_object_set (G_OBJECT(context->window), "accept_focus", FALSE, NULL);
|
||||||
gtk_window_set_title (GTK_WINDOW(context->window),
|
gtk_window_set_title (GTK_WINDOW(context->window),
|
||||||
_("Squeekboard"));
|
_("Squeekboard"));
|
||||||
@ -239,13 +255,13 @@ server_context_service_real_show_keyboard (EekboardContextService *_context)
|
|||||||
|
|
||||||
EEKBOARD_CONTEXT_SERVICE_CLASS (server_context_service_parent_class)->
|
EEKBOARD_CONTEXT_SERVICE_CLASS (server_context_service_parent_class)->
|
||||||
show_keyboard (_context);
|
show_keyboard (_context);
|
||||||
gtk_widget_show (context->window);
|
gtk_widget_show (GTK_WIDGET(context->window));
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
on_hide (ServerContextService *context)
|
on_hide (ServerContextService *context)
|
||||||
{
|
{
|
||||||
gtk_widget_hide (context->window);
|
gtk_widget_hide (GTK_WIDGET(context->window));
|
||||||
context->hiding = 0;
|
context->hiding = 0;
|
||||||
|
|
||||||
return G_SOURCE_REMOVE;
|
return G_SOURCE_REMOVE;
|
||||||
@ -357,3 +373,8 @@ server_context_service_new ()
|
|||||||
{
|
{
|
||||||
return EEKBOARD_CONTEXT_SERVICE(g_object_new (SERVER_TYPE_CONTEXT_SERVICE, NULL));
|
return EEKBOARD_CONTEXT_SERVICE(g_object_new (SERVER_TYPE_CONTEXT_SERVICE, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum layout_type server_context_service_get_layout_type(EekboardContextService *service)
|
||||||
|
{
|
||||||
|
return SERVER_CONTEXT_SERVICE(service)->last_type;
|
||||||
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
#define SERVER_CONTEXT_SERVICE_H 1
|
#define SERVER_CONTEXT_SERVICE_H 1
|
||||||
|
|
||||||
#include "eekboard/eekboard-service.h"
|
#include "eekboard/eekboard-service.h"
|
||||||
|
#include "src/layout.h"
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
@ -33,6 +34,7 @@ G_BEGIN_DECLS
|
|||||||
typedef struct _ServerContextService ServerContextService;
|
typedef struct _ServerContextService ServerContextService;
|
||||||
|
|
||||||
EekboardContextService *server_context_service_new ();
|
EekboardContextService *server_context_service_new ();
|
||||||
|
enum layout_type server_context_service_get_layout_type(EekboardContextService*);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
#endif /* SERVER_CONTEXT_SERVICE_H */
|
#endif /* SERVER_CONTEXT_SERVICE_H */
|
||||||
|
|||||||
Reference in New Issue
Block a user