2018-05-12 16:03:00 +00:00
|
|
|
/*
|
2009-10-06 23:04:32 +00:00
|
|
|
*
|
|
|
|
* Conky, a system monitor, based on torsmo
|
|
|
|
*
|
|
|
|
* Any original torsmo code is licensed under the BSD license
|
|
|
|
*
|
|
|
|
* All code written since the fork of torsmo is licensed under the GPL
|
|
|
|
*
|
|
|
|
* Please see COPYING for details
|
|
|
|
*
|
|
|
|
* Copyright (c) 2004, Hannu Saransaari and Lauri Hakkarainen
|
2024-02-22 13:33:31 +00:00
|
|
|
* Copyright (c) 2005-2024 Brenden Matthews, Philip Kovacs, et. al.
|
2009-10-06 23:04:32 +00:00
|
|
|
* (see AUTHORS)
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program 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 General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
*/
|
2018-05-13 22:46:09 +00:00
|
|
|
#include <vector>
|
2023-02-18 13:33:11 +00:00
|
|
|
#include "colours.h"
|
2009-10-06 23:04:32 +00:00
|
|
|
#include "conky.h"
|
|
|
|
#include "core.h"
|
2022-10-13 00:13:23 +00:00
|
|
|
#include "display-output.hh"
|
2009-10-06 23:04:32 +00:00
|
|
|
#include "logging.h"
|
|
|
|
#include "specials.h"
|
|
|
|
#include "text_object.h"
|
|
|
|
|
2017-12-07 20:00:38 +00:00
|
|
|
/**
|
|
|
|
* Length of a character in bytes.
|
|
|
|
* @param c first byte of the character
|
|
|
|
*/
|
|
|
|
inline int scroll_character_length(char c) {
|
2018-10-18 22:22:20 +00:00
|
|
|
#ifdef BUILD_GUI
|
2018-05-12 16:03:00 +00:00
|
|
|
if (utf8_mode.get(*state)) {
|
2018-05-12 23:26:31 +00:00
|
|
|
auto uc = static_cast<unsigned char>(c);
|
2018-05-12 16:03:00 +00:00
|
|
|
int len = 0;
|
2017-12-07 20:00:38 +00:00
|
|
|
|
2018-05-13 22:46:09 +00:00
|
|
|
if ((uc & 0x80) == 0) { return 1; }
|
2017-12-07 20:00:38 +00:00
|
|
|
|
2018-05-13 22:46:09 +00:00
|
|
|
while (len < 7 && (uc & (0x80 >> len)) != 0) { ++len; }
|
2017-12-07 20:00:38 +00:00
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
return len;
|
|
|
|
}
|
2022-10-13 21:21:32 +00:00
|
|
|
#else /* BUILD_GUI */
|
2018-12-22 18:26:23 +00:00
|
|
|
(void)c;
|
2022-10-13 21:21:32 +00:00
|
|
|
#endif /* BUILD_GUI */
|
2017-12-07 20:00:38 +00:00
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
return 1;
|
2017-12-07 20:00:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-05-12 16:03:00 +00:00
|
|
|
* Check if a byte should be skipped when counting characters to scroll text to
|
|
|
|
* right.
|
2017-12-07 20:00:38 +00:00
|
|
|
*/
|
|
|
|
inline bool scroll_check_skip_byte(char c) {
|
2018-10-18 22:22:20 +00:00
|
|
|
#ifdef BUILD_GUI
|
2018-05-13 22:46:09 +00:00
|
|
|
// Check if byte matches UTF-8 continuation byte pattern (0b10xxxxxx)
|
|
|
|
if (utf8_mode.get(*state) && (c & 0xC0) == 0x80) { return true; }
|
2017-12-07 20:00:38 +00:00
|
|
|
#endif
|
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
return SPECIAL_CHAR == c;
|
2017-12-07 20:00:38 +00:00
|
|
|
}
|
|
|
|
|
2016-07-18 15:11:35 +00:00
|
|
|
#define SCROLL_LEFT 1
|
|
|
|
#define SCROLL_RIGHT 2
|
|
|
|
#define SCROLL_WAIT 3
|
2010-02-17 16:49:03 +00:00
|
|
|
|
2009-10-06 23:04:32 +00:00
|
|
|
struct scroll_data {
|
2018-05-12 16:03:00 +00:00
|
|
|
char *text;
|
|
|
|
unsigned int show;
|
|
|
|
unsigned int step;
|
|
|
|
int wait;
|
|
|
|
unsigned int wait_arg;
|
|
|
|
signed int start;
|
2023-01-02 23:54:53 +00:00
|
|
|
Colour resetcolor;
|
2018-05-12 16:03:00 +00:00
|
|
|
int direction;
|
2009-10-06 23:04:32 +00:00
|
|
|
};
|
|
|
|
|
2017-12-07 20:00:38 +00:00
|
|
|
/**
|
|
|
|
* Get count of characters to right from (sd->start) position.
|
|
|
|
*/
|
2018-05-12 16:03:00 +00:00
|
|
|
static unsigned int scroll_count_characters_to_right(
|
|
|
|
struct scroll_data *sd, const std::vector<char> &buf) {
|
|
|
|
unsigned int n = 0;
|
2018-08-08 15:56:31 +00:00
|
|
|
unsigned int offset = sd->start;
|
2017-12-07 20:00:38 +00:00
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
while ('\0' != buf[offset] && offset < buf.size()) {
|
|
|
|
offset += scroll_character_length(buf[offset]);
|
|
|
|
++n;
|
|
|
|
}
|
2017-12-07 20:00:38 +00:00
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
return n;
|
2017-12-07 20:00:38 +00:00
|
|
|
}
|
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
static void scroll_scroll_left(struct scroll_data *sd,
|
|
|
|
const std::vector<char> &buf,
|
|
|
|
unsigned int amount) {
|
2018-12-22 18:26:23 +00:00
|
|
|
for (unsigned int i = 0; (i < amount) && (buf[sd->start] != '\0') &&
|
2019-02-23 19:43:44 +00:00
|
|
|
(static_cast<unsigned int>(sd->start) < buf.size());
|
2018-05-12 16:03:00 +00:00
|
|
|
++i) {
|
|
|
|
sd->start += scroll_character_length(buf[sd->start]);
|
|
|
|
}
|
|
|
|
|
2019-02-23 19:43:44 +00:00
|
|
|
if (buf[sd->start] == 0 ||
|
|
|
|
static_cast<unsigned int>(sd->start) > strlen(buf.data())) {
|
2018-12-22 18:26:23 +00:00
|
|
|
sd->start = 0;
|
|
|
|
}
|
2017-12-07 20:00:38 +00:00
|
|
|
}
|
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
static void scroll_scroll_right(struct scroll_data *sd,
|
|
|
|
const std::vector<char> &buf,
|
|
|
|
unsigned int amount) {
|
2018-08-08 15:56:31 +00:00
|
|
|
for (unsigned int i = 0; i < amount; ++i) {
|
2018-05-13 22:46:09 +00:00
|
|
|
if (sd->start <= 0) { sd->start = static_cast<int>(strlen(&(buf[0]))); }
|
2017-12-07 20:00:38 +00:00
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
while (--(sd->start) >= 0) {
|
2018-05-13 22:46:09 +00:00
|
|
|
if (!scroll_check_skip_byte(buf[sd->start])) { break; }
|
2017-12-07 20:00:38 +00:00
|
|
|
}
|
2018-05-12 16:03:00 +00:00
|
|
|
}
|
2017-12-07 20:00:38 +00:00
|
|
|
}
|
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
void parse_scroll_arg(struct text_object *obj, const char *arg,
|
|
|
|
void *free_at_crash, char *free_at_crash2) {
|
|
|
|
struct scroll_data *sd;
|
|
|
|
int n1 = 0, n2 = 0;
|
|
|
|
char dirarg[6];
|
|
|
|
|
2018-05-12 23:26:31 +00:00
|
|
|
sd = static_cast<struct scroll_data *>(malloc(sizeof(struct scroll_data)));
|
2018-05-12 16:03:00 +00:00
|
|
|
memset(sd, 0, sizeof(struct scroll_data));
|
|
|
|
|
|
|
|
sd->resetcolor = get_current_text_color();
|
|
|
|
sd->step = 1;
|
|
|
|
sd->direction = SCROLL_LEFT;
|
|
|
|
|
2018-05-12 23:26:31 +00:00
|
|
|
if ((arg != nullptr) && sscanf(arg, "%5s %n", dirarg, &n1) == 1) {
|
|
|
|
if (strcasecmp(dirarg, "right") == 0 || strcasecmp(dirarg, "r") == 0) {
|
2018-05-12 16:03:00 +00:00
|
|
|
sd->direction = SCROLL_RIGHT;
|
2018-05-12 23:26:31 +00:00
|
|
|
} else if (strcasecmp(dirarg, "wait") == 0 ||
|
|
|
|
strcasecmp(dirarg, "w") == 0) {
|
2018-05-12 16:03:00 +00:00
|
|
|
sd->direction = SCROLL_WAIT;
|
2018-05-12 23:26:31 +00:00
|
|
|
} else if (strcasecmp(dirarg, "left") != 0 &&
|
|
|
|
strcasecmp(dirarg, "l") != 0) {
|
2018-05-12 16:03:00 +00:00
|
|
|
n1 = 0;
|
2018-05-12 23:26:31 +00:00
|
|
|
}
|
2018-05-12 16:03:00 +00:00
|
|
|
}
|
|
|
|
|
2018-05-12 23:26:31 +00:00
|
|
|
if ((arg == nullptr) || sscanf(arg + n1, "%u %n", &sd->show, &n2) <= 0) {
|
2018-05-12 16:03:00 +00:00
|
|
|
free(sd);
|
2018-10-18 22:22:20 +00:00
|
|
|
#ifdef BUILD_GUI
|
2018-05-12 16:03:00 +00:00
|
|
|
free(obj->next);
|
2010-02-15 15:02:29 +00:00
|
|
|
#endif
|
2018-05-12 16:03:00 +00:00
|
|
|
free(free_at_crash2);
|
2023-05-05 01:24:55 +00:00
|
|
|
CRIT_ERR_FREE(obj, free_at_crash,
|
|
|
|
"scroll needs arguments: [left|right|wait] <length> [<step>] "
|
|
|
|
"[interval] <text>");
|
2018-05-12 16:03:00 +00:00
|
|
|
}
|
|
|
|
n1 += n2;
|
|
|
|
|
|
|
|
if (sscanf(arg + n1, "%u %n", &sd->step, &n2) == 1) {
|
|
|
|
n1 += n2;
|
|
|
|
} else {
|
|
|
|
sd->step = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sscanf(arg + n1, "%u %n", &sd->wait_arg, &n2) == 1) {
|
|
|
|
n1 += n2;
|
|
|
|
sd->wait = sd->wait_arg;
|
|
|
|
} else {
|
|
|
|
sd->wait_arg = sd->wait = 0;
|
|
|
|
}
|
|
|
|
|
2018-05-12 23:26:31 +00:00
|
|
|
sd->text = static_cast<char *>(malloc(strlen(arg + n1) + sd->show + 1));
|
2018-05-12 16:03:00 +00:00
|
|
|
|
|
|
|
if (strlen(arg) > sd->show && sd->direction != SCROLL_WAIT) {
|
2018-05-12 23:26:31 +00:00
|
|
|
for (n2 = 0; static_cast<unsigned int>(n2) < sd->show; n2++) {
|
2018-05-12 16:03:00 +00:00
|
|
|
sd->text[n2] = ' ';
|
|
|
|
}
|
|
|
|
sd->text[n2] = 0;
|
2018-05-12 23:26:31 +00:00
|
|
|
} else {
|
2018-05-12 16:03:00 +00:00
|
|
|
sd->text[0] = 0;
|
2018-05-12 23:26:31 +00:00
|
|
|
}
|
2018-05-12 16:03:00 +00:00
|
|
|
|
2023-02-18 13:33:11 +00:00
|
|
|
strncat(sd->text, arg + n1, max_user_text.get(*state) - n1);
|
2018-05-12 16:03:00 +00:00
|
|
|
sd->start = sd->direction == SCROLL_WAIT ? strlen(sd->text) : 0;
|
2018-05-12 23:26:31 +00:00
|
|
|
obj->sub =
|
|
|
|
static_cast<struct text_object *>(malloc(sizeof(struct text_object)));
|
2018-05-12 16:03:00 +00:00
|
|
|
extract_variable_text_internal(obj->sub, sd->text);
|
|
|
|
|
|
|
|
obj->data.opaque = sd;
|
2009-11-08 13:49:27 +00:00
|
|
|
|
2018-10-18 22:22:20 +00:00
|
|
|
#ifdef BUILD_GUI
|
2018-05-12 16:03:00 +00:00
|
|
|
/* add a color object right after scroll to reset any color changes */
|
2018-10-18 22:22:20 +00:00
|
|
|
#endif /* BUILD_GUI */
|
2009-10-06 23:04:32 +00:00
|
|
|
}
|
|
|
|
|
2018-08-07 23:22:23 +00:00
|
|
|
void print_scroll(struct text_object *obj, char *p, unsigned int p_max_size) {
|
2018-05-12 23:26:31 +00:00
|
|
|
auto *sd = static_cast<struct scroll_data *>(obj->data.opaque);
|
2018-05-12 16:03:00 +00:00
|
|
|
unsigned int j, colorchanges = 0, frontcolorchanges = 0,
|
2019-05-20 18:54:05 +00:00
|
|
|
visibcolorchanges = 0;
|
2018-05-12 23:26:31 +00:00
|
|
|
std::vector<char> buf(max_user_text.get(*state), static_cast<char>(0));
|
2018-05-12 16:03:00 +00:00
|
|
|
|
2018-05-13 22:46:09 +00:00
|
|
|
if (sd == nullptr) { return; }
|
2018-05-12 16:03:00 +00:00
|
|
|
|
|
|
|
generate_text_internal(&(buf[0]), max_user_text.get(*state), *obj->sub);
|
|
|
|
for (j = 0; buf[j] != 0; j++) {
|
2019-05-20 18:54:05 +00:00
|
|
|
if (buf[j] == '\n') {
|
|
|
|
// place all the lines behind each other with LINESEPARATOR between them
|
2009-10-06 23:04:32 +00:00
|
|
|
#define LINESEPARATOR '|'
|
2019-05-20 18:54:05 +00:00
|
|
|
buf[j] = LINESEPARATOR;
|
|
|
|
} else if (buf[j] == SPECIAL_CHAR) {
|
|
|
|
colorchanges++;
|
2018-05-12 16:03:00 +00:00
|
|
|
}
|
|
|
|
}
|
2019-05-20 18:54:05 +00:00
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
// no scrolling necessary if the length of the text to scroll is too short
|
|
|
|
if (strlen(&(buf[0])) - colorchanges <= sd->show) {
|
|
|
|
snprintf(p, p_max_size, "%s", &(buf[0]));
|
|
|
|
return;
|
|
|
|
}
|
2019-05-20 18:54:05 +00:00
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
// if length of text changed to shorter so the (sd->start) is already
|
|
|
|
// outside of actual text then reset (sd->start)
|
2019-02-23 19:43:44 +00:00
|
|
|
if (static_cast<unsigned int>(sd->start) >= strlen(&(buf[0]))) {
|
|
|
|
sd->start = 0;
|
|
|
|
}
|
2019-05-20 18:54:05 +00:00
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
// make sure a colorchange at the front is not part of the string we are going
|
|
|
|
// to show
|
2018-05-13 22:46:09 +00:00
|
|
|
while (buf[sd->start] == SPECIAL_CHAR) { sd->start++; }
|
2019-05-20 18:54:05 +00:00
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
// place all chars that should be visible in p, including colorchanges
|
2019-05-20 18:54:05 +00:00
|
|
|
unsigned int visiblechars;
|
2018-05-12 16:03:00 +00:00
|
|
|
for (j = 0, visiblechars = 0; visiblechars < sd->show;) {
|
2019-05-20 18:54:05 +00:00
|
|
|
char c = buf[sd->start + j];
|
|
|
|
p[j] = c;
|
2018-05-13 22:46:09 +00:00
|
|
|
if (0 == c) { break; }
|
2017-12-07 20:00:38 +00:00
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
++j;
|
2017-12-07 20:00:38 +00:00
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
if (SPECIAL_CHAR == c) {
|
|
|
|
++visibcolorchanges;
|
|
|
|
} else {
|
|
|
|
int l = scroll_character_length(c);
|
2017-12-07 20:00:38 +00:00
|
|
|
|
2018-05-12 23:26:31 +00:00
|
|
|
while (--l != 0) {
|
2018-05-12 16:03:00 +00:00
|
|
|
p[j] = buf[sd->start + j];
|
|
|
|
++j;
|
|
|
|
}
|
2017-12-07 20:00:38 +00:00
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
++visiblechars;
|
|
|
|
}
|
|
|
|
}
|
2019-05-20 18:54:05 +00:00
|
|
|
|
2018-05-13 22:46:09 +00:00
|
|
|
for (; visiblechars < sd->show; j++, visiblechars++) { p[j] = ' '; }
|
2018-05-12 16:03:00 +00:00
|
|
|
p[j] = 0;
|
2019-05-20 18:54:05 +00:00
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
// count colorchanges in front of the visible part and place that many
|
|
|
|
// colorchanges in front of the visible part
|
2018-05-12 23:26:31 +00:00
|
|
|
for (j = 0; j < static_cast<unsigned>(sd->start); j++) {
|
2018-05-13 22:46:09 +00:00
|
|
|
if (buf[j] == SPECIAL_CHAR) { frontcolorchanges++; }
|
2018-05-12 16:03:00 +00:00
|
|
|
}
|
2019-05-20 18:54:05 +00:00
|
|
|
|
2018-05-13 22:46:09 +00:00
|
|
|
int pwithcolors_len = strlen(p) + 4 + colorchanges - visibcolorchanges;
|
2019-05-20 18:54:05 +00:00
|
|
|
char *pwithcolors = static_cast<char *>(malloc(pwithcolors_len));
|
|
|
|
|
2018-05-13 22:46:09 +00:00
|
|
|
for (j = 0; j < frontcolorchanges; j++) { pwithcolors[j] = SPECIAL_CHAR; }
|
2018-05-12 16:03:00 +00:00
|
|
|
pwithcolors[j] = 0;
|
2018-05-13 22:46:09 +00:00
|
|
|
strncat(pwithcolors, p, pwithcolors_len);
|
2019-05-20 18:54:05 +00:00
|
|
|
unsigned int strend = strlen(pwithcolors);
|
2018-05-12 16:03:00 +00:00
|
|
|
// and place the colorchanges not in front or in the visible part behind the
|
|
|
|
// visible part
|
|
|
|
for (j = 0; j < colorchanges - frontcolorchanges - visibcolorchanges; j++) {
|
|
|
|
pwithcolors[strend + j] = SPECIAL_CHAR;
|
|
|
|
}
|
|
|
|
pwithcolors[strend + j] = 0;
|
2018-05-13 22:46:09 +00:00
|
|
|
strncpy(p, pwithcolors, p_max_size);
|
2018-05-12 16:03:00 +00:00
|
|
|
free(pwithcolors);
|
2019-05-20 18:54:05 +00:00
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
// scroll
|
|
|
|
if (sd->direction == SCROLL_LEFT) {
|
|
|
|
scroll_scroll_left(sd, buf, sd->step);
|
|
|
|
} else if (sd->direction == SCROLL_WAIT) {
|
|
|
|
unsigned int charsleft = scroll_count_characters_to_right(sd, buf);
|
|
|
|
|
2019-05-20 18:54:05 +00:00
|
|
|
if (sd->show >= charsleft) {
|
2019-05-20 18:52:12 +00:00
|
|
|
if (sd->wait_arg == 0) {
|
2018-05-12 16:03:00 +00:00
|
|
|
sd->start = 0;
|
2019-05-20 18:52:12 +00:00
|
|
|
} else {
|
|
|
|
sd->wait--;
|
|
|
|
if (sd->wait <= 0 && sd->wait_arg != 1) {
|
|
|
|
sd->wait = sd->wait_arg;
|
|
|
|
} else {
|
|
|
|
sd->start = 0;
|
|
|
|
}
|
2018-05-12 16:03:00 +00:00
|
|
|
}
|
|
|
|
} else {
|
2019-05-20 18:52:12 +00:00
|
|
|
if (sd->wait_arg == 0 || sd->wait_arg == 1 || sd->wait <= 0) {
|
2018-05-12 16:03:00 +00:00
|
|
|
sd->wait = 0;
|
|
|
|
|
|
|
|
if (sd->step < charsleft) {
|
|
|
|
scroll_scroll_left(sd, buf, sd->step);
|
|
|
|
} else {
|
|
|
|
scroll_scroll_left(sd, buf, charsleft);
|
2017-12-07 20:00:38 +00:00
|
|
|
}
|
2019-05-20 18:52:12 +00:00
|
|
|
} else {
|
|
|
|
sd->wait--;
|
2018-05-12 16:03:00 +00:00
|
|
|
}
|
2019-05-20 18:54:05 +00:00
|
|
|
}
|
|
|
|
} else {
|
2018-05-12 16:03:00 +00:00
|
|
|
scroll_scroll_right(sd, buf, sd->step);
|
|
|
|
}
|
2019-05-20 18:54:05 +00:00
|
|
|
|
2018-10-18 22:22:20 +00:00
|
|
|
#ifdef BUILD_GUI
|
2018-05-12 16:03:00 +00:00
|
|
|
// reset color when scroll is finished
|
2022-10-13 00:13:23 +00:00
|
|
|
if (display_output() && display_output()->graphical()) {
|
2024-04-23 21:15:37 +00:00
|
|
|
new_special(p + strlen(p), text_node_t::FG)->arg =
|
|
|
|
sd->resetcolor.to_argb32();
|
2018-05-12 23:26:31 +00:00
|
|
|
}
|
2010-02-16 14:45:32 +00:00
|
|
|
#endif
|
2009-10-06 23:04:32 +00:00
|
|
|
}
|
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
void free_scroll(struct text_object *obj) {
|
2018-05-12 23:26:31 +00:00
|
|
|
auto *sd = static_cast<struct scroll_data *>(obj->data.opaque);
|
2009-10-06 23:04:32 +00:00
|
|
|
|
2018-05-13 22:46:09 +00:00
|
|
|
if (sd == nullptr) { return; }
|
2009-10-06 23:04:32 +00:00
|
|
|
|
2018-05-12 16:03:00 +00:00
|
|
|
free_and_zero(sd->text);
|
|
|
|
free_text_objects(obj->sub);
|
|
|
|
free_and_zero(obj->sub);
|
|
|
|
free_and_zero(obj->data.opaque);
|
2009-10-06 23:04:32 +00:00
|
|
|
}
|