mirror of
https://gitlab.freedesktop.org/wlroots/wlroots.git
synced 2024-11-22 07:02:28 +00:00
Merge pull request #1476 from emersion/fullscreen-shell
fullscreen-shell-v1: initial protocol implementation
This commit is contained in:
commit
d4de2bd708
248
examples/fullscreen-shell.c
Normal file
248
examples/fullscreen-shell.c
Normal file
@ -0,0 +1,248 @@
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
#include <getopt.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-server.h>
|
||||
#include <wlr/backend.h>
|
||||
#include <wlr/render/wlr_renderer.h>
|
||||
#include <wlr/types/wlr_compositor.h>
|
||||
#include <wlr/types/wlr_fullscreen_shell_v1.h>
|
||||
#include <wlr/types/wlr_linux_dmabuf_v1.h>
|
||||
#include <wlr/types/wlr_matrix.h>
|
||||
#include <wlr/types/wlr_output_layout.h>
|
||||
#include <wlr/types/wlr_output.h>
|
||||
#include <wlr/types/wlr_surface.h>
|
||||
#include <wlr/util/log.h>
|
||||
|
||||
/**
|
||||
* A minimal fullscreen-shell server. It only supports rendering.
|
||||
*/
|
||||
|
||||
struct fullscreen_server {
|
||||
struct wl_display *wl_display;
|
||||
struct wlr_backend *backend;
|
||||
struct wlr_renderer *renderer;
|
||||
|
||||
struct wlr_fullscreen_shell_v1 *fullscreen_shell;
|
||||
struct wl_listener present_surface;
|
||||
|
||||
struct wlr_output_layout *output_layout;
|
||||
struct wl_list outputs;
|
||||
struct wl_listener new_output;
|
||||
};
|
||||
|
||||
struct fullscreen_output {
|
||||
struct wl_list link;
|
||||
struct fullscreen_server *server;
|
||||
struct wlr_output *wlr_output;
|
||||
struct wlr_surface *surface;
|
||||
struct wl_listener surface_destroy;
|
||||
|
||||
struct wl_listener frame;
|
||||
};
|
||||
|
||||
struct render_data {
|
||||
struct wlr_output *output;
|
||||
struct wlr_renderer *renderer;
|
||||
struct tinywl_view *view;
|
||||
struct timespec *when;
|
||||
};
|
||||
|
||||
static void render_surface(struct wlr_surface *surface,
|
||||
int sx, int sy, void *data) {
|
||||
struct render_data *rdata = data;
|
||||
struct wlr_output *output = rdata->output;
|
||||
|
||||
struct wlr_texture *texture = wlr_surface_get_texture(surface);
|
||||
if (texture == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct wlr_box box = {
|
||||
.x = sx * output->scale,
|
||||
.y = sy * output->scale,
|
||||
.width = surface->current.width * output->scale,
|
||||
.height = surface->current.height * output->scale,
|
||||
};
|
||||
|
||||
float matrix[9];
|
||||
enum wl_output_transform transform =
|
||||
wlr_output_transform_invert(surface->current.transform);
|
||||
wlr_matrix_project_box(matrix, &box, transform, 0,
|
||||
output->transform_matrix);
|
||||
|
||||
wlr_render_texture_with_matrix(rdata->renderer, texture, matrix, 1);
|
||||
|
||||
wlr_surface_send_frame_done(surface, rdata->when);
|
||||
}
|
||||
|
||||
static void output_handle_frame(struct wl_listener *listener, void *data) {
|
||||
struct fullscreen_output *output =
|
||||
wl_container_of(listener, output, frame);
|
||||
struct wlr_renderer *renderer = output->server->renderer;
|
||||
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
int width, height;
|
||||
wlr_output_effective_resolution(output->wlr_output, &width, &height);
|
||||
|
||||
if (!wlr_output_make_current(output->wlr_output, NULL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
wlr_renderer_begin(renderer, width, height);
|
||||
|
||||
float color[4] = {0.3, 0.3, 0.3, 1.0};
|
||||
wlr_renderer_clear(renderer, color);
|
||||
|
||||
if (output->surface != NULL) {
|
||||
struct render_data rdata = {
|
||||
.output = output->wlr_output,
|
||||
.renderer = renderer,
|
||||
.when = &now,
|
||||
};
|
||||
wlr_surface_for_each_surface(output->surface, render_surface, &rdata);
|
||||
}
|
||||
|
||||
wlr_renderer_end(renderer);
|
||||
wlr_output_swap_buffers(output->wlr_output, NULL, NULL);
|
||||
}
|
||||
|
||||
static void output_set_surface(struct fullscreen_output *output,
|
||||
struct wlr_surface *surface);
|
||||
|
||||
static void output_handle_surface_destroy(struct wl_listener *listener,
|
||||
void *data) {
|
||||
struct fullscreen_output *output =
|
||||
wl_container_of(listener, output, surface_destroy);
|
||||
output_set_surface(output, NULL);
|
||||
}
|
||||
|
||||
static void output_set_surface(struct fullscreen_output *output,
|
||||
struct wlr_surface *surface) {
|
||||
if (output->surface == surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (output->surface != NULL) {
|
||||
wl_list_remove(&output->surface_destroy.link);
|
||||
output->surface = NULL;
|
||||
}
|
||||
|
||||
if (surface != NULL) {
|
||||
output->surface_destroy.notify = output_handle_surface_destroy;
|
||||
wl_signal_add(&surface->events.destroy, &output->surface_destroy);
|
||||
output->surface = surface;
|
||||
}
|
||||
|
||||
wlr_log(WLR_DEBUG, "Presenting surface %p on output %s",
|
||||
surface, output->wlr_output->name);
|
||||
}
|
||||
|
||||
static void server_handle_new_output(struct wl_listener *listener, void *data) {
|
||||
struct fullscreen_server *server =
|
||||
wl_container_of(listener, server, new_output);
|
||||
struct wlr_output *wlr_output = data;
|
||||
|
||||
if (!wl_list_empty(&wlr_output->modes)) {
|
||||
struct wlr_output_mode *mode =
|
||||
wl_container_of(wlr_output->modes.prev, mode, link);
|
||||
wlr_output_set_mode(wlr_output, mode);
|
||||
}
|
||||
|
||||
struct fullscreen_output *output =
|
||||
calloc(1, sizeof(struct fullscreen_output));
|
||||
output->wlr_output = wlr_output;
|
||||
output->server = server;
|
||||
output->frame.notify = output_handle_frame;
|
||||
wl_signal_add(&wlr_output->events.frame, &output->frame);
|
||||
wl_list_insert(&server->outputs, &output->link);
|
||||
|
||||
wlr_output_layout_add_auto(server->output_layout, wlr_output);
|
||||
wlr_output_create_global(wlr_output);
|
||||
}
|
||||
|
||||
static void server_handle_present_surface(struct wl_listener *listener,
|
||||
void *data) {
|
||||
struct fullscreen_server *server =
|
||||
wl_container_of(listener, server, present_surface);
|
||||
struct wlr_fullscreen_shell_v1_present_surface_event *event = data;
|
||||
|
||||
struct fullscreen_output *output;
|
||||
wl_list_for_each(output, &server->outputs, link) {
|
||||
if (event->output == NULL || event->output == output->wlr_output) {
|
||||
output_set_surface(output, event->surface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
wlr_log_init(WLR_DEBUG, NULL);
|
||||
|
||||
char *startup_cmd = NULL;
|
||||
|
||||
int c;
|
||||
while ((c = getopt(argc, argv, "s:")) != -1) {
|
||||
switch (c) {
|
||||
case 's':
|
||||
startup_cmd = optarg;
|
||||
break;
|
||||
default:
|
||||
printf("usage: %s [-s startup-command]\n", argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
if (optind < argc) {
|
||||
printf("usage: %s [-s startup-command]\n", argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
struct fullscreen_server server = {0};
|
||||
server.wl_display = wl_display_create();
|
||||
server.backend = wlr_backend_autocreate(server.wl_display, NULL);
|
||||
server.renderer = wlr_backend_get_renderer(server.backend);
|
||||
wlr_renderer_init_wl_display(server.renderer, server.wl_display);
|
||||
|
||||
wlr_compositor_create(server.wl_display, server.renderer);
|
||||
wlr_linux_dmabuf_v1_create(server.wl_display, server.renderer);
|
||||
|
||||
server.output_layout = wlr_output_layout_create();
|
||||
|
||||
wl_list_init(&server.outputs);
|
||||
server.new_output.notify = server_handle_new_output;
|
||||
wl_signal_add(&server.backend->events.new_output, &server.new_output);
|
||||
|
||||
server.fullscreen_shell = wlr_fullscreen_shell_v1_create(server.wl_display);
|
||||
server.present_surface.notify = server_handle_present_surface;
|
||||
wl_signal_add(&server.fullscreen_shell->events.present_surface,
|
||||
&server.present_surface);
|
||||
|
||||
const char *socket = wl_display_add_socket_auto(server.wl_display);
|
||||
if (!socket) {
|
||||
wl_display_destroy(server.wl_display);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!wlr_backend_start(server.backend)) {
|
||||
wl_display_destroy(server.wl_display);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
setenv("WAYLAND_DISPLAY", socket, true);
|
||||
if (startup_cmd != NULL) {
|
||||
if (fork() == 0) {
|
||||
execl("/bin/sh", "/bin/sh", "-c", startup_cmd, (void *)NULL);
|
||||
}
|
||||
}
|
||||
|
||||
wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s",
|
||||
socket);
|
||||
wl_display_run(server.wl_display);
|
||||
|
||||
wl_display_destroy_clients(server.wl_display);
|
||||
wl_display_destroy(server.wl_display);
|
||||
return 0;
|
||||
}
|
@ -112,11 +112,15 @@ examples = {
|
||||
'text-input': {
|
||||
'src': 'text-input.c',
|
||||
'dep': [wayland_cursor, wayland_client, wlr_protos, wlroots],
|
||||
},
|
||||
},
|
||||
'foreign-toplevel': {
|
||||
'src': 'foreign-toplevel.c',
|
||||
'dep': [wayland_client, wlr_protos, wlroots],
|
||||
},
|
||||
'fullscreen-shell': {
|
||||
'src': 'fullscreen-shell.c',
|
||||
'dep': [wlr_protos, wlroots],
|
||||
},
|
||||
}
|
||||
|
||||
foreach name, info : examples
|
||||
|
@ -7,6 +7,7 @@ install_headers(
|
||||
'wlr_data_device.h',
|
||||
'wlr_export_dmabuf_v1.h',
|
||||
'wlr_foreign_toplevel_management_v1.h',
|
||||
'wlr_fullscreen_shell_v1.h',
|
||||
'wlr_gamma_control_v1.h',
|
||||
'wlr_gamma_control.h',
|
||||
'wlr_gtk_primary_selection.h',
|
||||
|
41
include/wlr/types/wlr_fullscreen_shell_v1.h
Normal file
41
include/wlr/types/wlr_fullscreen_shell_v1.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* This an unstable interface of wlroots. No guarantees are made regarding the
|
||||
* future consistency of this API.
|
||||
*/
|
||||
#ifndef WLR_USE_UNSTABLE
|
||||
#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features"
|
||||
#endif
|
||||
|
||||
#ifndef WLR_TYPES_WLR_FULLSCREEN_SHELL_V1_H
|
||||
#define WLR_TYPES_WLR_FULLSCREEN_SHELL_V1_H
|
||||
|
||||
#include <wayland-server.h>
|
||||
#include "fullscreen-shell-unstable-v1-protocol.h"
|
||||
|
||||
struct wlr_fullscreen_shell_v1 {
|
||||
struct wl_global *global;
|
||||
struct wl_list resources;
|
||||
|
||||
struct {
|
||||
struct wl_signal destroy;
|
||||
// wlr_fullscreen_shell_v1_present_surface_event
|
||||
struct wl_signal present_surface;
|
||||
} events;
|
||||
|
||||
struct wl_listener display_destroy;
|
||||
|
||||
void *data;
|
||||
};
|
||||
|
||||
struct wlr_fullscreen_shell_v1_present_surface_event {
|
||||
struct wl_client *client;
|
||||
struct wlr_surface *surface; // can be NULL
|
||||
enum zwp_fullscreen_shell_v1_present_method method;
|
||||
struct wlr_output *output; // can be NULL
|
||||
};
|
||||
|
||||
struct wlr_fullscreen_shell_v1 *wlr_fullscreen_shell_v1_create(
|
||||
struct wl_display *display);
|
||||
void wlr_fullscreen_shell_v1_destroy(struct wlr_fullscreen_shell_v1 *shell);
|
||||
|
||||
#endif
|
@ -13,14 +13,15 @@ endif
|
||||
protocols = [
|
||||
[wl_protocol_dir, 'stable/presentation-time/presentation-time.xml'],
|
||||
[wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
|
||||
[wl_protocol_dir, 'unstable/fullscreen-shell/fullscreen-shell-unstable-v1.xml'],
|
||||
[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],
|
||||
[wl_protocol_dir, 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml'],
|
||||
[wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'],
|
||||
[wl_protocol_dir, 'unstable/relative-pointer/relative-pointer-unstable-v1.xml'],
|
||||
[wl_protocol_dir, 'unstable/tablet/tablet-unstable-v2.xml'],
|
||||
[wl_protocol_dir, 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'],
|
||||
[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
|
||||
[wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'],
|
||||
[wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'],
|
||||
[wl_protocol_dir, 'unstable/relative-pointer/relative-pointer-unstable-v1.xml'],
|
||||
'gamma-control.xml',
|
||||
'gtk-primary-selection.xml',
|
||||
'idle.xml',
|
||||
@ -31,8 +32,8 @@ protocols = [
|
||||
'virtual-keyboard-unstable-v1.xml',
|
||||
'wlr-data-control-unstable-v1.xml',
|
||||
'wlr-export-dmabuf-unstable-v1.xml',
|
||||
'wlr-gamma-control-unstable-v1.xml',
|
||||
'wlr-foreign-toplevel-management-unstable-v1.xml',
|
||||
'wlr-gamma-control-unstable-v1.xml',
|
||||
'wlr-input-inhibitor-unstable-v1.xml',
|
||||
'wlr-layer-shell-unstable-v1.xml',
|
||||
'wlr-screencopy-unstable-v1.xml',
|
||||
@ -41,17 +42,17 @@ protocols = [
|
||||
client_protocols = [
|
||||
[wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
|
||||
[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],
|
||||
[wl_protocol_dir, 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'],
|
||||
[wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'],
|
||||
[wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'],
|
||||
[wl_protocol_dir, 'unstable/relative-pointer/relative-pointer-unstable-v1.xml'],
|
||||
[wl_protocol_dir, 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'],
|
||||
[wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'],
|
||||
'idle.xml',
|
||||
'input-method-unstable-v2.xml',
|
||||
'screenshooter.xml',
|
||||
'text-input-unstable-v3.xml',
|
||||
'wlr-export-dmabuf-unstable-v1.xml',
|
||||
'wlr-gamma-control-unstable-v1.xml',
|
||||
'wlr-foreign-toplevel-management-unstable-v1.xml',
|
||||
'wlr-gamma-control-unstable-v1.xml',
|
||||
'wlr-input-inhibitor-unstable-v1.xml',
|
||||
'wlr-layer-shell-unstable-v1.xml',
|
||||
'wlr-screencopy-unstable-v1.xml',
|
||||
|
@ -30,6 +30,7 @@ lib_wlr_types = static_library(
|
||||
'wlr_data_control_v1.c',
|
||||
'wlr_export_dmabuf_v1.c',
|
||||
'wlr_foreign_toplevel_management_v1.c',
|
||||
'wlr_fullscreen_shell_v1.c',
|
||||
'wlr_gamma_control_v1.c',
|
||||
'wlr_gamma_control.c',
|
||||
'wlr_gtk_primary_selection.c',
|
||||
@ -51,8 +52,8 @@ lib_wlr_types = static_library(
|
||||
'wlr_presentation_time.c',
|
||||
'wlr_primary_selection.c',
|
||||
'wlr_region.c',
|
||||
'wlr_screencopy_v1.c',
|
||||
'wlr_relative_pointer_v1.c',
|
||||
'wlr_screencopy_v1.c',
|
||||
'wlr_screenshooter.c',
|
||||
'wlr_server_decoration.c',
|
||||
'wlr_surface.c',
|
||||
|
129
types/wlr_fullscreen_shell_v1.c
Normal file
129
types/wlr_fullscreen_shell_v1.c
Normal file
@ -0,0 +1,129 @@
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <wlr/types/wlr_fullscreen_shell_v1.h>
|
||||
#include <wlr/types/wlr_output.h>
|
||||
#include <wlr/types/wlr_surface.h>
|
||||
#include <wlr/util/log.h>
|
||||
#include "util/signal.h"
|
||||
|
||||
#define FULLSCREEN_SHELL_VERSION 1
|
||||
|
||||
static const struct zwp_fullscreen_shell_v1_interface shell_impl;
|
||||
|
||||
static struct wlr_fullscreen_shell_v1 *shell_from_resource(
|
||||
struct wl_resource *resource) {
|
||||
assert(wl_resource_instance_of(resource,
|
||||
&zwp_fullscreen_shell_v1_interface, &shell_impl));
|
||||
return wl_resource_get_user_data(resource);
|
||||
}
|
||||
|
||||
static void shell_handle_present_surface(struct wl_client *client,
|
||||
struct wl_resource *shell_resource,
|
||||
struct wl_resource *surface_resource, uint32_t method,
|
||||
struct wl_resource *output_resource) {
|
||||
struct wlr_fullscreen_shell_v1 *shell = shell_from_resource(shell_resource);
|
||||
struct wlr_surface *surface = NULL;
|
||||
if (surface_resource != NULL) {
|
||||
surface = wlr_surface_from_resource(surface_resource);
|
||||
}
|
||||
struct wlr_output *output = NULL;
|
||||
if (output_resource != NULL) {
|
||||
output = wlr_output_from_resource(output_resource);
|
||||
}
|
||||
|
||||
struct wlr_fullscreen_shell_v1_present_surface_event event = {
|
||||
.client = client,
|
||||
.surface = surface,
|
||||
.method = method,
|
||||
.output = output,
|
||||
};
|
||||
wlr_signal_emit_safe(&shell->events.present_surface, &event);
|
||||
}
|
||||
|
||||
static void shell_handle_present_surface_for_mode(struct wl_client *client,
|
||||
struct wl_resource *shell_resource,
|
||||
struct wl_resource *surface_resource,
|
||||
struct wl_resource *output_resource, int32_t framerate,
|
||||
uint32_t feedback_id) {
|
||||
uint32_t version = wl_resource_get_version(shell_resource);
|
||||
struct wl_resource *feedback_resource =
|
||||
wl_resource_create(client, NULL, version, feedback_id);
|
||||
if (feedback_resource == NULL) {
|
||||
wl_resource_post_no_memory(shell_resource);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: add support for mode switch
|
||||
zwp_fullscreen_shell_mode_feedback_v1_send_mode_failed(feedback_resource);
|
||||
wl_resource_destroy(feedback_resource);
|
||||
}
|
||||
|
||||
static const struct zwp_fullscreen_shell_v1_interface shell_impl = {
|
||||
.present_surface = shell_handle_present_surface,
|
||||
.present_surface_for_mode = shell_handle_present_surface_for_mode,
|
||||
};
|
||||
|
||||
static void shell_handle_resource_destroy(struct wl_resource *resource) {
|
||||
wl_list_remove(wl_resource_get_link(resource));
|
||||
}
|
||||
|
||||
static void shell_bind(struct wl_client *client, void *data, uint32_t version,
|
||||
uint32_t id) {
|
||||
struct wlr_fullscreen_shell_v1 *shell = data;
|
||||
|
||||
struct wl_resource *resource = wl_resource_create(client,
|
||||
&zwp_fullscreen_shell_v1_interface, version, id);
|
||||
if (resource == NULL) {
|
||||
wl_client_post_no_memory(client);
|
||||
return;
|
||||
}
|
||||
wl_resource_set_implementation(resource, &shell_impl, shell,
|
||||
shell_handle_resource_destroy);
|
||||
|
||||
wl_list_insert(&shell->resources, wl_resource_get_link(resource));
|
||||
}
|
||||
|
||||
static void handle_display_destroy(struct wl_listener *listener, void *data) {
|
||||
struct wlr_fullscreen_shell_v1 *shell =
|
||||
wl_container_of(listener, shell, display_destroy);
|
||||
wlr_fullscreen_shell_v1_destroy(shell);
|
||||
}
|
||||
|
||||
struct wlr_fullscreen_shell_v1 *wlr_fullscreen_shell_v1_create(
|
||||
struct wl_display *display) {
|
||||
struct wlr_fullscreen_shell_v1 *shell =
|
||||
calloc(1, sizeof(struct wlr_fullscreen_shell_v1));
|
||||
if (shell == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
wl_list_init(&shell->resources);
|
||||
wl_signal_init(&shell->events.destroy);
|
||||
wl_signal_init(&shell->events.present_surface);
|
||||
|
||||
shell->global = wl_global_create(display,
|
||||
&zwp_fullscreen_shell_v1_interface, FULLSCREEN_SHELL_VERSION,
|
||||
shell, shell_bind);
|
||||
if (shell->global == NULL) {
|
||||
free(shell);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
shell->display_destroy.notify = handle_display_destroy;
|
||||
wl_display_add_destroy_listener(display, &shell->display_destroy);
|
||||
|
||||
return shell;
|
||||
}
|
||||
|
||||
void wlr_fullscreen_shell_v1_destroy(struct wlr_fullscreen_shell_v1 *shell) {
|
||||
if (shell == NULL) {
|
||||
return;
|
||||
}
|
||||
wlr_signal_emit_safe(&shell->events.destroy, shell);
|
||||
wl_list_remove(&shell->display_destroy.link);
|
||||
wl_global_destroy(shell->global);
|
||||
struct wl_resource *resource, *resource_tmp;
|
||||
wl_resource_for_each_safe(resource, resource_tmp, &shell->resources) {
|
||||
wl_resource_destroy(resource);
|
||||
}
|
||||
free(shell);
|
||||
}
|
Loading…
Reference in New Issue
Block a user