Add standalone login shell for diya-session-manager backend
This commit is contained in:
parent
f722acdd20
commit
dd8f2b3011
30
meson.build
30
meson.build
@ -48,18 +48,38 @@ foreach proto : wl_protocols
|
|||||||
command: [ wayland_scanner, 'public-code', '@INPUT@', '@OUTPUT@' ] )
|
command: [ wayland_scanner, 'public-code', '@INPUT@', '@OUTPUT@' ] )
|
||||||
endforeach
|
endforeach
|
||||||
|
|
||||||
src = [
|
gnome=import('gnome')
|
||||||
'src/base.c',
|
|
||||||
|
base = [
|
||||||
|
'src/base.c'
|
||||||
|
]
|
||||||
|
|
||||||
|
dm_src = [
|
||||||
|
base,
|
||||||
'src/launcher.c',
|
'src/launcher.c',
|
||||||
'src/background.c',
|
'src/background.c',
|
||||||
'src/wayland.c',
|
|
||||||
'src/shell.c',
|
'src/shell.c',
|
||||||
'src/foreign.c',
|
'src/foreign.c',
|
||||||
'src/session.c',
|
'src/session.c',
|
||||||
'src/main.c',
|
'src/wayland.c',
|
||||||
|
'src/dm.c',
|
||||||
wayland_targets]
|
wayland_targets]
|
||||||
|
|
||||||
executable(
|
executable(
|
||||||
'diya-shell',
|
'diya-shell',
|
||||||
src,
|
dm_src,
|
||||||
|
dependencies: [gtk, gtk_layer_shell, wayland_client])
|
||||||
|
|
||||||
|
login_src = [
|
||||||
|
base,
|
||||||
|
'src/login-shell.c',
|
||||||
|
'src/login.c'
|
||||||
|
]
|
||||||
|
|
||||||
|
login_resource = gnome.compile_resources('resources','resources/login-shell/gresource.xml')
|
||||||
|
|
||||||
|
executable(
|
||||||
|
'diya-login-shell',
|
||||||
|
login_src,
|
||||||
|
login_resource,
|
||||||
dependencies: [gtk, gtk_layer_shell, wayland_client])
|
dependencies: [gtk, gtk_layer_shell, wayland_client])
|
6
resources/login-shell/gresource.xml
Normal file
6
resources/login-shell/gresource.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<gresources>
|
||||||
|
<gresource prefix="/dev/iohub/diya/shell">
|
||||||
|
<file alias="login-shell.css">resources/login-shell/login-shell.css</file>
|
||||||
|
</gresource>
|
||||||
|
</gresources>
|
9
resources/login-shell/login-shell.css
Normal file
9
resources/login-shell/login-shell.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.header {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
font-size: 12px;
|
||||||
|
color: red;
|
||||||
|
}
|
@ -33,7 +33,7 @@ static GParamSpec *g_so_prop[SO_N_PROPERTIES] = {0};
|
|||||||
|
|
||||||
typedef struct _DiyaShellObjectPrivate
|
typedef struct _DiyaShellObjectPrivate
|
||||||
{
|
{
|
||||||
DiyaObject * parent;
|
DiyaObject parent;
|
||||||
DiyaShell * shell;
|
DiyaShell * shell;
|
||||||
} DiyaShellObjectPrivate;
|
} DiyaShellObjectPrivate;
|
||||||
G_DEFINE_TYPE_WITH_PRIVATE(DiyaShellObject, diya_shell_object, DIYA_TYPE_OBJECT);
|
G_DEFINE_TYPE_WITH_PRIVATE(DiyaShellObject, diya_shell_object, DIYA_TYPE_OBJECT);
|
||||||
|
273
src/login-shell.c
Normal file
273
src/login-shell.c
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
#include "login-shell.h"
|
||||||
|
|
||||||
|
#include <gtk4-layer-shell.h>
|
||||||
|
#include <gtk4-session-lock.h>
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
#define DBUS_SERVER_NAME "dev.iohub.diya.SessionManager"
|
||||||
|
#define DBUS_SERVER_PATH "/dev/iohub/diya/SessionManager"
|
||||||
|
#define DBUS_SERVER_ERROR_NAME "dev.iohub.diya.SessionManager.Error"
|
||||||
|
|
||||||
|
struct _DiyaLoginShell
|
||||||
|
{
|
||||||
|
DiyaObject parent_object;
|
||||||
|
GtkApplication *app;
|
||||||
|
GtkSessionLockInstance *lock;
|
||||||
|
GtkWidget *username;
|
||||||
|
GtkWidget *password;
|
||||||
|
GtkWidget *status;
|
||||||
|
guint bus_watch_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_FINAL_TYPE(DiyaLoginShell, diya_login_shell, DIYA_TYPE_OBJECT);
|
||||||
|
|
||||||
|
static void diya_login_shell_dispose(GObject *object)
|
||||||
|
{
|
||||||
|
DiyaLoginShell *self = DIYA_LOGIN_SHELL(object);
|
||||||
|
g_debug("diya_login_shell_dispose: %s", diya_object_to_string(self));
|
||||||
|
if (self->lock)
|
||||||
|
{
|
||||||
|
if (gtk_session_lock_instance_is_locked(self->lock))
|
||||||
|
{
|
||||||
|
gtk_session_lock_instance_unlock(self->lock);
|
||||||
|
}
|
||||||
|
g_object_unref(self->lock);
|
||||||
|
}
|
||||||
|
if (self->app)
|
||||||
|
{
|
||||||
|
g_object_unref(self->app);
|
||||||
|
}
|
||||||
|
if (self->bus_watch_id > 0)
|
||||||
|
{
|
||||||
|
g_bus_unwatch_name(self->bus_watch_id);
|
||||||
|
}
|
||||||
|
G_OBJECT_CLASS(diya_login_shell_parent_class)->dispose(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void diya_login_shell_init(DiyaLoginShell *self)
|
||||||
|
{
|
||||||
|
self->app = NULL;
|
||||||
|
self->lock = gtk_session_lock_instance_new();
|
||||||
|
self->username = NULL;
|
||||||
|
self->password = NULL;
|
||||||
|
self->bus_watch_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const gchar *diya_login_shell_to_string(DiyaObject *object)
|
||||||
|
{
|
||||||
|
(void)object;
|
||||||
|
return "Diya Login Shell";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void diya_login_shell_class_init(DiyaLoginShellClass *class)
|
||||||
|
{
|
||||||
|
GObjectClass *gobject_class = G_OBJECT_CLASS(class);
|
||||||
|
DiyaObjectClass *base_class = DIYA_OBJECT_CLASS(class);
|
||||||
|
gobject_class->dispose = diya_login_shell_dispose;
|
||||||
|
// gobject_class->set_property = diya_lock_session_set_property;
|
||||||
|
// gobject_class->get_property = diya_lock_session_get_property;
|
||||||
|
base_class->to_string = diya_login_shell_to_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_method_call_return(GObject *source_object, GAsyncResult *res, gpointer user_data)
|
||||||
|
{
|
||||||
|
(void)source_object;
|
||||||
|
DiyaLoginShell *self = DIYA_LOGIN_SHELL(user_data);
|
||||||
|
GError *error = NULL;
|
||||||
|
GDBusMessage *reply = g_dbus_connection_send_message_with_reply_finish(G_DBUS_CONNECTION(source_object), res, &error);
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
g_warning("Method call error: %s", error->message);
|
||||||
|
gtk_label_set_text(GTK_LABEL(self->status), "Unable send request to session manager");
|
||||||
|
g_error_free(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (g_dbus_message_get_message_type(reply) == G_DBUS_MESSAGE_TYPE_ERROR)
|
||||||
|
{
|
||||||
|
g_dbus_message_to_gerror(reply, &error);
|
||||||
|
g_warning("Server return error: %s", error->message);
|
||||||
|
gtk_label_set_text(GTK_LABEL(self->status), "Session manager return with error");
|
||||||
|
g_error_free(error);
|
||||||
|
g_object_unref(reply);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GVariant *result = g_dbus_message_get_body(reply);
|
||||||
|
gboolean success;
|
||||||
|
g_variant_get(result, "(b)", &success);
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
gtk_label_set_text(GTK_LABEL(self->status), "Login successful");
|
||||||
|
g_application_quit(G_APPLICATION(self->app));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gtk_label_set_text(GTK_LABEL(self->status), "Login failed! Please try again.");
|
||||||
|
}
|
||||||
|
g_variant_unref(result);
|
||||||
|
//g_object_unref(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_name_appeared(GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data)
|
||||||
|
{
|
||||||
|
(void)connection;
|
||||||
|
DiyaLoginShell *self = DIYA_LOGIN_SHELL(user_data);
|
||||||
|
g_debug("Name appeared: %s %s", name, name_owner);
|
||||||
|
/**
|
||||||
|
* send dbus message to SessionManager to request login
|
||||||
|
*/
|
||||||
|
GDBusMessage *request = NULL;
|
||||||
|
|
||||||
|
const gchar *username = gtk_editable_get_text(GTK_EDITABLE(self->username));
|
||||||
|
const gchar *password = gtk_editable_get_text(GTK_EDITABLE(self->password));
|
||||||
|
g_debug("Login request for user: %s", username);
|
||||||
|
|
||||||
|
request = g_dbus_message_new_method_call(name_owner, DBUS_SERVER_PATH, DBUS_SERVER_NAME, "login");
|
||||||
|
g_dbus_message_set_body(request, g_variant_new("(ss)", username, password));
|
||||||
|
g_dbus_connection_send_message_with_reply(connection, request, G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, NULL, on_method_call_return, self);
|
||||||
|
g_object_unref(request);
|
||||||
|
g_bus_unwatch_name (self->bus_watch_id);
|
||||||
|
self->bus_watch_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_name_vanished(GDBusConnection *connection, const gchar *name, gpointer user_data)
|
||||||
|
{
|
||||||
|
(void)connection;
|
||||||
|
DiyaLoginShell *self = DIYA_LOGIN_SHELL(user_data);
|
||||||
|
g_warning("Name vanished: %s. Unwatch it", name);
|
||||||
|
gtk_label_set_text(GTK_LABEL(self->status), "Unable to connect to session manager");
|
||||||
|
g_bus_unwatch_name(self->bus_watch_id);
|
||||||
|
self->bus_watch_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_login(GtkButton *button, DiyaLoginShell *self)
|
||||||
|
{
|
||||||
|
(void)button;
|
||||||
|
const gchar *username = gtk_editable_get_text(GTK_EDITABLE(self->username));
|
||||||
|
const gchar *password = gtk_editable_get_text(GTK_EDITABLE(self->password));
|
||||||
|
if (!username || !password || strlen(username) == 0 || strlen(password) == 0)
|
||||||
|
{
|
||||||
|
gtk_label_set_text(GTK_LABEL(self->status), "Please enter username and password");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (self->bus_watch_id > 0)
|
||||||
|
{
|
||||||
|
g_warning("A login operation is already in progress");
|
||||||
|
gtk_label_set_text(GTK_LABEL(self->status), "A login operation is already in progress");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* watch the bus name
|
||||||
|
*/
|
||||||
|
self->bus_watch_id = g_bus_watch_name(G_BUS_TYPE_SYSTEM,
|
||||||
|
DBUS_SERVER_NAME,
|
||||||
|
G_BUS_NAME_WATCHER_FLAGS_NONE,
|
||||||
|
on_name_appeared,
|
||||||
|
on_name_vanished,
|
||||||
|
self,
|
||||||
|
NULL);
|
||||||
|
// g_application_quit(G_APPLICATION(self->app));
|
||||||
|
}
|
||||||
|
|
||||||
|
DiyaLoginShell *diya_login_shell_new()
|
||||||
|
{
|
||||||
|
return g_object_new(DIYA_TYPE_LOGIN_SHELL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void diya_login_shell_attach(DiyaLoginShell *self, GtkApplication *app)
|
||||||
|
{
|
||||||
|
self->app = app;
|
||||||
|
if (!gtk_session_lock_instance_lock(self->lock))
|
||||||
|
{
|
||||||
|
g_error("gtk_session_lock_instance_lock: Unable to lock the display");
|
||||||
|
g_object_unref(self);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GdkDisplay *display = gdk_display_get_default();
|
||||||
|
GListModel *monitors = gdk_display_get_monitors(display);
|
||||||
|
guint n_monitors = g_list_model_get_n_items(monitors);
|
||||||
|
|
||||||
|
for (guint i = 0; i < n_monitors; ++i)
|
||||||
|
{
|
||||||
|
GdkMonitor *monitor = g_list_model_get_item(monitors, i);
|
||||||
|
|
||||||
|
GtkWindow *gtk_window = GTK_WINDOW(gtk_application_window_new(app));
|
||||||
|
gtk_session_lock_instance_assign_window_to_monitor(self->lock, gtk_window, monitor);
|
||||||
|
|
||||||
|
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||||
|
gtk_widget_set_halign(box, GTK_ALIGN_CENTER);
|
||||||
|
gtk_widget_set_valign(box, GTK_ALIGN_CENTER);
|
||||||
|
gtk_box_set_spacing(GTK_BOX(box), 10);
|
||||||
|
|
||||||
|
GtkWidget *label;
|
||||||
|
|
||||||
|
label = gtk_label_new("Please login");
|
||||||
|
gtk_box_append(GTK_BOX(box), label);
|
||||||
|
gtk_widget_add_css_class(label, "header");
|
||||||
|
|
||||||
|
GtkWidget *user_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||||
|
gtk_widget_set_valign(user_box, GTK_ALIGN_CENTER);
|
||||||
|
gtk_widget_set_halign(user_box, GTK_ALIGN_END);
|
||||||
|
gtk_box_set_spacing(GTK_BOX(user_box), 10);
|
||||||
|
|
||||||
|
label = gtk_label_new("Username");
|
||||||
|
gtk_box_append(GTK_BOX(user_box), label);
|
||||||
|
|
||||||
|
self->username = gtk_entry_new();
|
||||||
|
gtk_box_append(GTK_BOX(user_box), GTK_WIDGET(self->username));
|
||||||
|
|
||||||
|
gtk_box_append(GTK_BOX(box), user_box);
|
||||||
|
|
||||||
|
GtkWidget *password_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||||
|
gtk_widget_set_valign(password_box, GTK_ALIGN_CENTER);
|
||||||
|
gtk_widget_set_halign(password_box, GTK_ALIGN_END);
|
||||||
|
gtk_box_set_spacing(GTK_BOX(password_box), 10);
|
||||||
|
|
||||||
|
label = gtk_label_new("Password");
|
||||||
|
gtk_box_append(GTK_BOX(password_box), label);
|
||||||
|
|
||||||
|
self->password = gtk_password_entry_new();
|
||||||
|
gtk_box_append(GTK_BOX(password_box), GTK_WIDGET(self->password));
|
||||||
|
|
||||||
|
gtk_box_append(GTK_BOX(box), password_box);
|
||||||
|
|
||||||
|
GtkWidget *button = gtk_button_new_with_label("Login");
|
||||||
|
g_signal_connect(self->username, "activate", G_CALLBACK(do_login), self);
|
||||||
|
g_signal_connect(self->password, "activate", G_CALLBACK(do_login), self);
|
||||||
|
g_signal_connect(button, "clicked", G_CALLBACK(do_login), self);
|
||||||
|
gtk_box_append(GTK_BOX(box), button);
|
||||||
|
|
||||||
|
// Not displayed, but allows testing that creating popups doesn't crash GTK
|
||||||
|
gtk_widget_set_tooltip_text(button, "Login");
|
||||||
|
|
||||||
|
self->status = gtk_label_new(NULL);
|
||||||
|
gtk_box_append(GTK_BOX(box), self->status);
|
||||||
|
gtk_widget_add_css_class(self->status, "status");
|
||||||
|
|
||||||
|
gtk_window_set_child(GTK_WINDOW(gtk_window), box);
|
||||||
|
|
||||||
|
gtk_window_present(gtk_window);
|
||||||
|
}
|
||||||
|
// load xml content from gresource
|
||||||
|
GError *err = NULL;
|
||||||
|
GBytes *bytes = g_resources_lookup_data("/dev/iohub/diya/shell/login-shell.css", 0, &err);
|
||||||
|
if (err != NULL)
|
||||||
|
{
|
||||||
|
g_critical("Unable to load CSS resource: %s", err->message);
|
||||||
|
g_error_free(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char *data = g_bytes_get_data(bytes, NULL);
|
||||||
|
g_debug("CSS resource loaded:\n %s", data);
|
||||||
|
GtkCssProvider *provider = gtk_css_provider_new();
|
||||||
|
gtk_css_provider_load_from_string(provider, data);
|
||||||
|
gtk_style_context_add_provider_for_display(
|
||||||
|
gdk_display_get_default(),
|
||||||
|
GTK_STYLE_PROVIDER(provider),
|
||||||
|
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||||
|
g_bytes_unref(bytes);
|
||||||
|
}
|
||||||
|
}
|
14
src/login-shell.h
Normal file
14
src/login-shell.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef LOGIN_SHELL_H
|
||||||
|
#define LOGIN_SHELL_H
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
#include "base.h"
|
||||||
|
|
||||||
|
#define DIYA_TYPE_LOGIN_SHELL (diya_login_shell_get_type())
|
||||||
|
G_DECLARE_FINAL_TYPE(DiyaLoginShell, diya_login_shell, DIYA, LOGIN_SHELL, DiyaObject);
|
||||||
|
|
||||||
|
|
||||||
|
DiyaLoginShell *diya_login_shell_new();
|
||||||
|
void diya_login_shell_attach(DiyaLoginShell *self, GtkApplication *app);
|
||||||
|
|
||||||
|
#endif
|
57
src/login.c
Normal file
57
src/login.c
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#include <glib-unix.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "login-shell.h"
|
||||||
|
|
||||||
|
static gchar **g_shell_argv;
|
||||||
|
|
||||||
|
static void activate(GtkApplication *app, void *data)
|
||||||
|
{
|
||||||
|
(void)app;
|
||||||
|
(void)data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean restart(gpointer d)
|
||||||
|
{
|
||||||
|
(void)d;
|
||||||
|
gint i, fdlimit;
|
||||||
|
|
||||||
|
fdlimit = (int)sysconf(_SC_OPEN_MAX);
|
||||||
|
g_debug("reload: closing fd's %d to %d", STDERR_FILENO + 1, fdlimit);
|
||||||
|
for (i = STDERR_FILENO + 1; i < fdlimit; i++)
|
||||||
|
{
|
||||||
|
fcntl(i, F_SETFD, FD_CLOEXEC);
|
||||||
|
}
|
||||||
|
g_debug("reload: exec: %s", g_shell_argv[0]);
|
||||||
|
execvp(g_shell_argv[0], g_shell_argv);
|
||||||
|
exit(1);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void startup(GtkApplication *app, DiyaLoginShell* shell)
|
||||||
|
{
|
||||||
|
g_unix_signal_add(SIGHUP, (GSourceFunc)restart, NULL);
|
||||||
|
g_unix_signal_add(SIGINT,(GSourceFunc)g_application_quit,(void*)app);
|
||||||
|
diya_login_shell_attach(shell, app);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
g_shell_argv = g_malloc0(sizeof(gchar *) * (argc + 1));
|
||||||
|
for (int i = 0; i < argc; i++)
|
||||||
|
{
|
||||||
|
g_shell_argv[i] = argv[i];
|
||||||
|
}
|
||||||
|
GtkApplication* app = gtk_application_new("dev.iohub.diya.login-shell", G_APPLICATION_DEFAULT_FLAGS);
|
||||||
|
|
||||||
|
DiyaLoginShell* shell = diya_login_shell_new();
|
||||||
|
|
||||||
|
g_signal_connect(app, "startup", G_CALLBACK(startup), shell);
|
||||||
|
g_signal_connect(app, "activate", G_CALLBACK(activate), shell);
|
||||||
|
// g_signal_connect(app, "shutdown", G_CALLBACK(shutdown), (void*)shell);
|
||||||
|
int status = g_application_run(G_APPLICATION(app), argc, argv);
|
||||||
|
g_warning("Exiting SHELL");
|
||||||
|
g_object_unref(shell);
|
||||||
|
return status;
|
||||||
|
}
|
@ -21,10 +21,6 @@ G_DECLARE_FINAL_TYPE (DiyaWayland, diya_wayland, DIYA, WAYLAND, DiyaShellObject)
|
|||||||
#define DIYA_TYPE_FOREIGN_WINDOW (diya_foreign_window_get_type ())
|
#define DIYA_TYPE_FOREIGN_WINDOW (diya_foreign_window_get_type ())
|
||||||
G_DECLARE_FINAL_TYPE (DiyaForeignWindow, diya_foreign_window, DIYA, FOREIGN_WINDOW, DiyaShellObject)
|
G_DECLARE_FINAL_TYPE (DiyaForeignWindow, diya_foreign_window, DIYA, FOREIGN_WINDOW, DiyaShellObject)
|
||||||
|
|
||||||
#define DIYA_TYPE_LOCK_SESSION (diya_lock_session_get_type ())
|
|
||||||
G_DECLARE_FINAL_TYPE (DiyaLockSession, diya_lock_session, DIYA, LOCK_SESSION, DiyaShellObject)
|
|
||||||
|
|
||||||
|
|
||||||
DiyaForeignWindow* diya_shell_get_window(DiyaShell * shell, gpointer handle);
|
DiyaForeignWindow* diya_shell_get_window(DiyaShell * shell, gpointer handle);
|
||||||
gboolean diya_shell_add_window(DiyaShell * shell, DiyaForeignWindow * win);
|
gboolean diya_shell_add_window(DiyaShell * shell, DiyaForeignWindow * win);
|
||||||
gboolean diya_shell_remove_window(DiyaShell * shell, DiyaForeignWindow * win);
|
gboolean diya_shell_remove_window(DiyaShell * shell, DiyaForeignWindow * win);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user