Add xmusic tracker/audio player, xfiles media actions, ringtone volume & vibration fix
This commit is contained in:
@@ -281,6 +281,10 @@ static const char *resolve_plugin_name_from_argv0(const char *argv0) {
|
||||
strcasecmp(base, "xbackground") == 0 || strcasecmp(base, "background") == 0) {
|
||||
return "wallpapers";
|
||||
}
|
||||
if (strcasecmp(base, "xmusic") == 0 || strcasecmp(base, "music") == 0 ||
|
||||
strcasecmp(base, "xplayer") == 0) {
|
||||
return "music";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -93,12 +93,28 @@ std::string getExtLower(const std::string& name) {
|
||||
}
|
||||
|
||||
bool isImageExt(const std::string& ext) {
|
||||
return ext == "jpg" || ext == "jpeg" || ext == "png";
|
||||
return ext == "jpg" || ext == "jpeg" || ext == "png" || ext == "bmp" ||
|
||||
ext == "gif" || ext == "webp" || ext == "ppm" || ext == "pgm" ||
|
||||
ext == "pbm" || ext == "tif" || ext == "tiff" || ext == "ico" ||
|
||||
ext == "svg" || ext == "avif" || ext == "heic";
|
||||
}
|
||||
|
||||
bool isAudioExt(const std::string& ext) {
|
||||
return ext == "mp3" || ext == "wav" || ext == "ogg" || ext == "oga" ||
|
||||
ext == "opus" || ext == "flac" || ext == "m4a" || ext == "aac" ||
|
||||
ext == "wma" || ext == "aiff" || ext == "wv" || ext == "ape" ||
|
||||
ext == "spx" || ext == "mod" || ext == "xm" || ext == "it" ||
|
||||
ext == "s3m" || ext == "mptm" || ext == "stm" || ext == "med" ||
|
||||
ext == "okt" || ext == "669" || ext == "ult" || ext == "far" ||
|
||||
ext == "mtm" || ext == "ptm" || ext == "amf" || ext == "dsm" ||
|
||||
ext == "imf" || ext == "gdm" || ext == "stx" || ext == "umx" ||
|
||||
ext == "mo3" || ext == "j2b" || ext == "psm" || ext == "dbm" ||
|
||||
ext == "digi" || ext == "mdl" || ext == "mt2" || ext == "ams" ||
|
||||
ext == "plm" || ext == "sfx";
|
||||
}
|
||||
|
||||
bool isMediaExt(const std::string& ext) {
|
||||
return ext == "mp3" || ext == "wav" || ext == "ogg" || ext == "flac" ||
|
||||
ext == "m4a" || ext == "mp4" || ext == "mkv" || ext == "webm" ||
|
||||
return isAudioExt(ext) || ext == "mp4" || ext == "mkv" || ext == "webm" ||
|
||||
ext == "avi";
|
||||
}
|
||||
|
||||
@@ -311,9 +327,17 @@ int runFilesApp(int argc, char** argv) {
|
||||
dpy, visual, DefaultDepth(dpy, screen), ZPixmap, 0,
|
||||
reinterpret_cast<char*>(imgCanvas.data()), kImgCanvasW, kImgCanvasH, 32,
|
||||
kImgCanvasW * 4);
|
||||
constexpr int kImgFullW = 240;
|
||||
constexpr int kImgFullH = 270;
|
||||
std::vector<uint32_t> imgFullCanvas(kImgFullW * kImgFullH, 0x111111);
|
||||
XImage* xImgFullPreview = XCreateImage(
|
||||
dpy, visual, DefaultDepth(dpy, screen), ZPixmap, 0,
|
||||
reinterpret_cast<char*>(imgFullCanvas.data()), kImgFullW, kImgFullH, 32,
|
||||
kImgFullW * 4);
|
||||
int imgOrigW = 0;
|
||||
int imgOrigH = 0;
|
||||
bool imgValid = false;
|
||||
bool imgFullscreen = false;
|
||||
|
||||
// Modal input state (New Folder / New File / Rename)
|
||||
InputAction inputAction = InputAction::NewFolder;
|
||||
@@ -487,19 +511,113 @@ int runFilesApp(int argc, char** argv) {
|
||||
if (imgValid) {
|
||||
scaleRgb24ToBgrxCanvas(rgb.data(), imgOrigW, imgOrigH, imgCanvas.data(),
|
||||
kImgCanvasW, kImgCanvasH, 0x111111);
|
||||
scaleRgb24ToBgrxCanvas(rgb.data(), imgOrigW, imgOrigH,
|
||||
imgFullCanvas.data(), kImgFullW, kImgFullH,
|
||||
0x111111);
|
||||
} else {
|
||||
std::fill(imgCanvas.begin(), imgCanvas.end(), 0x111111);
|
||||
std::fill(imgFullCanvas.begin(), imgFullCanvas.end(), 0x111111);
|
||||
}
|
||||
mode = ViewMode::ImageView;
|
||||
};
|
||||
|
||||
auto installAsWallpaper = [&](const std::string& srcPath,
|
||||
const std::string& fileName) {
|
||||
std::string targetPath = srcPath;
|
||||
std::string wallDir = homeDir + "/Pictures/Wallpapers";
|
||||
std::string picDir = homeDir + "/Pictures";
|
||||
if (srcPath.rfind("/usr/share/wallpapers/", 0) != 0 &&
|
||||
srcPath.rfind(wallDir + "/", 0) != 0 &&
|
||||
srcPath.rfind(picDir + "/", 0) != 0) {
|
||||
::mkdir(picDir.c_str(), 0755);
|
||||
::mkdir(wallDir.c_str(), 0755);
|
||||
std::string dst = wallDir + "/" + fileName;
|
||||
if (copyRecursive(srcPath, dst)) {
|
||||
targetPath = dst;
|
||||
}
|
||||
}
|
||||
|
||||
std::string cfgDir = homeDir + "/.config/sxmo";
|
||||
const char* xdgCfg = std::getenv("XDG_CONFIG_HOME");
|
||||
if (xdgCfg && *xdgCfg) cfgDir = std::string(xdgCfg) + "/sxmo";
|
||||
::mkdir((homeDir + "/.config").c_str(), 0755);
|
||||
::mkdir(cfgDir.c_str(), 0755);
|
||||
|
||||
{
|
||||
std::ofstream ofs(cfgDir + "/wallpaper", std::ios::trunc);
|
||||
if (ofs) ofs << targetPath << "\n";
|
||||
}
|
||||
|
||||
std::string fehFlag = "--bg-fill";
|
||||
{
|
||||
std::ifstream ifs(cfgDir + "/wallpaper_mode");
|
||||
std::string m;
|
||||
if (ifs && std::getline(ifs, m)) {
|
||||
m = toLowerStr(trimStr(m));
|
||||
if (m == "max" || m == "--bg-max") fehFlag = "--bg-max";
|
||||
else if (m == "scale" || m == "--bg-scale") fehFlag = "--bg-scale";
|
||||
else if (m == "center" || m == "--bg-center") fehFlag = "--bg-center";
|
||||
else if (m == "tile" || m == "--bg-tile") fehFlag = "--bg-tile";
|
||||
}
|
||||
}
|
||||
|
||||
runAsyncCmd("feh --no-fehbg " + fehFlag + " " + shellQuote(targetPath) +
|
||||
" >/dev/null 2>&1; "
|
||||
"PATH=\"$HOME/.config/sxmo/hooks:/usr/share/sxmo/default_hooks:$PATH\" "
|
||||
"sxmo_hook_statusbar.sh time >/dev/null 2>&1");
|
||||
statusBanner = "Wallpaper set: " + fileName;
|
||||
statusBannerUntilMs = nowMs() + 2200;
|
||||
};
|
||||
|
||||
auto installAsRingtone = [&](const std::string& srcPath,
|
||||
const std::string& fileName) {
|
||||
std::string targetPath = srcPath;
|
||||
std::string ringDir = homeDir + "/Ringtones";
|
||||
std::string musDir = homeDir + "/Music";
|
||||
if (srcPath.rfind("/usr/share/sxmo/", 0) != 0 &&
|
||||
srcPath.rfind(ringDir + "/", 0) != 0 &&
|
||||
srcPath.rfind(musDir + "/", 0) != 0) {
|
||||
::mkdir(ringDir.c_str(), 0755);
|
||||
std::string dst = ringDir + "/" + fileName;
|
||||
if (copyRecursive(srcPath, dst)) {
|
||||
targetPath = dst;
|
||||
}
|
||||
}
|
||||
|
||||
std::string cfgDir = homeDir + "/.config/sxmo";
|
||||
const char* xdgCfg = std::getenv("XDG_CONFIG_HOME");
|
||||
if (xdgCfg && *xdgCfg) cfgDir = std::string(xdgCfg) + "/sxmo";
|
||||
::mkdir((homeDir + "/.config").c_str(), 0755);
|
||||
::mkdir(cfgDir.c_str(), 0755);
|
||||
|
||||
{
|
||||
std::ofstream ofs(cfgDir + "/ringtone", std::ios::trunc);
|
||||
if (ofs) ofs << targetPath << "\n";
|
||||
}
|
||||
statusBanner = "Ringtone set: " + fileName;
|
||||
statusBannerUntilMs = nowMs() + 2200;
|
||||
};
|
||||
|
||||
auto buildActionsList = [&]() {
|
||||
actionsList.clear();
|
||||
bool hasSel = !filteredItems.empty();
|
||||
bool isParent = hasSel && filteredItems[selected].isParent;
|
||||
|
||||
if (hasSel) {
|
||||
actionsList.push_back({1, kIconEye, "Open / View"});
|
||||
if (!filteredItems[selected].isDir) {
|
||||
std::string ext = getExtLower(filteredItems[selected].name);
|
||||
if (isImageExt(ext)) {
|
||||
actionsList.push_back({1, kIconEye, "Preview Image"});
|
||||
actionsList.push_back({14, kIconImage, "Set as Wallpaper"});
|
||||
} else if (isAudioExt(ext)) {
|
||||
actionsList.push_back({1, kIconMusic, "Play in Music Player"});
|
||||
actionsList.push_back({15, kIconBell, "Set as Ringtone"});
|
||||
} else {
|
||||
actionsList.push_back({1, kIconEye, "Open / View"});
|
||||
}
|
||||
} else {
|
||||
actionsList.push_back({1, kIconFolderOpen, "Open Folder"});
|
||||
}
|
||||
}
|
||||
if (!clipPath.empty()) {
|
||||
actionsList.push_back(
|
||||
@@ -540,6 +658,15 @@ int runFilesApp(int argc, char** argv) {
|
||||
::unlink(activePidPath.c_str());
|
||||
};
|
||||
|
||||
auto openInMusicPlayer = [&](const std::string& path) {
|
||||
hideWindow();
|
||||
if (isPluginDaemonAlive("music")) {
|
||||
sendPluginCommand("music", "OPEN " + path);
|
||||
} else {
|
||||
runAsyncArgv("xmusic", path.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
auto openSelectedItem = [&]() {
|
||||
if (filteredItems.empty()) return;
|
||||
const FileItem item = filteredItems[selected];
|
||||
@@ -557,7 +684,11 @@ int runFilesApp(int argc, char** argv) {
|
||||
}
|
||||
std::string ext = getExtLower(item.name);
|
||||
if (isImageExt(ext)) {
|
||||
imgFullscreen = false;
|
||||
openImageViewer(item);
|
||||
} else if (isAudioExt(ext)) {
|
||||
openInMusicPlayer(item.fullPath);
|
||||
return;
|
||||
} else if (isMediaExt(ext)) {
|
||||
hideWindow();
|
||||
runAsyncArgv("sxmo_open.sh", item.fullPath.c_str());
|
||||
@@ -786,19 +917,25 @@ int runFilesApp(int argc, char** argv) {
|
||||
drawSoftkeyBar("Edit", "Down", "Back");
|
||||
|
||||
} else if (mode == ViewMode::ImageView) {
|
||||
XSetForeground(dpy, gc, pt.spec.bgHeader);
|
||||
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 36);
|
||||
drawPango(layoutIcon, &colGreen, 6, 1, 18, kIconImage);
|
||||
drawPango(layoutMain, &colWhite, 28, 0, 20, viewingFileName, 145);
|
||||
std::string dim = imgValid
|
||||
? (std::to_string(imgOrigW) + "x" +
|
||||
std::to_string(imgOrigH))
|
||||
: "Decode failed";
|
||||
drawPango(layoutSmall, &colMuted, 28, 19, 15, dim, 200);
|
||||
if (imgFullscreen) {
|
||||
XPutImage(dpy, pix, gc, xImgFullPreview, 0, 0, 0, 0, kImgFullW,
|
||||
kImgFullH);
|
||||
} else {
|
||||
XSetForeground(dpy, gc, pt.spec.bgHeader);
|
||||
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 36);
|
||||
drawPango(layoutIcon, &colGreen, 6, 1, 18, kIconImage);
|
||||
drawPango(layoutMain, &colWhite, 28, 0, 20, viewingFileName, 145);
|
||||
std::string dim = imgValid
|
||||
? (std::to_string(imgOrigW) + "x" +
|
||||
std::to_string(imgOrigH) + " ([*] Full)")
|
||||
: "Decode failed";
|
||||
drawPango(layoutSmall, &colMuted, 28, 19, 15, dim, 200);
|
||||
|
||||
XPutImage(dpy, pix, gc, xImgPreview, 0, 0, 0, 36, kImgCanvasW,
|
||||
kImgCanvasH);
|
||||
drawSoftkeyBar("Full", "Next", "Back");
|
||||
XPutImage(dpy, pix, gc, xImgPreview, 0, 0, 0, 36, kImgCanvasW,
|
||||
kImgCanvasH);
|
||||
}
|
||||
drawSoftkeyBar("Set Wall", "Next",
|
||||
imgFullscreen ? "Header" : "Back");
|
||||
|
||||
} else if (mode == ViewMode::PropsView && !filteredItems.empty()) {
|
||||
const auto& it = filteredItems[selected];
|
||||
@@ -1027,6 +1164,14 @@ int runFilesApp(int argc, char** argv) {
|
||||
} else if (actId == 13 && !filteredItems.empty()) {
|
||||
confirmSelected = 0;
|
||||
mode = ViewMode::ConfirmDelete;
|
||||
} else if (actId == 14 && !filteredItems.empty()) {
|
||||
installAsWallpaper(filteredItems[selected].fullPath,
|
||||
filteredItems[selected].name);
|
||||
mode = ViewMode::Browser;
|
||||
} else if (actId == 15 && !filteredItems.empty()) {
|
||||
installAsRingtone(filteredItems[selected].fullPath,
|
||||
filteredItems[selected].name);
|
||||
mode = ViewMode::Browser;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1077,9 +1222,13 @@ int runFilesApp(int argc, char** argv) {
|
||||
}
|
||||
} else if (mode == ViewMode::ImageView) {
|
||||
if (isLeft) {
|
||||
runAsyncArgv("feh", viewingFilePath.c_str());
|
||||
installAsWallpaper(viewingFilePath, viewingFileName);
|
||||
} else {
|
||||
mode = ViewMode::Browser;
|
||||
if (imgFullscreen) {
|
||||
imgFullscreen = false;
|
||||
} else {
|
||||
mode = ViewMode::Browser;
|
||||
}
|
||||
}
|
||||
} else if (mode == ViewMode::PropsView) {
|
||||
mode = ViewMode::Browser;
|
||||
@@ -1300,8 +1449,12 @@ int runFilesApp(int argc, char** argv) {
|
||||
drawUI();
|
||||
}
|
||||
} else if (mode == ViewMode::ImageView) {
|
||||
if (sym == XK_Left || sym == XK_KP_Left || sym == XK_Up ||
|
||||
sym == XK_KP_Up) {
|
||||
char d = mapDialKey(sym, kc, ev.xkey.state);
|
||||
if (d == '*') {
|
||||
imgFullscreen = !imgFullscreen;
|
||||
drawUI();
|
||||
} else if (sym == XK_Left || sym == XK_KP_Left || sym == XK_Up ||
|
||||
sym == XK_KP_Up) {
|
||||
stepImageInFolder(-1);
|
||||
drawUI();
|
||||
} else if (sym == XK_Right || sym == XK_KP_Right || sym == XK_Down ||
|
||||
@@ -1395,6 +1548,10 @@ int runFilesApp(int argc, char** argv) {
|
||||
xImgPreview->data = nullptr;
|
||||
XDestroyImage(xImgPreview);
|
||||
}
|
||||
if (xImgFullPreview) {
|
||||
xImgFullPreview->data = nullptr;
|
||||
XDestroyImage(xImgFullPreview);
|
||||
}
|
||||
if (fifoFd >= 0) ::close(fifoFd);
|
||||
::unlink(daemonPidPath.c_str());
|
||||
return 0;
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
#include <jpeglib.h>
|
||||
#include <png.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace bananarchy {
|
||||
|
||||
@@ -159,6 +161,73 @@ inline bool loadPngRgb24(const std::string& path,
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool loadFfmpegImageRgb24(const std::string& path,
|
||||
std::vector<uint8_t>* outRgb,
|
||||
int* outW,
|
||||
int* outH) {
|
||||
int pipefd[2];
|
||||
if (::pipe(pipefd) != 0) {
|
||||
return false;
|
||||
}
|
||||
pid_t pid = ::fork();
|
||||
if (pid < 0) {
|
||||
::close(pipefd[0]);
|
||||
::close(pipefd[1]);
|
||||
return false;
|
||||
}
|
||||
if (pid == 0) {
|
||||
::close(pipefd[0]);
|
||||
::dup2(pipefd[1], STDOUT_FILENO);
|
||||
if (pipefd[1] != STDOUT_FILENO) ::close(pipefd[1]);
|
||||
int devnull = ::open("/dev/null", O_RDWR);
|
||||
if (devnull >= 0) {
|
||||
::dup2(devnull, STDIN_FILENO);
|
||||
::dup2(devnull, STDERR_FILENO);
|
||||
if (devnull > 2) ::close(devnull);
|
||||
}
|
||||
::execlp("ffmpeg", "ffmpeg", "-v", "error", "-i", path.c_str(),
|
||||
"-frames:v", "1", "-f", "image2pipe", "-vcodec", "png", "-",
|
||||
static_cast<char*>(nullptr));
|
||||
::_exit(127);
|
||||
}
|
||||
::close(pipefd[1]);
|
||||
std::vector<uint8_t> pngBuf;
|
||||
uint8_t chunk[4096];
|
||||
ssize_t n = 0;
|
||||
while ((n = ::read(pipefd[0], chunk, sizeof(chunk))) > 0) {
|
||||
if (pngBuf.size() + static_cast<size_t>(n) > 16 * 1024 * 1024) break;
|
||||
pngBuf.insert(pngBuf.end(), chunk, chunk + n);
|
||||
}
|
||||
::close(pipefd[0]);
|
||||
int status = 0;
|
||||
::waitpid(pid, &status, 0);
|
||||
if (pngBuf.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
png_image image {};
|
||||
image.version = PNG_IMAGE_VERSION;
|
||||
if (!png_image_begin_read_from_memory(&image, pngBuf.data(), pngBuf.size())) {
|
||||
return false;
|
||||
}
|
||||
image.format = PNG_FORMAT_RGB;
|
||||
int w = static_cast<int>(image.width);
|
||||
int h = static_cast<int>(image.height);
|
||||
if (w <= 0 || h <= 0 || w > 8192 || h > 8192) {
|
||||
png_image_free(&image);
|
||||
return false;
|
||||
}
|
||||
outRgb->resize(PNG_IMAGE_SIZE(image));
|
||||
if (!png_image_finish_read(&image, nullptr, outRgb->data(), 0, nullptr)) {
|
||||
png_image_free(&image);
|
||||
return false;
|
||||
}
|
||||
png_image_free(&image);
|
||||
*outW = w;
|
||||
*outH = h;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool loadImageRgb24(const std::string& path,
|
||||
std::vector<uint8_t>* outRgb,
|
||||
int* outW,
|
||||
@@ -178,6 +247,9 @@ inline bool loadImageRgb24(const std::string& path,
|
||||
if (loadPngRgb24(path, outRgb, outW, outH)) {
|
||||
return true;
|
||||
}
|
||||
if (loadFfmpegImageRgb24(path, outRgb, outW, outH)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1156,6 +1156,7 @@ inline void closeOtherPlugins(const char* selfPluginName) {
|
||||
{"xnetworks.pid", "networks"},
|
||||
{"xringtones.pid", "ringtones"},
|
||||
{"xwallpapers.pid", "wallpapers"},
|
||||
{"xmusic.pid", "music"},
|
||||
};
|
||||
for (const auto& p : kAllPlugins) {
|
||||
if (selfPluginName && std::strcmp(p.name, selfPluginName) == 0) {
|
||||
@@ -1190,6 +1191,7 @@ inline bool isAnotherPluginActive(const char* selfPluginName) {
|
||||
{"xnetworks.pid", "networks"},
|
||||
{"xringtones.pid", "ringtones"},
|
||||
{"xwallpapers.pid", "wallpapers"},
|
||||
{"xmusic.pid", "music"},
|
||||
};
|
||||
for (const auto& p : kAllPlugins) {
|
||||
if (selfPluginName && std::strcmp(p.name, selfPluginName) == 0) {
|
||||
@@ -1233,6 +1235,7 @@ inline void broadcastThemeReload(const char* selfPluginName = nullptr) {
|
||||
"networks",
|
||||
"ringtones",
|
||||
"wallpapers",
|
||||
"music",
|
||||
};
|
||||
for (const char* name : kPlugins) {
|
||||
if (selfPluginName && std::strcmp(name, selfPluginName) == 0) {
|
||||
|
||||
@@ -43,6 +43,7 @@ struct RingtoneEntry {
|
||||
|
||||
struct RingSettings {
|
||||
int ringerMode = 0; // 0 = Ring + Vibrate, 1 = Ring Only, 2 = Vibrate Only, 3 = Silent
|
||||
int ringVol = 100;
|
||||
int ringTimeSec = 20;
|
||||
int ringLoops = 10;
|
||||
int vibrateStrength = 1;
|
||||
@@ -198,6 +199,8 @@ RingSettings loadRingSettings() {
|
||||
s.ringLoops = val;
|
||||
} else if (k == "SXMO_VIBRATE_STRENGTH" && val >= 1 && val <= 3) {
|
||||
s.vibrateStrength = val;
|
||||
} else if (k == "SXMO_RINGVOL" && val >= 0 && val <= 100) {
|
||||
s.ringVol = (val / 5) * 5;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
@@ -228,6 +231,7 @@ void saveRingSettings(const RingSettings& s) {
|
||||
ofs << "SXMO_RINGTIME=" << s.ringTimeSec << "\n";
|
||||
ofs << "SXMO_RINGNUMBER=" << s.ringLoops << "\n";
|
||||
ofs << "SXMO_VIBRATE_STRENGTH=" << s.vibrateStrength << "\n";
|
||||
ofs << "SXMO_RINGVOL=" << std::clamp(s.ringVol, 0, 100) << "\n";
|
||||
}
|
||||
runAsyncCmd("sxmo_hook_statusbar.sh audio >/dev/null 2>&1");
|
||||
}
|
||||
@@ -372,6 +376,16 @@ int runRingtonesApp(int argc, char** argv) {
|
||||
return;
|
||||
}
|
||||
stopPreview();
|
||||
settings = loadRingSettings();
|
||||
int rvol = std::clamp(settings.ringVol, 0, 100);
|
||||
int rx3 = (rvol <= 0) ? 0 : (60 + rvol * 34 / 100);
|
||||
int mpvVol = rvol * 130 / 100;
|
||||
std::string volArg = "--volume=" + std::to_string(mpvVol);
|
||||
runAsyncCmd("amixer -c 0 cset \"name='SPK DAC Switch'\" 1 >/dev/null 2>&1; "
|
||||
"amixer -c 0 cset \"name='RX3 Digital Volume'\" " +
|
||||
std::to_string(rx3) + " >/dev/null 2>&1; "
|
||||
"wpctl set-mute @DEFAULT_AUDIO_SINK@ 0 >/dev/null 2>&1; "
|
||||
"wpctl set-volume @DEFAULT_AUDIO_SINK@ 100% >/dev/null 2>&1");
|
||||
pid_t pid = ::fork();
|
||||
if (pid == 0) {
|
||||
int devNull = ::open("/dev/null", O_RDWR);
|
||||
@@ -382,7 +396,8 @@ int runRingtonesApp(int argc, char** argv) {
|
||||
if (devNull > 2) ::close(devNull);
|
||||
}
|
||||
::execlp("mpv", "mpv", "--no-resume-playback", "--quiet",
|
||||
"--no-video", "--loop=2", path.c_str(), nullptr);
|
||||
"--no-video", "--volume-max=150", volArg.c_str(),
|
||||
"--loop=2", path.c_str(), nullptr);
|
||||
::_exit(127);
|
||||
} else if (pid > 0) {
|
||||
previewPid = pid;
|
||||
@@ -532,9 +547,11 @@ int runRingtonesApp(int argc, char** argv) {
|
||||
"Vibrate Only",
|
||||
"Silent",
|
||||
};
|
||||
std::string rows[5][2] = {
|
||||
std::string rows[6][2] = {
|
||||
{"Ringer Mode",
|
||||
"< " + std::string(modeLabels[settings.ringerMode % 4]) + " >"},
|
||||
{"Ring Volume",
|
||||
"< " + std::to_string(settings.ringVol) + "% >"},
|
||||
{"Ring Duration",
|
||||
"< " + std::to_string(settings.ringTimeSec) + " sec >"},
|
||||
{"Ring Loop Count",
|
||||
@@ -544,18 +561,18 @@ int runRingtonesApp(int argc, char** argv) {
|
||||
{"Test Vibration", "Press OK to vibrate"},
|
||||
};
|
||||
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
int ry = 32 + i * 42;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
int ry = 30 + i * 35;
|
||||
bool isSel = (settingsRow == i);
|
||||
XSetForeground(dpy, gc, isSel ? pt.spec.selBg : pt.spec.bgAltEven);
|
||||
XFillRectangle(dpy, pix, gc, 4, ry, kWinWidth - 8, 38);
|
||||
XFillRectangle(dpy, pix, gc, 4, ry, kWinWidth - 8, 33);
|
||||
if (isSel) {
|
||||
XSetForeground(dpy, gc, pt.spec.accent);
|
||||
XDrawRectangle(dpy, pix, gc, 4, ry, kWinWidth - 9, 37);
|
||||
XDrawRectangle(dpy, pix, gc, 4, ry, kWinWidth - 9, 32);
|
||||
}
|
||||
drawPango(layoutSmall, isSel ? &colWhite : &colMuted, 10, ry + 2, 17,
|
||||
drawPango(layoutSmall, isSel ? &colWhite : &colMuted, 10, ry + 1, 15,
|
||||
rows[i][0], kWinWidth - 20);
|
||||
drawPango(layoutMain, isSel ? &colYellow : &colBlue, 10, ry + 18, 19,
|
||||
drawPango(layoutMain, isSel ? &colYellow : &colBlue, 10, ry + 15, 17,
|
||||
rows[i][1], kWinWidth - 20);
|
||||
}
|
||||
drawSoftkeyBar("Change", "Select", "Close");
|
||||
@@ -629,6 +646,10 @@ int runRingtonesApp(int argc, char** argv) {
|
||||
saveRingSettings(settings);
|
||||
setBanner("Ringer mode updated");
|
||||
} else if (settingsRow == 1) {
|
||||
settings.ringVol = std::clamp(settings.ringVol + delta * 5, 0, 100);
|
||||
saveRingSettings(settings);
|
||||
setBanner("Ring volume: " + std::to_string(settings.ringVol) + "%");
|
||||
} else if (settingsRow == 2) {
|
||||
int curIdx = 2;
|
||||
for (int i = 0; i < kNumRingTimeSteps; ++i) {
|
||||
if (kRingTimeSteps[i] == settings.ringTimeSec) {
|
||||
@@ -639,7 +660,7 @@ int runRingtonesApp(int argc, char** argv) {
|
||||
curIdx = (curIdx + kNumRingTimeSteps + delta) % kNumRingTimeSteps;
|
||||
settings.ringTimeSec = kRingTimeSteps[curIdx];
|
||||
saveRingSettings(settings);
|
||||
} else if (settingsRow == 2) {
|
||||
} else if (settingsRow == 3) {
|
||||
int curIdx = 2;
|
||||
for (int i = 0; i < kNumRingLoopSteps; ++i) {
|
||||
if (kRingLoopSteps[i] == settings.ringLoops) {
|
||||
@@ -650,14 +671,14 @@ int runRingtonesApp(int argc, char** argv) {
|
||||
curIdx = (curIdx + kNumRingLoopSteps + delta) % kNumRingLoopSteps;
|
||||
settings.ringLoops = kRingLoopSteps[curIdx];
|
||||
saveRingSettings(settings);
|
||||
} else if (settingsRow == 3) {
|
||||
} else if (settingsRow == 4) {
|
||||
settings.vibrateStrength =
|
||||
((settings.vibrateStrength - 1 + 3 + delta) % 3) + 1;
|
||||
saveRingSettings(settings);
|
||||
} else if (settingsRow == 4) {
|
||||
} else if (settingsRow == 5) {
|
||||
int mag = (settings.vibrateStrength == 1)
|
||||
? 20000
|
||||
: (settings.vibrateStrength == 3 ? 65535 : 40000);
|
||||
? 8000
|
||||
: (settings.vibrateStrength == 3 ? 65535 : 32000);
|
||||
runAsyncCmd(
|
||||
"for d in /dev/input/by-path/platform-vibrator-event "
|
||||
"/dev/input/by-path/*vibrator*-event; do "
|
||||
@@ -744,7 +765,7 @@ int runRingtonesApp(int argc, char** argv) {
|
||||
}
|
||||
|
||||
if (sym == XK_Left || sym == XK_KP_Left) {
|
||||
if (activeTab == TabId::Settings && settingsRow < 4) {
|
||||
if (activeTab == TabId::Settings && settingsRow < 5) {
|
||||
adjustSettingRow(-1);
|
||||
} else {
|
||||
activeTab = (activeTab == TabId::Ringtones) ? TabId::Settings
|
||||
@@ -752,7 +773,7 @@ int runRingtonesApp(int argc, char** argv) {
|
||||
}
|
||||
render();
|
||||
} else if (sym == XK_Right || sym == XK_KP_Right) {
|
||||
if (activeTab == TabId::Settings && settingsRow < 4) {
|
||||
if (activeTab == TabId::Settings && settingsRow < 5) {
|
||||
adjustSettingRow(1);
|
||||
} else {
|
||||
activeTab = (activeTab == TabId::Ringtones) ? TabId::Settings
|
||||
@@ -764,14 +785,14 @@ int runRingtonesApp(int argc, char** argv) {
|
||||
selIdx = (selIdx + static_cast<int>(tones.size()) - 1) %
|
||||
static_cast<int>(tones.size());
|
||||
} else if (activeTab == TabId::Settings) {
|
||||
settingsRow = (settingsRow + 4) % 5;
|
||||
settingsRow = (settingsRow + 5) % 6;
|
||||
}
|
||||
render();
|
||||
} else if (sym == XK_Down || sym == XK_KP_Down) {
|
||||
if (activeTab == TabId::Ringtones && !tones.empty()) {
|
||||
selIdx = (selIdx + 1) % static_cast<int>(tones.size());
|
||||
} else if (activeTab == TabId::Settings) {
|
||||
settingsRow = (settingsRow + 1) % 5;
|
||||
settingsRow = (settingsRow + 1) % 6;
|
||||
}
|
||||
render();
|
||||
} else if (sym == XK_Return || sym == XK_KP_Enter) {
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <glob.h>
|
||||
#include <linux/input.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int read_configured_level(void) {
|
||||
char path[512];
|
||||
const char* cfg = getenv("XDG_CONFIG_HOME");
|
||||
const char* home = getenv("HOME");
|
||||
if (cfg && *cfg) {
|
||||
snprintf(path, sizeof(path), "%s/sxmo/ringsettings", cfg);
|
||||
} else if (home && *home) {
|
||||
snprintf(path, sizeof(path), "%s/.config/sxmo/ringsettings", home);
|
||||
} else {
|
||||
snprintf(path, sizeof(path), "/home/user/.config/sxmo/ringsettings");
|
||||
}
|
||||
|
||||
FILE* fp = fopen(path, "r");
|
||||
if (!fp) return 0;
|
||||
|
||||
int level = 0;
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
char* p = line;
|
||||
while (*p == ' ' || *p == '\t') ++p;
|
||||
if (strncmp(p, "SXMO_VIBRATE_STRENGTH=", 22) == 0) {
|
||||
int v = atoi(p + 22);
|
||||
if (v >= 1 && v <= 3) level = v;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
return level;
|
||||
}
|
||||
|
||||
static int resolve_magnitude(int raw_arg, int has_arg) {
|
||||
int val = raw_arg;
|
||||
if (!has_arg || (val == 1 && !getenv("SXMO_VIBRATE_STRENGTH"))) {
|
||||
int cfg = read_configured_level();
|
||||
if (cfg >= 1 && cfg <= 3) val = cfg;
|
||||
}
|
||||
if (val <= 1) return 8000;
|
||||
if (val == 2) return 32000;
|
||||
if (val == 3) return 65535;
|
||||
if (val < 256) return val * 257;
|
||||
if (val > 65535) return 65535;
|
||||
return val;
|
||||
}
|
||||
|
||||
static int open_vibrator_device(void) {
|
||||
const char* env_dev = getenv("SXMO_VIBRATE_DEV");
|
||||
if (env_dev && *env_dev) {
|
||||
int fd = open(env_dev, O_RDWR | O_CLOEXEC);
|
||||
if (fd >= 0) return fd;
|
||||
}
|
||||
|
||||
int fd = open("/dev/input/by-path/platform-vibrator-event", O_RDWR | O_CLOEXEC);
|
||||
if (fd >= 0) return fd;
|
||||
|
||||
glob_t g;
|
||||
memset(&g, 0, sizeof(g));
|
||||
if (glob("/dev/input/by-path/*vibrator*-event", 0, NULL, &g) == 0) {
|
||||
for (size_t i = 0; i < g.gl_pathc; ++i) {
|
||||
fd = open(g.gl_pathv[i], O_RDWR | O_CLOEXEC);
|
||||
if (fd >= 0) {
|
||||
globfree(&g);
|
||||
return fd;
|
||||
}
|
||||
}
|
||||
globfree(&g);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2 || argc > 3) {
|
||||
fprintf(stderr, "Usage: sxmo_vibrate duration_ms [strength_number]\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* endp = NULL;
|
||||
errno = 0;
|
||||
long duration_ms = strtol(argv[1], &endp, 10);
|
||||
if (errno != 0 || !endp || *endp != '\0' || duration_ms <= 0) {
|
||||
fprintf(stderr, "sxmo_vibrate: invalid duration\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int raw_strength = 2;
|
||||
int has_strength = 0;
|
||||
if (argc == 3) {
|
||||
endp = NULL;
|
||||
errno = 0;
|
||||
long s = strtol(argv[2], &endp, 10);
|
||||
if (errno == 0 && endp && *endp == '\0' && s >= 1 && s <= 65535) {
|
||||
raw_strength = (int)s;
|
||||
has_strength = 1;
|
||||
}
|
||||
}
|
||||
|
||||
int magnitude = resolve_magnitude(raw_strength, has_strength);
|
||||
|
||||
int fd = open_vibrator_device();
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "sxmo_vibrate: cannot open vibrator device: %s\n",
|
||||
strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct ff_effect effect;
|
||||
memset(&effect, 0, sizeof(effect));
|
||||
effect.type = FF_RUMBLE;
|
||||
effect.id = -1;
|
||||
effect.u.rumble.strong_magnitude = (unsigned short)magnitude;
|
||||
effect.u.rumble.weak_magnitude = (unsigned short)magnitude;
|
||||
|
||||
if (ioctl(fd, EVIOCSFF, &effect) < 0) {
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct input_event play;
|
||||
memset(&play, 0, sizeof(play));
|
||||
play.type = EV_FF;
|
||||
play.code = (unsigned short)effect.id;
|
||||
play.value = 3;
|
||||
|
||||
if (write(fd, &play, sizeof(play)) < 0) {
|
||||
ioctl(fd, EVIOCRMFF, effect.id);
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct timespec req;
|
||||
req.tv_sec = duration_ms / 1000;
|
||||
req.tv_nsec = (duration_ms % 1000) * 1000000L;
|
||||
while (nanosleep(&req, &req) == -1 && errno == EINTR) {
|
||||
}
|
||||
|
||||
play.value = 0;
|
||||
(void)write(fd, &play, sizeof(play));
|
||||
(void)ioctl(fd, EVIOCRMFF, effect.id);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
@@ -2275,6 +2275,9 @@ open_builtin_menu(const char *menu_name, int keep_idx)
|
||||
snprintf(label, sizeof(label), "%s Camera", ICON_CAM);
|
||||
add_menu_item(label, "@plugin:camera", 0);
|
||||
|
||||
snprintf(label, sizeof(label), "%s Music", ICON_MUS);
|
||||
add_menu_item(label, "@plugin:music", 0);
|
||||
|
||||
snprintf(label, sizeof(label), "%s Themes", ICON_THM);
|
||||
add_menu_item(label, "@plugin:themes", 0);
|
||||
|
||||
@@ -2337,6 +2340,8 @@ open_builtin_menu(const char *menu_name, int keep_idx)
|
||||
add_menu_item(label, "@plugin:wallpapers", 0);
|
||||
snprintf(label, sizeof(label), "%s Ringtones", ICON_BEL);
|
||||
add_menu_item(label, "@plugin:ringtones", 0);
|
||||
snprintf(label, sizeof(label), "%s Music", ICON_MUS);
|
||||
add_menu_item(label, "@plugin:music", 0);
|
||||
snprintf(label, sizeof(label), "%s System Menu", ICON_MNU);
|
||||
add_menu_item(label, "@menu:sys", 1);
|
||||
snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS);
|
||||
@@ -2401,6 +2406,9 @@ open_builtin_menu(const char *menu_name, int keep_idx)
|
||||
rdir = "/run/user/10000";
|
||||
char pbuf[256];
|
||||
|
||||
snprintf(label, sizeof(label), "%s Music Player", ICON_MUS);
|
||||
add_menu_item(label, "@plugin:music", 0);
|
||||
|
||||
snprintf(label, sizeof(label), "%s Volume & Controls (XDrawer)", ICON_MUS);
|
||||
add_menu_item(label, "@plugin:xdrawer", 0);
|
||||
|
||||
|
||||
+107
-28
@@ -26,6 +26,7 @@
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <fcntl.h>
|
||||
#include <fstream>
|
||||
#include <poll.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -42,7 +43,7 @@ constexpr int kWinX = 0;
|
||||
constexpr int kWinY = kTopBarHeight;
|
||||
constexpr int kWinWidth = kScreenWidth;
|
||||
constexpr int kWinHeight = kScreenHeight - kTopBarHeight; // 296px flush below 24px dwm bar
|
||||
constexpr int kRowCount = 8;
|
||||
constexpr int kRowCount = 9;
|
||||
|
||||
// Nerd Font PUA icons matching sxmo_hook_icons.sh & dwm ("Fira Code, DejaVuSansM Nerd Font")
|
||||
constexpr const char* kIconSpeaker = "\xef\x80\xa8"; // U+F028 speaker
|
||||
@@ -239,12 +240,13 @@ class AlsaControl {
|
||||
enum class ControlId {
|
||||
SpeakerVol = 0,
|
||||
EarpieceVol = 1,
|
||||
Flashlight = 2,
|
||||
SoundMute = 3,
|
||||
MicMute = 4,
|
||||
RingerMode = 5,
|
||||
ScreenBright = 6,
|
||||
KbdBright = 7,
|
||||
RingVol = 2,
|
||||
RingerMode = 3,
|
||||
Flashlight = 4,
|
||||
SoundMute = 5,
|
||||
MicMute = 6,
|
||||
ScreenBright = 7,
|
||||
KbdBright = 8,
|
||||
};
|
||||
|
||||
struct ControlItem {
|
||||
@@ -282,31 +284,55 @@ class PhoneController {
|
||||
void refreshAll() {
|
||||
items_.clear();
|
||||
|
||||
// 1. Speaker volume (0..100%, mapped to ALSA RX3 Digital Volume 0..84)
|
||||
long rx3 = alsa_.getInteger("RX3 Digital Volume", 84);
|
||||
int spkPct = std::clamp(static_cast<int>(std::lround((rx3 * 100.0) / 84.0)), 0, 100);
|
||||
// 1. Speaker volume (0..100%, mapped to ALSA RX3 Digital Volume 60..92 + PipeWire 0..100%)
|
||||
int spkPct = readIntFile(getSxmoCfgDir() + "/speaker_vol", -1);
|
||||
if (spkPct < 0 || spkPct > 100) {
|
||||
long rx3 = alsa_.getInteger("RX3 Digital Volume", 92);
|
||||
if (rx3 <= 0) {
|
||||
spkPct = 0;
|
||||
} else if (rx3 >= 84) {
|
||||
spkPct = 100;
|
||||
} else {
|
||||
spkPct = std::clamp(static_cast<int>(std::lround(((rx3 - 60) * 100.0) / 32.0)), 5, 100);
|
||||
}
|
||||
}
|
||||
spkPct = (spkPct / 5) * 5;
|
||||
items_.push_back({ControlId::SpeakerVol, kIconSpeaker, kIconSoundOff,
|
||||
"Speaker", true, 0, 100, 5, spkPct});
|
||||
|
||||
// 2. Earpiece volume (0..100%, mapped to ALSA RX1 Digital Volume 0..84)
|
||||
// 2. Earpiece volume (0..100%, mapped to ALSA RX1 Digital Volume 58..90)
|
||||
long rx1 = alsa_.getInteger("RX1 Digital Volume", 84);
|
||||
int earPct = std::clamp(static_cast<int>(std::lround((rx1 * 100.0) / 84.0)), 0, 100);
|
||||
int earPct = 100;
|
||||
if (rx1 <= 0) {
|
||||
earPct = 0;
|
||||
} else if (rx1 < 84) {
|
||||
earPct = std::clamp(static_cast<int>(std::lround((rx1 * 100.0) / 84.0)), 5, 100);
|
||||
}
|
||||
earPct = (earPct / 5) * 5;
|
||||
items_.push_back({ControlId::EarpieceVol, kIconEarpiece, kIconEarpiece,
|
||||
"Earpiece", true, 0, 100, 5, earPct});
|
||||
|
||||
// 3. Flashlight on/off (toggle)
|
||||
// 3. Ringtone volume (0..100%, stored as SXMO_RINGVOL in ~/.config/sxmo/ringsettings)
|
||||
int ringPct = readRingVolume();
|
||||
items_.push_back({ControlId::RingVol, kIconBell, kIconBellSlash,
|
||||
"Ring Vol", true, 0, 100, 5, ringPct});
|
||||
|
||||
// 4. Ringer Mode (0=Ring+Vib, 1=Vibrate, 2=Silent, 3=DND)
|
||||
int ringerVal = readRingerMode();
|
||||
items_.push_back({ControlId::RingerMode, kIconBell, kIconBellSlash,
|
||||
"Ringer", false, 0, 3, 1, ringerVal});
|
||||
|
||||
// 5. Flashlight on/off (toggle)
|
||||
int torchCur = readIntFile(torchDir_ + "/brightness", 0);
|
||||
items_.push_back({ControlId::Flashlight, kIconFlashlight, kIconFlashlight,
|
||||
"Flashlight", false, 0, 1, 1, (torchCur > 0) ? 1 : 0});
|
||||
|
||||
// 4. Sound on/off (toggle)
|
||||
// 6. Sound on/off (toggle)
|
||||
bool spkOn = alsa_.getBoolean("SPK DAC Switch", true);
|
||||
items_.push_back({ControlId::SoundMute, kIconSoundOn, kIconSoundOff,
|
||||
"Sound", false, 0, 1, 1, spkOn ? 1 : 0});
|
||||
|
||||
// 5. Microphone on/off (toggle)
|
||||
// 7. Microphone on/off (toggle)
|
||||
unsigned int dec1 = alsa_.getEnum("DEC1 MUX", 2);
|
||||
if (dec1 > 0) {
|
||||
savedMicMux_ = dec1;
|
||||
@@ -317,18 +343,13 @@ class PhoneController {
|
||||
items_.push_back({ControlId::MicMute, kIconMicOn, kIconMicOff,
|
||||
"Microphone", false, 0, 1, 1, (dec1 > 0) ? 1 : 0});
|
||||
|
||||
// 6. Ringer Mode (0=Ring+Vib, 1=Vibrate, 2=Silent, 3=DND)
|
||||
int ringerVal = readRingerMode();
|
||||
items_.push_back({ControlId::RingerMode, kIconBell, kIconBellSlash,
|
||||
"Ringer", false, 0, 3, 1, ringerVal});
|
||||
|
||||
// 7. Screen brightness (slider 1..screenMax_)
|
||||
// 8. Screen brightness (slider 1..screenMax_)
|
||||
int scrCur = std::clamp(readIntFile(backlightDir_ + "/brightness", screenMax_), 1, screenMax_);
|
||||
int scrStep = std::max(1, screenMax_ / 14);
|
||||
items_.push_back({ControlId::ScreenBright, kIconBrightness, kIconBrightness,
|
||||
"Screen", true, 1, screenMax_, scrStep, scrCur});
|
||||
|
||||
// 7. Keyboard brightness (slider if max_brightness > 1, toggle otherwise)
|
||||
// 9. Keyboard brightness (slider if max_brightness > 1, toggle otherwise)
|
||||
int kbdCur = std::clamp(readIntFile(kbdDir_ + "/brightness", 0), 0, kbdMax_);
|
||||
if (kbdSupportsSlider_) {
|
||||
int kbdStep = std::max(1, kbdMax_ / 10);
|
||||
@@ -395,25 +416,81 @@ class PhoneController {
|
||||
return 0; // Ring + Vibrate
|
||||
}
|
||||
|
||||
static int readRingVolume() {
|
||||
std::ifstream ifs(getSxmoCfgDir() + "/ringsettings");
|
||||
std::string line;
|
||||
while (std::getline(ifs, line)) {
|
||||
auto eq = line.find('=');
|
||||
if (eq == std::string::npos) continue;
|
||||
std::string k = line.substr(0, eq);
|
||||
std::string v = line.substr(eq + 1);
|
||||
if (k == "SXMO_RINGVOL") {
|
||||
int val = std::clamp(std::atoi(v.c_str()), 0, 100);
|
||||
return (val / 5) * 5;
|
||||
}
|
||||
}
|
||||
return 100;
|
||||
}
|
||||
|
||||
static void writeRingVolume(int ringVol) {
|
||||
std::string cfg = getSxmoCfgDir();
|
||||
::mkdir(cfg.c_str(), 0755);
|
||||
std::string path = cfg + "/ringsettings";
|
||||
int ringTime = 20;
|
||||
int ringLoops = 10;
|
||||
int vibStrength = 2;
|
||||
{
|
||||
std::ifstream ifs(path);
|
||||
std::string line;
|
||||
while (std::getline(ifs, line)) {
|
||||
auto eq = line.find('=');
|
||||
if (eq == std::string::npos) continue;
|
||||
std::string k = line.substr(0, eq);
|
||||
int val = std::atoi(line.substr(eq + 1).c_str());
|
||||
if (k == "SXMO_RINGTIME" && val >= 5 && val <= 120) ringTime = val;
|
||||
else if (k == "SXMO_RINGNUMBER" && val >= 1 && val <= 100) ringLoops = val;
|
||||
else if (k == "SXMO_VIBRATE_STRENGTH" && val >= 1 && val <= 3) vibStrength = val;
|
||||
}
|
||||
}
|
||||
std::ofstream ofs(path, std::ios::trunc);
|
||||
if (ofs) {
|
||||
ofs << "SXMO_RINGTIME=" << ringTime << "\n";
|
||||
ofs << "SXMO_RINGNUMBER=" << ringLoops << "\n";
|
||||
ofs << "SXMO_VIBRATE_STRENGTH=" << vibStrength << "\n";
|
||||
ofs << "SXMO_RINGVOL=" << std::clamp(ringVol, 0, 100) << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void apply(ControlItem& it) {
|
||||
switch (it.id) {
|
||||
case ControlId::SpeakerVol: {
|
||||
long hw = std::lround((it.value * 84.0) / 100.0);
|
||||
std::string cfg = getSxmoCfgDir();
|
||||
::mkdir(cfg.c_str(), 0755);
|
||||
writeIntFile(cfg + "/speaker_vol", it.value);
|
||||
long hw = (it.value <= 0) ? 0 : (60 + std::lround((it.value * 32.0) / 100.0));
|
||||
if (it.value > 0) {
|
||||
alsa_.setBoolean("SPK DAC Switch", true);
|
||||
}
|
||||
alsa_.setInteger("RX3 Digital Volume", hw);
|
||||
char cmd[128];
|
||||
char cmd[160];
|
||||
std::snprintf(
|
||||
cmd, sizeof(cmd),
|
||||
"wpctl set-mute @DEFAULT_AUDIO_SINK@ %d >/dev/null 2>&1; "
|
||||
"wpctl set-volume @DEFAULT_AUDIO_SINK@ %d%% >/dev/null 2>&1; "
|
||||
"sxmo_hook_statusbar.sh audio >/dev/null 2>&1",
|
||||
it.value);
|
||||
"sxmo_hook_statusbar.sh volume >/dev/null 2>&1",
|
||||
(it.value <= 0) ? 1 : 0, it.value);
|
||||
runAsyncCmd(cmd);
|
||||
break;
|
||||
}
|
||||
case ControlId::EarpieceVol: {
|
||||
long hw = std::lround((it.value * 84.0) / 100.0);
|
||||
long hw = (it.value <= 0) ? 0 : (58 + std::lround((it.value * 32.0) / 100.0));
|
||||
alsa_.setInteger("RX1 Digital Volume", hw);
|
||||
break;
|
||||
}
|
||||
case ControlId::RingVol: {
|
||||
writeRingVolume(it.value);
|
||||
break;
|
||||
}
|
||||
case ControlId::Flashlight: {
|
||||
int v = it.value ? torchMax_ : 0;
|
||||
if (!writeIntFile(torchDir_ + "/brightness", v)) {
|
||||
@@ -994,7 +1071,9 @@ static int xdrawer_run_main(int argc, char** argv) {
|
||||
case XK_4:
|
||||
case XK_5:
|
||||
case XK_6:
|
||||
case XK_7: {
|
||||
case XK_7:
|
||||
case XK_8:
|
||||
case XK_9: {
|
||||
size_t idx = static_cast<size_t>(sym - XK_1);
|
||||
if (idx < count) {
|
||||
selected = idx;
|
||||
|
||||
Reference in New Issue
Block a user