mirror of
https://github.com/edeproject/ede.git
synced 2023-08-10 21:13:03 +03:00
655 lines
16 KiB
C++
655 lines
16 KiB
C++
//
|
|
// Util.cc for pekwm
|
|
// Copyright © 2002-2009 Claes Nästén <me@pekdon.net>
|
|
//
|
|
// misc.cc for aewm++
|
|
// Copyright (C) 2000 Frank Hale <frankhale@yahoo.com>
|
|
// http://sapphire.sourceforge.net/
|
|
//
|
|
// This program is licensed under the GNU GPL.
|
|
// See the LICENSE file for more information.
|
|
//
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif // HAVE_CONFIG_H
|
|
|
|
#include <cerrno>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <fstream>
|
|
#include <list>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <cwchar>
|
|
|
|
extern "C" {
|
|
#include <iconv.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <pwd.h>
|
|
#include <errno.h>
|
|
}
|
|
|
|
#include "Util.hh"
|
|
|
|
using std::cerr;
|
|
using std::endl;
|
|
using std::ostringstream;
|
|
using std::string;
|
|
using std::transform;
|
|
using std::vector;
|
|
using std::wstring;
|
|
using std::list;
|
|
using std::ifstream;
|
|
using std::ofstream;
|
|
using std::find;
|
|
using std::map;
|
|
using std::getenv;
|
|
using std::wcstombs;
|
|
using std::wmemset;
|
|
using std::mbstowcs;
|
|
using std::exit;
|
|
|
|
namespace Util {
|
|
|
|
#ifndef HOST_NAME_MAX
|
|
#define HOST_NAME_MAX 255
|
|
#endif // HOST_NAME_MAX
|
|
|
|
static iconv_t do_iconv_open(const char **from_names, const char **to_names);
|
|
static size_t do_iconv (iconv_t ic, const char **inp, size_t *in_bytes,
|
|
char **outp, size_t *out_bytes);
|
|
|
|
// Initializers, members used for shared buffer
|
|
unsigned int WIDE_STRING_COUNT = 0;
|
|
iconv_t IC_TO_WC = reinterpret_cast<iconv_t>(-1);
|
|
iconv_t IC_TO_UTF8 = reinterpret_cast<iconv_t>(-1);
|
|
char *ICONV_BUF = 0;
|
|
size_t ICONV_BUF_LEN = 0;
|
|
|
|
// Constants, name of iconv internal names
|
|
const char *ICONV_WC_NAMES[] = {"WCHAR_T", "UCS-4", 0};
|
|
const char *ICONV_UTF8_NAMES[] = {"UTF-8", "UTF8", 0};
|
|
|
|
const char *ICONV_UTF8_INVALID_STR = "<INVALID>";
|
|
const wchar_t *ICONV_WIDE_INVALID_STR = L"<INVALID>";
|
|
|
|
// Constants, maximum number of bytes a single UTF-8 character can use.
|
|
const size_t UTF8_MAX_BYTES = 6;
|
|
|
|
//! @brief Fork and execute command with /bin/sh and execlp
|
|
void
|
|
forkExec(std::string command)
|
|
{
|
|
if (command.length() == 0) {
|
|
#ifdef DEBUG
|
|
cerr << __FILE__ << "@" << __LINE__ << ": "
|
|
<< "Util::forkExec() *** command length == 0" << endl;
|
|
#endif // DEBUG
|
|
return;
|
|
}
|
|
|
|
pid_t pid = fork();
|
|
switch (pid) {
|
|
case 0:
|
|
setsid();
|
|
execlp("/bin/sh", "sh", "-c", command.c_str(), (char *) 0);
|
|
cerr << __FILE__ << "@" << __LINE__ << ": "
|
|
<< "Util::forkExec(" << command << ") execlp failed." << endl;
|
|
exit(1);
|
|
case -1:
|
|
cerr << __FILE__ << "@" << __LINE__ << ": "
|
|
<< "Util::forkExec(" << command << ") fork failed." << endl;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wrapper for gethostname returning a string instead of populating
|
|
* char buffer.
|
|
*/
|
|
std::string
|
|
getHostname(void)
|
|
{
|
|
string hostname;
|
|
|
|
// Set WM_CLIENT_MACHINE
|
|
char hostname_buf[HOST_NAME_MAX + 1];
|
|
if (! gethostname(hostname_buf, HOST_NAME_MAX)) {
|
|
// Make sure it is null terminated
|
|
hostname_buf[HOST_NAME_MAX] = '\0';
|
|
hostname = hostname_buf;
|
|
}
|
|
|
|
return hostname;
|
|
}
|
|
|
|
//! @brief Determines if the file exists
|
|
bool
|
|
isFile(const std::string &file)
|
|
{
|
|
if (file.size() == 0) {
|
|
return false;
|
|
}
|
|
|
|
struct stat stat_buf;
|
|
if (stat(file.c_str(), &stat_buf) == 0) {
|
|
return (S_ISREG(stat_buf.st_mode));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//! @brief Determines if the file is executable for the current user.
|
|
bool
|
|
isExecutable(const std::string &file)
|
|
{
|
|
if (file.size() == 0) {
|
|
#ifdef DEBUG
|
|
cerr << __FILE__ << "@" << __LINE__ << ": "
|
|
<< "Util::isExecutable() *** file length == 0" << endl;
|
|
#endif // DEBUG
|
|
return false;
|
|
}
|
|
|
|
struct stat stat_buf;
|
|
if (! stat(file.c_str(), &stat_buf)) {
|
|
if (stat_buf.st_uid == getuid()) { // user readable and executable
|
|
if ((stat_buf.st_mode&S_IRUSR) && (stat_buf.st_mode&S_IXUSR)) {
|
|
return true;
|
|
}
|
|
}
|
|
if (getgid() == stat_buf.st_gid) { // group readable and executable
|
|
if ((stat_buf.st_mode&S_IRGRP) && (stat_buf.st_mode&S_IXGRP)) {
|
|
return true;
|
|
}
|
|
}
|
|
if ((stat_buf.st_mode&S_IROTH) && (stat_buf.st_mode&S_IXOTH)) {
|
|
return true; // other readable and executable
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get file mtime.
|
|
*/
|
|
time_t
|
|
getMtime(const std::string &file)
|
|
{
|
|
struct stat stat_buf;
|
|
|
|
if (! stat(file.c_str(), &stat_buf)) {
|
|
return stat_buf.st_mtime;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if file has different mtime than provided mtime.
|
|
*/
|
|
bool
|
|
isFileChanged(const std::string &file, time_t &mtime)
|
|
{
|
|
time_t cur_mtime = getMtime(file);
|
|
if (cur_mtime != mtime) {
|
|
mtime = cur_mtime;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if old_file needs to be reloaded due to it being different
|
|
* from new_file, path or mtime.
|
|
*/
|
|
bool
|
|
requireReload(std::map <std::string, time_t> &state, const std::string &file)
|
|
{
|
|
// Check for the file, signal reload if not previously loaded.
|
|
map<string, time_t>::iterator it(state.find(file));
|
|
if (it == state.end()) {
|
|
return true;
|
|
}
|
|
|
|
// Check state of all files, if one is updated reload.
|
|
for (it = state.begin(); it != state.end(); ++it) {
|
|
if (isFileChanged(it->first, it->second)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Copies a single text file.
|
|
*/
|
|
bool
|
|
copyTextFile(const std::string &from, const std::string &to)
|
|
{
|
|
if ((from.length() == 0) || (to.length() == 0)) {
|
|
return false;
|
|
}
|
|
|
|
ifstream stream_from(from.c_str());
|
|
if (! stream_from.good()) {
|
|
cerr << __FILE__ << "@" << __LINE__ << ": "
|
|
<< "Can't copy: " << from << " to: " << to << endl;
|
|
return false;
|
|
}
|
|
|
|
ofstream stream_to(to.c_str());
|
|
if (! stream_to.good()) {
|
|
cerr << __FILE__ << "@" << __LINE__ << ": "
|
|
<< "Can't copy: " << from << " to: " << to << endl;
|
|
return false;
|
|
}
|
|
|
|
stream_to << stream_from.rdbuf();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get name of the current user.
|
|
*/
|
|
string
|
|
getUserName(void)
|
|
{
|
|
// Try to lookup current user with
|
|
struct passwd *entry = getpwuid(geteuid());
|
|
|
|
if (entry && entry->pw_name) {
|
|
return entry->pw_name;
|
|
} else {
|
|
if (getenv("USER")) {
|
|
return getenv("USER");
|
|
} else {
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//! @brief Returns .extension of file
|
|
std::string
|
|
getFileExt(const std::string &file)
|
|
{
|
|
string::size_type pos = file.find_last_of('.');
|
|
if ((pos != string::npos) && (pos < file.size())) {
|
|
return file.substr(pos + 1, file.size() - pos - 1);
|
|
} else {
|
|
return string("");
|
|
}
|
|
}
|
|
|
|
//! @brief Returns dir part of file
|
|
std::string
|
|
getDir(const std::string &file)
|
|
{
|
|
string::size_type pos = file.find_last_of('/');
|
|
if ((pos != string::npos) && (pos < file.size())) {
|
|
return file.substr(0, pos);
|
|
} else {
|
|
return string("");
|
|
}
|
|
}
|
|
|
|
//! @brief Replaces the ~ with the complete homedir path.
|
|
void
|
|
expandFileName(std::string &file)
|
|
{
|
|
if (file.size() > 0) {
|
|
if (file[0] == '~') {
|
|
file.replace(0, 1, getenv("HOME"));
|
|
}
|
|
}
|
|
}
|
|
|
|
//! @brief Split the string str based on separator sep and put into vals
|
|
//!
|
|
//! This splits the string str into to max_tokens parts and puts in the vector
|
|
//! vals. If max is 0 then it'll split it into as many tokens
|
|
//! as possible, max defaults to 0.
|
|
//! splitString returns the number of tokens it put into vals.
|
|
//!
|
|
//! @param str String to split
|
|
//! @param vals Vector to put split values into
|
|
//! @param sep Separators to use when splitting string
|
|
//! @param max Maximum number of elements to put into vals (optional)
|
|
//! @param include_empty Include empty elements, defaults to false.
|
|
//! @return Number of tokens inserted into vals
|
|
uint
|
|
splitString(const std::string str, std::vector<std::string> &toks, const char *sep, uint max, bool include_empty)
|
|
{
|
|
if (str.size() < 1) {
|
|
return 0;
|
|
}
|
|
|
|
uint n = toks.size();
|
|
string::size_type s, e;
|
|
|
|
s = str.find_first_not_of(" \t\n");
|
|
for (uint i = 0; ((i < max) || (max == 0)) && (s != string::npos); ++i) {
|
|
e = str.find_first_of(sep, s);
|
|
|
|
if ((e != string::npos) && (i < (max - 1))) {
|
|
toks.push_back(str.substr(s, e - s));
|
|
} else if (s < str.size()) {
|
|
toks.push_back(str.substr(s, str.size() - s));
|
|
break;
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
if (include_empty) {
|
|
s = str.find_first_of(sep, e + 1);
|
|
if (s != (e + 1)) {
|
|
s = str.find_first_not_of(sep, e);
|
|
}
|
|
} else {
|
|
s = str.find_first_not_of(sep, e);
|
|
}
|
|
}
|
|
|
|
return (toks.size() - n);
|
|
}
|
|
|
|
//! @brief Converts wide-character string to multibyte version
|
|
//! @param str String to convert.
|
|
//! @return Returns multibyte version of string.
|
|
std::string
|
|
to_mb_str(const std::wstring &str)
|
|
{
|
|
size_t ret, num = str.size() * 6 + 1;
|
|
char *buf = new char[num];
|
|
memset(buf, '\0', num);
|
|
|
|
ret = wcstombs(buf, str.c_str(), num);
|
|
if (ret == static_cast<size_t>(-1)) {
|
|
cerr << " *** WARNING: failed to convert wide string to multibyte string" << endl;
|
|
}
|
|
string ret_str(buf);
|
|
|
|
delete [] buf;
|
|
|
|
return ret_str;
|
|
}
|
|
|
|
//! @brief Converts multibyte string to wide-character version
|
|
//! @param str String to convert.
|
|
//! @return Returns wide-character version of string.
|
|
std::wstring
|
|
to_wide_str(const std::string &str)
|
|
{
|
|
size_t ret, num = str.size() + 1;
|
|
wchar_t *buf = new wchar_t[num];
|
|
wmemset(buf, L'\0', num);
|
|
|
|
ret = mbstowcs(buf, str.c_str(), num);
|
|
if (ret == static_cast<size_t>(-1)) {
|
|
cerr << " *** WARNING: failed to convert multibyte string to wide string" << endl;
|
|
}
|
|
wstring ret_str(buf);
|
|
|
|
delete [] buf;
|
|
|
|
return ret_str;
|
|
}
|
|
|
|
//! @brief Open iconv handle with to/from names.
|
|
//! @param from_names null terminated list of from name alternatives.
|
|
//! @param to_names null terminated list of to name alternatives.
|
|
//! @return iconv_t handle on success, else -1.
|
|
iconv_t
|
|
do_iconv_open(const char **from_names, const char **to_names)
|
|
{
|
|
iconv_t ic = reinterpret_cast<iconv_t>(-1);
|
|
|
|
// Try all combinations of from/to name's to get a working
|
|
// conversion handle.
|
|
for (unsigned int i = 0; from_names[i]; ++i) {
|
|
for (unsigned int j = 0; to_names[j]; ++j) {
|
|
ic = iconv_open (to_names[j], from_names[i]);
|
|
if (ic != reinterpret_cast<iconv_t>(-1)) {
|
|
#ifdef HAVE_ICONVCTL
|
|
int int_value_one = 1;
|
|
iconvctl(ic, ICONV_SET_DISCARD_ILSEQ, &int_value_one);
|
|
#endif // HAVE_ICONVCTL
|
|
return ic;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ic;
|
|
}
|
|
|
|
//! @brief Iconv wrapper to hide different definitions of iconv.
|
|
//! @param ic iconv handle.
|
|
//! @param inp Input pointer.
|
|
//! @param in_bytes Input bytes.
|
|
//! @param outp Output pointer.
|
|
//! @param out_bytes Output bytes.
|
|
//! @return number of bytes converted irreversible or -1 on error.
|
|
size_t
|
|
do_iconv (iconv_t ic, const char **inp, size_t *in_bytes,
|
|
char **outp, size_t *out_bytes)
|
|
{
|
|
#ifdef ICONV_CONST
|
|
return iconv(ic, inp, in_bytes, outp, out_bytes);
|
|
#else // !ICONV_CONST
|
|
# ifdef __minix
|
|
// this is as MESS across OS-es...
|
|
return iconv(ic, (const char**)inp, in_bytes, outp, out_bytes);
|
|
# else
|
|
return iconv(ic, const_cast<char**>(inp), in_bytes, outp, out_bytes);
|
|
# endif
|
|
#endif // ICONV_CONST
|
|
}
|
|
|
|
//! @brief Init iconv conversion.
|
|
void
|
|
iconv_init (void)
|
|
{
|
|
// Cleanup previous init if any, being paranoid.
|
|
iconv_deinit ();
|
|
|
|
// Raise exception if this fails
|
|
IC_TO_WC = do_iconv_open (ICONV_UTF8_NAMES, ICONV_WC_NAMES);
|
|
IC_TO_UTF8 = do_iconv_open (ICONV_WC_NAMES, ICONV_UTF8_NAMES);
|
|
|
|
// Equal mean
|
|
if (IC_TO_WC != reinterpret_cast<iconv_t>(-1)
|
|
&& IC_TO_UTF8 != reinterpret_cast<iconv_t>(-1)) {
|
|
// Create shared buffer.
|
|
ICONV_BUF_LEN = 1024;
|
|
ICONV_BUF = new char[ICONV_BUF_LEN];
|
|
}
|
|
}
|
|
|
|
//! @brief Deinit iconv conversion.
|
|
void
|
|
iconv_deinit (void)
|
|
{
|
|
// Cleanup resources
|
|
if (IC_TO_WC != reinterpret_cast<iconv_t>(-1)) {
|
|
iconv_close (IC_TO_WC);
|
|
}
|
|
|
|
if (IC_TO_UTF8 != reinterpret_cast<iconv_t>(-1)) {
|
|
iconv_close (IC_TO_UTF8);
|
|
}
|
|
|
|
if (ICONV_BUF) {
|
|
delete [] ICONV_BUF;
|
|
}
|
|
|
|
// Set members to safe values
|
|
IC_TO_WC = reinterpret_cast<iconv_t>(-1);
|
|
IC_TO_UTF8 = reinterpret_cast<iconv_t>(-1);
|
|
ICONV_BUF = 0;
|
|
ICONV_BUF_LEN = 0;
|
|
}
|
|
|
|
//! @brief Validate buffer size, grow if required.
|
|
//! @param size Required size.
|
|
void
|
|
iconv_buf_grow (size_t size)
|
|
{
|
|
if (ICONV_BUF_LEN < size) {
|
|
// Free resources, if any.
|
|
if (ICONV_BUF) {
|
|
delete [] ICONV_BUF;
|
|
}
|
|
|
|
// Calculate new buffer length and allocate new buffer
|
|
for (; ICONV_BUF_LEN < size; ICONV_BUF_LEN *= 2)
|
|
;
|
|
ICONV_BUF = new char[ICONV_BUF_LEN];
|
|
}
|
|
}
|
|
|
|
//! @brief Converts wide-character string to UTF-8
|
|
//! @param str String to convert.
|
|
//! @return Returns UTF-8 representation of string.
|
|
std::string
|
|
to_utf8_str(const std::wstring &str)
|
|
{
|
|
string utf8_str;
|
|
|
|
// Calculate length
|
|
size_t in_bytes = str.size() * sizeof (wchar_t);
|
|
size_t out_bytes = str.size() * UTF8_MAX_BYTES + 1;
|
|
|
|
iconv_buf_grow(out_bytes);
|
|
|
|
// Convert
|
|
const char *inp = reinterpret_cast<const char*>(str.c_str());
|
|
char *outp = ICONV_BUF;
|
|
size_t len = do_iconv (IC_TO_UTF8, &inp, &in_bytes, &outp, &out_bytes);
|
|
if (len != static_cast<size_t>(-1)) {
|
|
// Terminate string and cache result
|
|
*outp = '\0';
|
|
|
|
utf8_str = ICONV_BUF;
|
|
|
|
} else {
|
|
cerr << " *** WARNING: to_utf8_str, failed with error "
|
|
<< strerror (errno) << endl;
|
|
utf8_str = ICONV_UTF8_INVALID_STR;
|
|
}
|
|
|
|
return utf8_str;
|
|
}
|
|
|
|
//! @brief Converts to wide string from UTF-8
|
|
//! @param str String to convert.
|
|
//! @return Returns wide representation of string.
|
|
std::wstring
|
|
from_utf8_str(const std::string &str)
|
|
{
|
|
wstring wide_str;
|
|
|
|
// Calculate length
|
|
size_t in_bytes = str.size();
|
|
size_t out_bytes = (in_bytes + 1) * sizeof(wchar_t);
|
|
|
|
iconv_buf_grow(out_bytes);
|
|
|
|
// Convert
|
|
const char *inp = str.c_str();
|
|
char *outp = ICONV_BUF;
|
|
size_t len = do_iconv(IC_TO_WC, &inp, &in_bytes, &outp, &out_bytes);
|
|
if (len != static_cast<size_t>(-1)) {
|
|
// Terminate string and cache result
|
|
*reinterpret_cast<wchar_t*>(outp) = L'\0';
|
|
|
|
wide_str = reinterpret_cast<wchar_t*>(ICONV_BUF);
|
|
} else {
|
|
cerr << " *** WARNING: from_utf8_str, failed on string \""
|
|
<< str << "\"." << endl;
|
|
wide_str = ICONV_WIDE_INVALID_STR;
|
|
}
|
|
|
|
return wide_str;
|
|
}
|
|
|
|
/**
|
|
* Add object to list making sure there are no duplicates.
|
|
*/
|
|
void
|
|
file_backed_list::push_back_unique(const std::wstring &entry)
|
|
{
|
|
list<wstring>::iterator it(find(begin(), end(), entry));
|
|
if (it != end()) {
|
|
erase(it);
|
|
}
|
|
|
|
push_back(entry);
|
|
}
|
|
|
|
/**
|
|
* Load list from file, updating _path if set.
|
|
*
|
|
* @param path Load data from path.
|
|
* @return Number of elements loaded.
|
|
*/
|
|
unsigned int
|
|
file_backed_list::load (const std::string &path)
|
|
{
|
|
unsigned int loaded = 0;
|
|
ifstream ifile;
|
|
ifile.open(path.c_str());
|
|
if (ifile.is_open()) {
|
|
// Update only path if successfully opened.
|
|
_path = path;
|
|
|
|
string mb_line;
|
|
|
|
while (ifile.good()) {
|
|
getline(ifile, mb_line);
|
|
if (mb_line.size()) {
|
|
push_back(to_wide_str(mb_line));
|
|
++loaded;
|
|
}
|
|
}
|
|
|
|
ifile.close();
|
|
}
|
|
|
|
return loaded;
|
|
}
|
|
|
|
/**
|
|
* Save list from file overwriting previous data.
|
|
*/
|
|
bool
|
|
file_backed_list::save (const std::string &path)
|
|
{
|
|
bool status = false;
|
|
ofstream ofile(path.c_str());
|
|
if (ofile.is_open()) {
|
|
// Update path if successfully opened.
|
|
_path = path;
|
|
|
|
string line;
|
|
|
|
list<wstring>::iterator it(begin ());
|
|
for (; it != end(); ++it) {
|
|
ofile << to_utf8_str(*it) << "\n";
|
|
}
|
|
|
|
ofile.close();
|
|
status = true;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
} // end namespace Util.
|
|
|