data-control: add primary selection support

data-control: separate out a data_offer struct

This is a prerequisite to adding primary selection support.

data-control: separate out data_control_source

This is a prerequisite to adding primary selection support, since that
doesn't use wlr_data_source, but rather wlr_primary_selection_source.

Update the data-control protocol

data-control: add primary selection support

Merge create_offer and create_primary_offer

Extract code into data_control_source_destroy()

Fix pointer style

Move resource neutralization to destructor

Store wl_resource in the data_offer

Extract data_offer destruction into a function
This commit is contained in:
Ivan Molodetskikh 2019-02-01 12:49:46 +03:00 committed by emersion
parent 95ff898512
commit 9e49ceb129
3 changed files with 435 additions and 74 deletions

View file

@ -32,9 +32,11 @@ struct wlr_data_control_device_v1 {
struct wlr_seat *seat;
struct wl_resource *selection_offer_resource; // current selection offer
struct wl_resource *primary_selection_offer_resource; // current primary selection offer
struct wl_listener seat_destroy;
struct wl_listener seat_set_selection;
struct wl_listener seat_set_primary_selection;
};
struct wlr_data_control_manager_v1 *wlr_data_control_manager_v1_create(

View file

@ -2,6 +2,7 @@
<protocol name="wlr_data_control_unstable_v1">
<copyright>
Copyright © 2018 Simon Ser
Copyright © 2019 Ivan Molodetskikh
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
@ -40,7 +41,7 @@
interface version number is reset.
</description>
<interface name="zwlr_data_control_manager_v1" version="1">
<interface name="zwlr_data_control_manager_v1" version="2">
<description summary="manager to control data devices">
This interface is a manager that allows creating per-seat data device
controls.
@ -68,9 +69,18 @@
appropriate destroy request has been called.
</description>
</request>
<!-- Version 2 additions -->
<event name="primary_selection" since="2">
<description summary="advertise primary selection support">
This event can be sent when binding to the wlr_data_control_manager
global to advertise that it supports the primary selection.
</description>
</event>
</interface>
<interface name="zwlr_data_control_device_v1" version="1">
<interface name="zwlr_data_control_device_v1" version="2">
<description summary="manage a data device for a seat">
This interface allows a client to manage a seat's selection.
@ -79,8 +89,14 @@
<request name="set_selection">
<description summary="copy data to the selection">
All objects created by the device will still remain valid, until their
appropriate destroy request has been called.
This request asks the compositor to set the selection to the data from
the source on behalf of the client.
The given source may not be used in any further set_selection or
set_primary_selection requests. Attempting to use a previously used
source is a protocol error.
To unset the selection, set the source to NULL.
</description>
<arg name="source" type="object" interface="zwlr_data_control_source_v1"
allow-null="true"/>
@ -95,20 +111,19 @@
<event name="data_offer">
<description summary="introduce a new wlr_data_control_offer">
The data_offer event introduces a new wlr_data_control_offer object,
which will subsequently be used in the wlr_data_control_device.selection
event. Immediately following the wlr_data_control_device.data_offer
event, the new data_offer object will send out
wlr_data_control_offer.offer events to describe the MIME types it
offers.
This event replaces the previous data offer, which should be destroyed
by the client.
which will subsequently be used in either the
wlr_data_control_device.selection event (for the regular clipboard
selections) or the wlr_data_control_device.primary_selection event (for
the primary clipboard selections). Immediately following the
wlr_data_control_device.data_offer event, the new data_offer object
will send out wlr_data_control_offer.offer events to describe the MIME
types it offers.
</description>
<arg name="id" type="new_id" interface="zwlr_data_control_offer_v1"/>
</event>
<event name="selection">
<description summary="introduce a new wlr_data_control_offer">
<description summary="advertise new selection">
The selection event is sent out to notify the client of a new
wlr_data_control_offer for the selection for this device. The
wlr_data_control_device.data_offer and the wlr_data_control_offer.offer
@ -118,6 +133,9 @@
wlr_data_control_offer or NULL is received. The client must destroy the
previous selection wlr_data_control_offer, if any, upon receiving this
event.
The first selection event is sent upon binding the
wlr_data_control_device object.
</description>
<arg name="id" type="object" interface="zwlr_data_control_offer_v1"
allow-null="true"/>
@ -129,6 +147,51 @@
the client.
</description>
</event>
<!-- Version 2 additions -->
<event name="primary_selection" since="2">
<description summary="advertise new primary selection">
The primary_selection event is sent out to notify the client of a new
wlr_data_control_offer for the primary selection for this device. The
wlr_data_control_device.data_offer and the wlr_data_control_offer.offer
events are sent out immediately before this event to introduce the data
offer object. The primary_selection event is sent to a client when a
new primary selection is set. The wlr_data_control_offer is valid until
a new wlr_data_control_offer or NULL is received. The client must
destroy the previous primary selection wlr_data_control_offer, if any,
upon receiving this event.
If the compositor supports primary selection, the first
primary_selection event is sent upon binding the
wlr_data_control_device object.
</description>
<arg name="id" type="object" interface="zwlr_data_control_offer_v1"
allow-null="true"/>
</event>
<request name="set_primary_selection" since="2">
<description summary="copy data to the primary selection">
This request asks the compositor to set the primary selection to the
data from the source on behalf of the client.
The given source may not be used in any further set_selection or
set_primary_selection requests. Attempting to use a previously used
source is a protocol error.
To unset the primary selection, set the source to NULL.
The compositor will ignore this request if it does not support primary
selection.
</description>
<arg name="source" type="object" interface="zwlr_data_control_source_v1"
allow-null="true"/>
</request>
<enum name="error" since="2">
<entry name="used_source" value="1"
summary="source given to set_selection or set_primary_selection was already used before"/>
</enum>
</interface>
<interface name="zwlr_data_control_source_v1" version="1">

View file

@ -5,51 +5,26 @@
#include <unistd.h>
#include <wlr/types/wlr_data_control_v1.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_primary_selection.h>
#include <wlr/util/log.h>
#include "util/signal.h"
#include "wlr-data-control-unstable-v1-protocol.h"
#define DATA_CONTROL_MANAGER_VERSION 1
#define DATA_CONTROL_MANAGER_VERSION 2
struct client_data_source {
struct wlr_data_source source;
struct data_control_source {
struct wl_resource *resource;
struct wl_array mime_types;
bool finalized;
};
static const struct wlr_data_source_impl client_source_impl;
static struct client_data_source *
client_data_source_from_source(struct wlr_data_source *wlr_source) {
assert(wlr_source->impl == &client_source_impl);
return (struct client_data_source *)wlr_source;
}
static void client_source_send(struct wlr_data_source *wlr_source,
const char *mime_type, int fd) {
struct client_data_source *source =
client_data_source_from_source(wlr_source);
zwlr_data_control_source_v1_send_send(source->resource, mime_type, fd);
close(fd);
}
static void client_source_destroy(struct wlr_data_source *wlr_source) {
struct client_data_source *source =
client_data_source_from_source(wlr_source);
zwlr_data_control_source_v1_send_cancelled(source->resource);
// Make the resource inert
wl_resource_set_user_data(source->resource, NULL);
free(source);
}
static const struct wlr_data_source_impl client_source_impl = {
.send = client_source_send,
.destroy = client_source_destroy,
// Only one of these is non-NULL.
struct wlr_data_source *active_source;
struct wlr_primary_selection_source *active_primary_source;
};
static const struct zwlr_data_control_source_v1_interface source_impl;
static struct client_data_source *source_from_resource(
static struct data_control_source *source_from_resource(
struct wl_resource *resource) {
assert(wl_resource_instance_of(resource,
&zwlr_data_control_source_v1_interface, &source_impl));
@ -58,7 +33,7 @@ static struct client_data_source *source_from_resource(
static void source_handle_offer(struct wl_client *client,
struct wl_resource *resource, const char *mime_type) {
struct client_data_source *source = source_from_resource(resource);
struct data_control_source *source = source_from_resource(resource);
if (source == NULL) {
return;
}
@ -66,12 +41,13 @@ static void source_handle_offer(struct wl_client *client,
if (source->finalized) {
wl_resource_post_error(resource,
ZWLR_DATA_CONTROL_SOURCE_V1_ERROR_INVALID_OFFER,
"cannot mutate offer after set_selection");
"cannot mutate offer after set_selection or "
"set_primary_selection");
return;
}
const char **mime_type_ptr;
wl_array_for_each(mime_type_ptr, &source->source.mime_types) {
wl_array_for_each(mime_type_ptr, &source->mime_types) {
if (strcmp(*mime_type_ptr, mime_type) == 0) {
wlr_log(WLR_DEBUG, "Ignoring duplicate MIME type offer %s",
mime_type);
@ -85,7 +61,7 @@ static void source_handle_offer(struct wl_client *client,
return;
}
char **p = wl_array_add(&source->source.mime_types, sizeof(char *));
char **p = wl_array_add(&source->mime_types, sizeof(char *));
if (p == NULL) {
free(dup_mime_type);
wl_resource_post_no_memory(resource);
@ -105,18 +81,156 @@ static const struct zwlr_data_control_source_v1_interface source_impl = {
.destroy = source_handle_destroy,
};
static void source_handle_resource_destroy(struct wl_resource *resource) {
struct client_data_source *source = source_from_resource(resource);
static void data_control_source_destroy(struct data_control_source *source) {
if (source == NULL) {
return;
}
wlr_data_source_destroy(&source->source);
char **p;
wl_array_for_each(p, &source->mime_types) {
free(*p);
}
wl_array_release(&source->mime_types);
// Prevent destructors below from calling this recursively.
wl_resource_set_user_data(source->resource, NULL);
if (source->active_source != NULL) {
wlr_data_source_destroy(source->active_source);
} else if (source->active_primary_source != NULL) {
wlr_primary_selection_source_destroy(
source->active_primary_source);
}
free(source);
}
static void source_handle_resource_destroy(struct wl_resource *resource) {
struct data_control_source *source = source_from_resource(resource);
data_control_source_destroy(source);
}
struct client_data_source {
struct wlr_data_source source;
struct wl_resource *resource;
};
static const struct wlr_data_source_impl client_source_impl;
static struct client_data_source *
client_data_source_from_source(struct wlr_data_source *wlr_source) {
assert(wlr_source->impl == &client_source_impl);
return (struct client_data_source *)wlr_source;
}
static void client_source_send(struct wlr_data_source *wlr_source,
const char *mime_type, int fd) {
struct client_data_source *source =
client_data_source_from_source(wlr_source);
zwlr_data_control_source_v1_send_send(source->resource, mime_type, fd);
close(fd);
}
static void client_source_destroy(struct wlr_data_source *wlr_source) {
struct client_data_source *client_source =
client_data_source_from_source(wlr_source);
struct data_control_source *source =
source_from_resource(client_source->resource);
free(client_source);
if (source == NULL) {
return;
}
source->active_source = NULL;
zwlr_data_control_source_v1_send_cancelled(source->resource);
data_control_source_destroy(source);
}
static const struct wlr_data_source_impl client_source_impl = {
.send = client_source_send,
.destroy = client_source_destroy,
};
struct client_primary_selection_source {
struct wlr_primary_selection_source source;
struct wl_resource *resource;
};
static const struct wlr_primary_selection_source_impl
client_primary_selection_source_impl;
static struct client_primary_selection_source *
client_primary_selection_source_from_source(
struct wlr_primary_selection_source *wlr_source) {
assert(wlr_source->impl == &client_primary_selection_source_impl);
return (struct client_primary_selection_source *)wlr_source;
}
static void client_primary_selection_source_send(
struct wlr_primary_selection_source *wlr_source,
const char *mime_type, int fd) {
struct client_primary_selection_source *source =
client_primary_selection_source_from_source(wlr_source);
zwlr_data_control_source_v1_send_send(source->resource, mime_type, fd);
close(fd);
}
static void client_primary_selection_source_destroy(
struct wlr_primary_selection_source *wlr_source) {
struct client_primary_selection_source *client_source =
client_primary_selection_source_from_source(wlr_source);
struct data_control_source *source =
source_from_resource(client_source->resource);
free(client_source);
if (source == NULL) {
return;
}
source->active_primary_source = NULL;
zwlr_data_control_source_v1_send_cancelled(source->resource);
data_control_source_destroy(source);
}
static const struct wlr_primary_selection_source_impl
client_primary_selection_source_impl = {
.send = client_primary_selection_source_send,
.destroy = client_primary_selection_source_destroy,
};
struct data_offer {
struct wl_resource *resource;
struct wlr_data_control_device_v1 *device;
bool is_primary;
};
static void data_offer_destroy(struct data_offer *offer) {
if (offer == NULL) {
return;
}
struct wlr_data_control_device_v1 *device = offer->device;
if (device != NULL) {
if (offer->is_primary) {
device->primary_selection_offer_resource = NULL;
} else {
device->selection_offer_resource = NULL;
}
}
wl_resource_set_user_data(offer->resource, NULL);
free(offer);
}
static const struct zwlr_data_control_offer_v1_interface offer_impl;
static struct wlr_data_control_device_v1 *control_from_offer_resource(
static struct data_offer *data_offer_from_offer_resource(
struct wl_resource *resource) {
assert(wl_resource_instance_of(resource,
&zwlr_data_control_offer_v1_interface, &offer_impl));
@ -125,12 +239,33 @@ static struct wlr_data_control_device_v1 *control_from_offer_resource(
static void offer_handle_receive(struct wl_client *client,
struct wl_resource *resource, const char *mime_type, int fd) {
struct wlr_data_control_device_v1 *device = control_from_offer_resource(resource);
if (device == NULL || device->seat->selection_source == NULL) {
struct data_offer *offer = data_offer_from_offer_resource(resource);
if (offer == NULL) {
close(fd);
return;
}
wlr_data_source_send(device->seat->selection_source, mime_type, fd);
struct wlr_data_control_device_v1 *device = offer->device;
if (device == NULL) {
close(fd);
return;
}
if (offer->is_primary) {
if (device->seat->primary_selection_source == NULL) {
close(fd);
return;
}
wlr_primary_selection_source_send(
device->seat->primary_selection_source,
mime_type, fd);
} else {
if (device->seat->selection_source == NULL) {
close(fd);
return;
}
wlr_data_source_send(device->seat->selection_source, mime_type, fd);
}
}
static void offer_handle_destroy(struct wl_client *client,
@ -144,28 +279,39 @@ static const struct zwlr_data_control_offer_v1_interface offer_impl = {
};
static void offer_handle_resource_destroy(struct wl_resource *resource) {
struct wlr_data_control_device_v1 *device = control_from_offer_resource(resource);
if (device != NULL) {
device->selection_offer_resource = NULL;
}
struct data_offer *offer = data_offer_from_offer_resource(resource);
data_offer_destroy(offer);
}
static struct wl_resource *create_offer(struct wlr_data_control_device_v1 *device,
struct wlr_data_source *source) {
struct wl_array *mime_types, bool is_primary) {
struct wl_client *client = wl_resource_get_client(device->resource);
struct data_offer *offer = calloc(1, sizeof(struct data_offer));
if (offer == NULL) {
wl_client_post_no_memory(client);
return NULL;
}
offer->device = device;
offer->is_primary = is_primary;
uint32_t version = wl_resource_get_version(device->resource);
struct wl_resource *resource = wl_resource_create(client,
&zwlr_data_control_offer_v1_interface, version, 0);
if (resource == NULL) {
return NULL;
}
wl_resource_set_implementation(resource, &offer_impl, device,
offer->resource = resource;
wl_resource_set_implementation(resource, &offer_impl, offer,
offer_handle_resource_destroy);
zwlr_data_control_device_v1_send_data_offer(device->resource, resource);
char **p;
wl_array_for_each(p, &source->mime_types) {
wl_array_for_each(p, mime_types) {
zwlr_data_control_offer_v1_send_offer(resource, *p);
}
@ -191,16 +337,103 @@ static void control_handle_set_selection(struct wl_client *client,
return;
}
struct client_data_source *source = NULL;
struct data_control_source *source = NULL;
if (source_resource != NULL) {
source = source_from_resource(source_resource);
}
struct wlr_data_source *wlr_source = source ? &source->source : NULL;
if (source == NULL) {
wlr_seat_request_set_selection(device->seat, NULL,
wl_display_next_serial(device->seat->display));
return;
}
if (source->active_source != NULL ||
source->active_primary_source != NULL) {
wl_resource_post_error(control_resource,
ZWLR_DATA_CONTROL_DEVICE_V1_ERROR_USED_SOURCE,
"cannot use a data source in set_selection or "
"set_primary_selection more than once");
return;
}
struct client_data_source *client_source =
calloc(1, sizeof(struct client_data_source));
if (client_source == NULL) {
wl_client_post_no_memory(client);
return;
}
client_source->resource = source_resource;
struct wlr_data_source *wlr_source = &client_source->source;
wlr_data_source_init(wlr_source, &client_source_impl);
source->active_source = wlr_source;
wl_array_release(&wlr_source->mime_types);
wlr_source->mime_types = source->mime_types;
wl_array_init(&source->mime_types);
source->finalized = true;
wlr_seat_request_set_selection(device->seat, wlr_source,
wl_display_next_serial(device->seat->display));
}
static void control_handle_set_primary_selection(struct wl_client *client,
struct wl_resource *control_resource,
struct wl_resource *source_resource) {
struct wlr_data_control_device_v1 *device =
control_from_resource(control_resource);
if (device == NULL) {
return;
}
struct data_control_source *source = NULL;
if (source_resource != NULL) {
source = source_from_resource(source_resource);
}
if (source == NULL) {
wlr_seat_request_set_primary_selection(device->seat, NULL,
wl_display_next_serial(device->seat->display));
return;
}
if (source->active_source != NULL ||
source->active_primary_source != NULL) {
wl_resource_post_error(control_resource,
ZWLR_DATA_CONTROL_DEVICE_V1_ERROR_USED_SOURCE,
"cannot use a data source in set_selection or "
"set_primary_selection more than once");
return;
}
struct client_primary_selection_source *client_source =
calloc(1, sizeof(struct client_primary_selection_source));
if (client_source == NULL) {
wl_client_post_no_memory(client);
return;
}
client_source->resource = source_resource;
struct wlr_primary_selection_source *wlr_source = &client_source->source;
wlr_primary_selection_source_init(wlr_source, &client_primary_selection_source_impl);
source->active_primary_source = wlr_source;
wl_array_release(&wlr_source->mime_types);
wlr_source->mime_types = source->mime_types;
wl_array_init(&source->mime_types);
source->finalized = true;
wlr_seat_request_set_primary_selection(device->seat, wlr_source,
wl_display_next_serial(device->seat->display));
}
static void control_handle_destroy(struct wl_client *client,
struct wl_resource *control_resource) {
wl_resource_destroy(control_resource);
@ -208,6 +441,7 @@ static void control_handle_destroy(struct wl_client *client,
static const struct zwlr_data_control_device_v1_interface control_impl = {
.set_selection = control_handle_set_selection,
.set_primary_selection = control_handle_set_primary_selection,
.destroy = control_handle_destroy,
};
@ -216,12 +450,15 @@ static void control_send_selection(struct wlr_data_control_device_v1 *device) {
if (device->selection_offer_resource != NULL) {
// Make the offer inert
wl_resource_set_user_data(device->selection_offer_resource, NULL);
struct data_offer *offer = data_offer_from_offer_resource(
device->selection_offer_resource);
data_offer_destroy(offer);
}
device->selection_offer_resource = NULL;
if (source != NULL) {
device->selection_offer_resource = create_offer(device, source);
device->selection_offer_resource =
create_offer(device, &source->mime_types, false);
if (device->selection_offer_resource == NULL) {
wl_resource_post_no_memory(device->resource);
return;
@ -232,6 +469,37 @@ static void control_send_selection(struct wlr_data_control_device_v1 *device) {
device->selection_offer_resource);
}
static void control_send_primary_selection(
struct wlr_data_control_device_v1 *device) {
uint32_t version = wl_resource_get_version(device->resource);
if (version < ZWLR_DATA_CONTROL_DEVICE_V1_PRIMARY_SELECTION_SINCE_VERSION) {
return;
}
struct wlr_primary_selection_source *source =
device->seat->primary_selection_source;
if (device->primary_selection_offer_resource != NULL) {
// Make the offer inert
struct data_offer *offer = data_offer_from_offer_resource(
device->primary_selection_offer_resource);
data_offer_destroy(offer);
}
device->primary_selection_offer_resource = NULL;
if (source != NULL) {
device->primary_selection_offer_resource =
create_offer(device, &source->mime_types, true);
if (device->primary_selection_offer_resource == NULL) {
wl_resource_post_no_memory(device->resource);
return;
}
}
zwlr_data_control_device_v1_send_primary_selection(device->resource,
device->primary_selection_offer_resource);
}
static void control_handle_resource_destroy(struct wl_resource *resource) {
struct wlr_data_control_device_v1 *device = control_from_resource(resource);
wlr_data_control_device_v1_destroy(device);
@ -251,6 +519,14 @@ static void control_handle_seat_set_selection(struct wl_listener *listener,
control_send_selection(device);
}
static void control_handle_seat_set_primary_selection(
struct wl_listener *listener,
void *data) {
struct wlr_data_control_device_v1 *device =
wl_container_of(listener, device, seat_set_primary_selection);
control_send_primary_selection(device);
}
void wlr_data_control_device_v1_destroy(struct wlr_data_control_device_v1 *device) {
if (device == NULL) {
return;
@ -259,10 +535,18 @@ void wlr_data_control_device_v1_destroy(struct wlr_data_control_device_v1 *devic
// Make the resources inert
wl_resource_set_user_data(device->resource, NULL);
if (device->selection_offer_resource != NULL) {
wl_resource_set_user_data(device->selection_offer_resource, NULL);
struct data_offer *offer = data_offer_from_offer_resource(
device->selection_offer_resource);
data_offer_destroy(offer);
}
if (device->primary_selection_offer_resource != NULL) {
struct data_offer *offer = data_offer_from_offer_resource(
device->primary_selection_offer_resource);
data_offer_destroy(offer);
}
wl_list_remove(&device->seat_destroy.link);
wl_list_remove(&device->seat_set_selection.link);
wl_list_remove(&device->seat_set_primary_selection.link);
wl_list_remove(&device->link);
free(device);
}
@ -279,19 +563,21 @@ static struct wlr_data_control_manager_v1 *manager_from_resource(
static void manager_handle_create_data_source(struct wl_client *client,
struct wl_resource *manager_resource, uint32_t id) {
struct client_data_source *source =
calloc(1, sizeof(struct client_data_source));
struct data_control_source *source =
calloc(1, sizeof(struct data_control_source));
if (source == NULL) {
wl_resource_post_no_memory(manager_resource);
return;
}
wlr_data_source_init(&source->source, &client_source_impl);
wl_array_init(&source->mime_types);
uint32_t version = wl_resource_get_version(manager_resource);
source->resource = wl_resource_create(client,
&zwlr_data_control_source_v1_interface, version, id);
if (source->resource == NULL) {
wl_resource_post_no_memory(manager_resource);
wl_array_release(&source->mime_types);
free(source);
return;
}
@ -335,6 +621,11 @@ static void manager_handle_get_data_device(struct wl_client *client,
wl_signal_add(&device->seat->events.set_selection,
&device->seat_set_selection);
device->seat_set_primary_selection.notify =
control_handle_seat_set_primary_selection;
wl_signal_add(&device->seat->events.set_primary_selection,
&device->seat_set_primary_selection);
wl_list_insert(&manager->devices, &device->link);
wlr_signal_emit_safe(&manager->events.new_device, device);
@ -343,6 +634,7 @@ static void manager_handle_get_data_device(struct wl_client *client,
device = control_from_resource(resource);
if (device != NULL) {
control_send_selection(device);
control_send_primary_selection(device);
}
}
@ -375,6 +667,10 @@ static void manager_bind(struct wl_client *client, void *data, uint32_t version,
manager_handle_resource_destroy);
wl_list_insert(&manager->resources, wl_resource_get_link(resource));
if (version >= ZWLR_DATA_CONTROL_MANAGER_V1_PRIMARY_SELECTION_SINCE_VERSION) {
zwlr_data_control_manager_v1_send_primary_selection(resource);
}
}
static void handle_display_destroy(struct wl_listener *listener, void *data) {
@ -396,8 +692,8 @@ struct wlr_data_control_manager_v1 *wlr_data_control_manager_v1_create(
wl_signal_init(&manager->events.new_device);
manager->global = wl_global_create(display,
&zwlr_data_control_manager_v1_interface, DATA_CONTROL_MANAGER_VERSION,
manager, manager_bind);
&zwlr_data_control_manager_v1_interface,
DATA_CONTROL_MANAGER_VERSION, manager, manager_bind);
if (manager->global == NULL) {
free(manager);
return NULL;