Add getopt supporting unicode

Fixes #1191
This commit is contained in:
Jonas Kvinge
2023-04-30 02:06:27 +02:00
parent b01f3f4bb5
commit 1d3223e9c6
9 changed files with 1068 additions and 746 deletions

View File

@@ -22,10 +22,14 @@
#include "version.h"
#include <cstdlib>
#include <getopt.h>
#include <iostream>
#include <QtGlobal>
#ifdef Q_OS_WIN32
# include <windows.h>
#endif
#include <QObject>
#include <QIODevice>
#include <QDataStream>
@@ -39,6 +43,8 @@
#include "commandlineoptions.h"
#include "core/logging.h"
#include <getopt.h>
const char *CommandlineOptions::kHelpText =
"%1: strawberry [%2] [%3]\n"
"\n"
@@ -80,7 +86,11 @@ const char *CommandlineOptions::kVersionText = "Strawberry %1";
CommandlineOptions::CommandlineOptions(int argc, char **argv)
: argc_(argc),
#ifdef Q_OS_WIN32
argv_(CommandLineToArgvW(GetCommandLineW(), &argc)),
#else
argv_(argv),
#endif
url_list_action_(UrlListAction::None),
player_action_(PlayerAction::None),
set_volume_(-1),
@@ -92,6 +102,10 @@ CommandlineOptions::CommandlineOptions(int argc, char **argv)
toggle_pretty_osd_(false),
log_levels_(logging::kDefaultLogLevels) {
#ifdef Q_OS_WIN32
Q_UNUSED(argv);
#endif
#ifdef Q_OS_MACOS
// Remove -psn_xxx option that Mac passes when opened from Finder.
RemoveArg("-psn", 1);
@@ -99,12 +113,13 @@ CommandlineOptions::CommandlineOptions(int argc, char **argv)
// Remove the -session option that KDE passes
RemoveArg("-session", 2);
}
void CommandlineOptions::RemoveArg(const QString &starts_with, int count) {
for (int i = 0; i < argc_; ++i) {
QString opt(argv_[i]);
const QString opt = OptArgToString(argv_[i]);
if (opt.startsWith(starts_with)) {
for (int j = i; j < argc_ - count + 1; ++j) {
argv_[j] = argv_[j + count];
@@ -119,41 +134,79 @@ void CommandlineOptions::RemoveArg(const QString &starts_with, int count) {
bool CommandlineOptions::Parse() {
static const struct option kOptions[] = {
{"help", no_argument, nullptr, 'h'},
{"play", no_argument, nullptr, 'p'},
{"play-pause", no_argument, nullptr, 't'},
{"pause", no_argument, nullptr, 'u'},
{"stop", no_argument, nullptr, 's'},
{"stop-after-current", no_argument, nullptr, 'q'},
{"previous", no_argument, nullptr, 'r'},
{"next", no_argument, nullptr, 'f'},
{"volume", required_argument, nullptr, 'v'},
{"volume-up", no_argument, nullptr, LongOptions::VolumeUp},
{"volume-down", no_argument, nullptr, LongOptions::VolumeDown},
{"volume-increase-by", required_argument, nullptr, LongOptions::VolumeIncreaseBy},
{"volume-decrease-by", required_argument, nullptr, LongOptions::VolumeDecreaseBy},
{"seek-to", required_argument, nullptr, LongOptions::SeekTo},
{"seek-by", required_argument, nullptr, LongOptions::SeekBy},
{"restart-or-previous", no_argument, nullptr, LongOptions::RestartOrPrevious},
{"create", required_argument, nullptr, 'c'},
{"append", no_argument, nullptr, 'a'},
{"load", no_argument, nullptr, 'l'},
{"play-track", required_argument, nullptr, 'k'},
{"play-playlist", required_argument, nullptr, 'i'},
{"show-osd", no_argument, nullptr, 'o'},
{"toggle-pretty-osd", no_argument, nullptr, 'y'},
{"language", required_argument, nullptr, 'g'},
{"resize-window", required_argument, nullptr, 'w'},
{"quiet", no_argument, nullptr, LongOptions::Quiet},
{"verbose", no_argument, nullptr, LongOptions::Verbose},
{"log-levels", required_argument, nullptr, LongOptions::LogLevels},
{"version", no_argument, nullptr, LongOptions::Version},
{nullptr, 0, nullptr, 0}};
#ifdef Q_OS_WIN32
{L"help", no_argument, nullptr, 'h'},
{L"play", no_argument, nullptr, 'p'},
{L"play-pause", no_argument, nullptr, 't'},
{L"pause", no_argument, nullptr, 'u'},
{L"stop", no_argument, nullptr, 's'},
{L"stop-after-current", no_argument, nullptr, 'q'},
{L"previous", no_argument, nullptr, 'r'},
{L"next", no_argument, nullptr, 'f'},
{L"volume", required_argument, nullptr, 'v'},
{L"volume-up", no_argument, nullptr, LongOptions::VolumeUp},
{L"volume-down", no_argument, nullptr, LongOptions::VolumeDown},
{L"volume-increase-by", required_argument, nullptr, LongOptions::VolumeIncreaseBy},
{L"volume-decrease-by", required_argument, nullptr, LongOptions::VolumeDecreaseBy},
{L"seek-to", required_argument, nullptr, LongOptions::SeekTo},
{L"seek-by", required_argument, nullptr, LongOptions::SeekBy},
{L"restart-or-previous", no_argument, nullptr, LongOptions::RestartOrPrevious },
{L"create", required_argument, nullptr, 'c' },
{L"append", no_argument, nullptr, 'a' },
{L"load", no_argument, nullptr, 'l'},
{L"play-track", required_argument, nullptr, 'k'},
{L"play-playlist", required_argument, nullptr, 'i'},
{L"show-osd", no_argument, nullptr, 'o'},
{L"toggle-pretty-osd", no_argument, nullptr, 'y'},
{L"language", required_argument, nullptr, 'g'},
{L"resize-window", required_argument, nullptr, 'w'},
{L"quiet", no_argument, nullptr, LongOptions::Quiet},
{L"verbose", no_argument, nullptr, LongOptions::Verbose},
{L"log-levels", required_argument, nullptr, LongOptions::LogLevels},
{L"version", no_argument, nullptr, LongOptions::Version},
{nullptr, 0, nullptr, 0}
#else
{ "help", no_argument, nullptr, 'h' },
{ "play", no_argument, nullptr, 'p' },
{ "play-pause", no_argument, nullptr, 't' },
{ "pause", no_argument, nullptr, 'u' },
{ "stop", no_argument, nullptr, 's' },
{ "stop-after-current", no_argument, nullptr, 'q' },
{ "previous", no_argument, nullptr, 'r' },
{ "next", no_argument, nullptr, 'f' },
{ "volume", required_argument, nullptr, 'v' },
{ "volume-up", no_argument, nullptr, LongOptions::VolumeUp },
{ "volume-down", no_argument, nullptr, LongOptions::VolumeDown },
{ "volume-increase-by", required_argument, nullptr, LongOptions::VolumeIncreaseBy },
{ "volume-decrease-by", required_argument, nullptr, LongOptions::VolumeDecreaseBy },
{ "seek-to", required_argument, nullptr, LongOptions::SeekTo },
{ "seek-by", required_argument, nullptr, LongOptions::SeekBy },
{ "restart-or-previous", no_argument, nullptr, LongOptions::RestartOrPrevious },
{ "create", required_argument, nullptr, 'c' },
{ "append", no_argument, nullptr, 'a' },
{ "load", no_argument, nullptr, 'l' },
{ "play-track", required_argument, nullptr, 'k' },
{ "play-playlist", required_argument, nullptr, 'i' },
{ "show-osd", no_argument, nullptr, 'o' },
{ "toggle-pretty-osd", no_argument, nullptr, 'y' },
{ "language", required_argument, nullptr, 'g' },
{ "resize-window", required_argument, nullptr, 'w' },
{ "quiet", no_argument, nullptr, LongOptions::Quiet },
{ "verbose", no_argument, nullptr, LongOptions::Verbose },
{ "log-levels", required_argument, nullptr, LongOptions::LogLevels },
{ "version", no_argument, nullptr, LongOptions::Version },
{ nullptr, 0, nullptr, 0 }
#endif
};
// Parse the arguments
bool ok = false;
forever {
#ifdef Q_OS_WIN32
int c = getopt_long(argc_, argv_, L"hptusqrfv:c:alk:i:oyg:w:", kOptions, nullptr);
#else
int c = getopt_long(argc_, argv_, "hptusqrfv:c:alk:i:oyg:w:", kOptions, nullptr);
#endif
// End of the options
if (c == -1) break;
@@ -162,36 +215,36 @@ bool CommandlineOptions::Parse() {
case 'h': {
QString translated_help_text =
QString(kHelpText)
.arg(tr("Usage"), tr("options"), tr("URL(s)"),
tr("Player options"),
tr("Start the playlist currently playing"),
tr("Play if stopped, pause if playing"),
tr("Pause playback"), tr("Stop playback"),
tr("Stop playback after current track"))
.arg(tr("Skip backwards in playlist"),
tr("Skip forwards in playlist"),
tr("Set the volume to <value> percent"),
tr("Increase the volume by 4 percent"),
tr("Decrease the volume by 4 percent"),
tr("Increase the volume by <value> percent"),
tr("Decrease the volume by <value> percent"))
.arg(tr("Seek the currently playing track to an absolute position"),
tr("Seek the currently playing track by a relative amount"),
tr("Restart the track, or play the previous track if within 8 seconds of start."),
tr("Playlist options"),
tr("Create a new playlist with files"),
tr("Append files/URLs to the playlist"),
tr("Loads files/URLs, replacing current playlist"),
tr("Play the <n>th track in the playlist"),
tr("Play given playlist"))
.arg(tr("Other options"), tr("Display the on-screen-display"),
tr("Toggle visibility for the pretty on-screen-display"),
tr("Change the language"),
tr("Resize the window"),
tr("Equivalent to --log-levels *:1"),
tr("Equivalent to --log-levels *:3"),
tr("Comma separated list of class:level, level is 0-3"))
.arg(tr("Print out version information"));
.arg(QObject::tr("Usage"), QObject::tr("options"), QObject::tr("URL(s)"),
QObject::tr("Player options"),
QObject::tr("Start the playlist currently playing"),
QObject::tr("Play if stopped, pause if playing"),
QObject::tr("Pause playback"), QObject::tr("Stop playback"),
QObject::tr("Stop playback after current track"))
.arg(QObject::tr("Skip backwards in playlist"),
QObject::tr("Skip forwards in playlist"),
QObject::tr("Set the volume to <value> percent"),
QObject::tr("Increase the volume by 4 percent"),
QObject::tr("Decrease the volume by 4 percent"),
QObject::tr("Increase the volume by <value> percent"),
QObject::tr("Decrease the volume by <value> percent"))
.arg(QObject::tr("Seek the currently playing track to an absolute position"),
QObject::tr("Seek the currently playing track by a relative amount"),
QObject::tr("Restart the track, or play the previous track if within 8 seconds of start."),
QObject::tr("Playlist options"),
QObject::tr("Create a new playlist with files"),
QObject::tr("Append files/URLs to the playlist"),
QObject::tr("Loads files/URLs, replacing current playlist"),
QObject::tr("Play the <n>th track in the playlist"),
QObject::tr("Play given playlist"))
.arg(QObject::tr("Other options"), QObject::tr("Display the on-screen-display"),
QObject::tr("Toggle visibility for the pretty on-screen-display"),
QObject::tr("Change the language"),
QObject::tr("Resize the window"),
QObject::tr("Equivalent to --log-levels *:1"),
QObject::tr("Equivalent to --log-levels *:3"),
QObject::tr("Comma separated list of class:level, level is 0-3"))
.arg(QObject::tr("Print out version information"));
std::cout << translated_help_text.toLocal8Bit().constData();
return false;
@@ -220,11 +273,11 @@ bool CommandlineOptions::Parse() {
break;
case 'i':
player_action_ = PlayerAction::PlayPlaylist;
playlist_name_ = QString(optarg);
playlist_name_ = OptArgToString(optarg);
break;
case 'c':
url_list_action_ = UrlListAction::CreateNew;
playlist_name_ = QString(optarg);
playlist_name_ = OptArgToString(optarg);
break;
case 'a':
url_list_action_ = UrlListAction::Append;
@@ -239,7 +292,7 @@ bool CommandlineOptions::Parse() {
toggle_pretty_osd_ = true;
break;
case 'g':
language_ = QString(optarg);
language_ = OptArgToString(optarg);
break;
case LongOptions::VolumeUp:
volume_modifier_ = +4;
@@ -254,7 +307,7 @@ bool CommandlineOptions::Parse() {
log_levels_ = "3";
break;
case LongOptions::LogLevels:
log_levels_ = QString(optarg);
log_levels_ = OptArgToString(optarg);
break;
case LongOptions::Version: {
QString version_text = QString(kVersionText).arg(STRAWBERRY_VERSION_DISPLAY);
@@ -262,27 +315,27 @@ bool CommandlineOptions::Parse() {
std::exit(0);
}
case 'v':
set_volume_ = QString(optarg).toInt(&ok);
set_volume_ = OptArgToString(optarg).toInt(&ok);
if (!ok) set_volume_ = -1;
break;
case LongOptions::VolumeIncreaseBy:
volume_modifier_ = QString(optarg).toInt(&ok);
volume_modifier_ = OptArgToString(optarg).toInt(&ok);
if (!ok) volume_modifier_ = 0;
break;
case LongOptions::VolumeDecreaseBy:
volume_modifier_ = -QString(optarg).toInt(&ok);
volume_modifier_ = -OptArgToString(optarg).toInt(&ok);
if (!ok) volume_modifier_ = 0;
break;
case LongOptions::SeekTo:
seek_to_ = QString(optarg).toInt(&ok);
seek_to_ = OptArgToString(optarg).toInt(&ok);
if (!ok) seek_to_ = -1;
break;
case LongOptions::SeekBy:
seek_by_ = QString(optarg).toInt(&ok);
seek_by_ = OptArgToString(optarg).toInt(&ok);
if (!ok) seek_by_ = 0;
break;
@@ -291,12 +344,12 @@ bool CommandlineOptions::Parse() {
break;
case 'k':
play_track_at_ = QString(optarg).toInt(&ok);
play_track_at_ = OptArgToString(optarg).toInt(&ok);
if (!ok) play_track_at_ = -1;
break;
case 'w':
window_size_ = QString(optarg);
window_size_ = OptArgToString(optarg);
player_action_ = PlayerAction::ResizeWindow;
break;
@@ -308,10 +361,10 @@ bool CommandlineOptions::Parse() {
// Get any filenames or URLs following the arguments
for (int i = optind; i < argc_; ++i) {
QString value = QFile::decodeName(argv_[i]);
QFileInfo file_info(value);
if (file_info.exists()) {
urls_ << QUrl::fromLocalFile(file_info.canonicalFilePath());
const QString value = DecodeName(argv_[i]);
QFileInfo fileinfo(value);
if (fileinfo.exists()) {
urls_ << QUrl::fromLocalFile(fileinfo.canonicalFilePath());
}
else {
urls_ << QUrl::fromUserInput(value);
@@ -362,10 +415,30 @@ void CommandlineOptions::Load(const QByteArray &serialized) {
}
QString CommandlineOptions::tr(const char *source_text) {
return QObject::tr(source_text); // clazy:exclude=tr-non-literal
#ifdef Q_OS_WIN32
QString CommandlineOptions::OptArgToString(wchar_t *opt) {
return QString::fromWCharArray(opt);
}
QString CommandlineOptions::DecodeName(wchar_t *opt) {
return QString::fromWCharArray(opt);
}
#else
QString CommandlineOptions::OptArgToString(char *opt) {
return QString(opt);
}
QString CommandlineOptions::DecodeName(char *opt) {
return QFile::decodeName(opt);
}
#endif
QDataStream &operator<<(QDataStream &s, const CommandlineOptions &a) {
s << static_cast<quint32>(a.player_action_)