Fix mobile data static IP bearer, call recording, earpiece audio, and ringtone/vibration
This commit is contained in:
@@ -192,10 +192,12 @@ std::string sanitizeForFilename(const std::string& raw) {
|
||||
}
|
||||
}
|
||||
if (out.empty()) {
|
||||
return "unknown";
|
||||
return "voice";
|
||||
}
|
||||
if (out.size() > 20) {
|
||||
out = out.substr(0, 20);
|
||||
if (out.size() > 10 && out[0] == '+') {
|
||||
out = out.substr(out.size() - 10);
|
||||
} else if (out.size() > 10) {
|
||||
out = out.substr(0, 10);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -217,7 +219,7 @@ void finalizeWavHeader(const std::string& path) {
|
||||
return;
|
||||
}
|
||||
uint8_t hdr[64] = {0};
|
||||
ssize_t n = :: pread(fd, hdr, sizeof(hdr), 0);
|
||||
ssize_t n = ::pread(fd, hdr, sizeof(hdr), 0);
|
||||
if (n < 44 || std::memcmp(hdr, "RIFF", 4) != 0 ||
|
||||
std::memcmp(hdr + 8, "WAVE", 4) != 0) {
|
||||
::close(fd);
|
||||
@@ -237,6 +239,12 @@ void finalizeWavHeader(const std::string& path) {
|
||||
};
|
||||
(void)::pwrite(fd, b, 4, 4);
|
||||
}
|
||||
ssize_t dataOff = -1;
|
||||
uint32_t dataBytes = 0;
|
||||
uint16_t audioFmt = static_cast<uint16_t>(hdr[20]) |
|
||||
(static_cast<uint16_t>(hdr[21]) << 8);
|
||||
uint16_t bitsPerSample = static_cast<uint16_t>(hdr[34]) |
|
||||
(static_cast<uint16_t>(hdr[35]) << 8);
|
||||
for (ssize_t off = 12; off + 8 <= n;) {
|
||||
uint32_t chunkLen = static_cast<uint32_t>(hdr[off + 4]) |
|
||||
(static_cast<uint32_t>(hdr[off + 5]) << 8) |
|
||||
@@ -244,6 +252,8 @@ void finalizeWavHeader(const std::string& path) {
|
||||
(static_cast<uint32_t>(hdr[off + 7]) << 24);
|
||||
if (std::memcmp(hdr + off, "data", 4) == 0) {
|
||||
uint32_t expectedData = static_cast<uint32_t>(st.st_size - (off + 8));
|
||||
dataOff = off + 8;
|
||||
dataBytes = expectedData;
|
||||
if (chunkLen == 0 || chunkLen != expectedData) {
|
||||
uint8_t b[4] = {
|
||||
static_cast<uint8_t>(expectedData & 0xff),
|
||||
@@ -258,6 +268,33 @@ void finalizeWavHeader(const std::string& path) {
|
||||
if (chunkLen == 0) break;
|
||||
off += 8 + static_cast<ssize_t>((chunkLen + 1) & ~1u);
|
||||
}
|
||||
// Peak-normalize quiet 16-bit signed PCM recordings so playback is clearly audible
|
||||
if (audioFmt == 1 && bitsPerSample == 16 && dataOff >= 44 &&
|
||||
dataBytes >= 2 && dataBytes <= 32 * 1024 * 1024) {
|
||||
size_t numSamples = dataBytes / 2;
|
||||
std::vector<int16_t> pcm(numSamples);
|
||||
ssize_t rd = ::pread(fd, pcm.data(), numSamples * sizeof(int16_t), dataOff);
|
||||
if (rd == static_cast<ssize_t>(numSamples * sizeof(int16_t))) {
|
||||
int maxAbs = 0;
|
||||
size_t skip = std::min<size_t>(numSamples / 4, 400);
|
||||
for (size_t i = skip; i < numSamples; ++i) {
|
||||
int v = std::abs(static_cast<int>(pcm[i]));
|
||||
if (v > maxAbs) {
|
||||
maxAbs = v;
|
||||
}
|
||||
}
|
||||
if (maxAbs >= 32 && maxAbs < 18000) {
|
||||
double gain = std::min(24.0, 24000.0 / static_cast<double>(maxAbs));
|
||||
if (gain > 1.15) {
|
||||
for (size_t i = 0; i < numSamples; ++i) {
|
||||
int scaled = static_cast<int>(std::lround(pcm[i] * gain));
|
||||
pcm[i] = static_cast<int16_t>(std::clamp(scaled, -32767, 32767));
|
||||
}
|
||||
(void)::pwrite(fd, pcm.data(), numSamples * sizeof(int16_t), dataOff);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
::fsync(fd);
|
||||
::close(fd);
|
||||
}
|
||||
@@ -434,7 +471,7 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
bool ringMuted = false;
|
||||
bool micMuted = false;
|
||||
bool speakerOn = false;
|
||||
int callVolumePct = 80;
|
||||
int callVolumePct = 85;
|
||||
int selectedRow = 0;
|
||||
|
||||
int64_t callStartMs = 0;
|
||||
@@ -446,6 +483,8 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
int64_t recordStartMs = 0;
|
||||
std::string recordFilePath;
|
||||
std::string recordFileName;
|
||||
int lastRecordedSec = 0;
|
||||
int recordedClipCount = 0;
|
||||
|
||||
bool visible = false;
|
||||
int64_t openStartMs = 0;
|
||||
@@ -454,6 +493,22 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
int softkeyPending = 0;
|
||||
int64_t softkeyDeadlineMs = 0;
|
||||
|
||||
auto pctToHwVol = [](int pct, bool spk) -> long {
|
||||
int c = std::clamp(pct, 5, 100);
|
||||
// RX1/RX3 Digital Volume: 84 = 0 dB, 88 = +4 dB, 92 = +8 dB, 94 = +10 dB
|
||||
return spk ? (68 + (c * 26) / 100) : (68 + (c * 24) / 100);
|
||||
};
|
||||
|
||||
auto hwVolToPct = [](long hwVol, bool spk) -> int {
|
||||
if (hwVol < 68) {
|
||||
return 85;
|
||||
}
|
||||
int span = spk ? 26 : 24;
|
||||
int pct = static_cast<int>(std::lround(((hwVol - 68) * 100.0) / span));
|
||||
pct = std::clamp(pct, 5, 100);
|
||||
return std::max(5, ((pct + 2) / 5) * 5);
|
||||
};
|
||||
|
||||
auto refreshHardwareAudioState = [&]() {
|
||||
unsigned int dec1 = alsa.getEnum("DEC1 MUX", 1);
|
||||
if (dec1 > 0) {
|
||||
@@ -464,27 +519,18 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
savedMicMux = static_cast<unsigned int>(std::max(1, readIntFile("/tmp/xdrawer_mic_mux", 1)));
|
||||
micMuted = true;
|
||||
}
|
||||
bool spkSwitch = alsa.getBoolean("SPK DAC Switch", false);
|
||||
bool earSwitch = (alsa.getEnum("EAR_S", 1) != 0);
|
||||
speakerOn = (spkSwitch && !earSwitch);
|
||||
|
||||
long rawVol = speakerOn ? alsa.getInteger("RX3 Digital Volume", 68)
|
||||
: alsa.getInteger("RX1 Digital Volume", 68);
|
||||
if (rawVol <= 0) {
|
||||
rawVol = alsa.getInteger("RX1 Digital Volume", 68);
|
||||
}
|
||||
callVolumePct = std::clamp(static_cast<int>(std::lround((rawVol * 100.0) / 84.0)), 0, 100);
|
||||
callVolumePct = (callVolumePct / 5) * 5;
|
||||
if (callVolumePct == 0) {
|
||||
callVolumePct = 75;
|
||||
int savedVol = readIntFile("/tmp/sxmo_call_vol", -1);
|
||||
if (savedVol >= 5 && savedVol <= 100) {
|
||||
callVolumePct = savedVol;
|
||||
}
|
||||
};
|
||||
|
||||
auto setMicMuted = [&](bool mute) {
|
||||
micMuted = mute;
|
||||
alsa.setEnum("DEC1 MUX", mute ? 0 : (savedMicMux > 0 ? savedMicMux : 1));
|
||||
alsa.setInteger("ADC1 Volume", mute ? 0 : 6);
|
||||
alsa.setInteger("ADC3 Volume", mute ? 0 : 6);
|
||||
alsa.setInteger("ADC1 Volume", mute ? 0 : 8);
|
||||
alsa.setInteger("ADC3 Volume", mute ? 0 : 8);
|
||||
alsa.setInteger("TX1 Digital Volume", mute ? 0 : 88);
|
||||
alsa.setBoolean("VoiceMMode1 Capture Mixer TERT_MI2S_TX", !mute);
|
||||
runAsyncCmd(mute ? "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ 1 >/dev/null 2>&1"
|
||||
: "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ 0 >/dev/null 2>&1");
|
||||
@@ -494,7 +540,15 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
|
||||
auto setSpeakerRoute = [&](bool toSpeaker) {
|
||||
speakerOn = toSpeaker;
|
||||
long hwVol = std::lround((callVolumePct * 84.0) / 100.0);
|
||||
long hwVol = pctToHwVol(callVolumePct, toSpeaker);
|
||||
alsa.setBoolean("PRI_MI2S_RX Voice Mixer VoiceMMode1", true);
|
||||
alsa.setBoolean("VoiceMMode1 Capture Mixer TERT_MI2S_TX", !micMuted);
|
||||
alsa.setEnum("CIC1 MUX", 0);
|
||||
if (!micMuted) {
|
||||
alsa.setEnum("DEC1 MUX", savedMicMux > 0 ? savedMicMux : 1);
|
||||
alsa.setInteger("ADC1 Volume", 8);
|
||||
alsa.setInteger("TX1 Digital Volume", 88);
|
||||
}
|
||||
if (toSpeaker) {
|
||||
alsa.setEnum("EAR_S", 0);
|
||||
alsa.setEnum("RX1 MIX1 INP1", 0);
|
||||
@@ -505,9 +559,11 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
} else {
|
||||
alsa.setBoolean("SPK DAC Switch", false);
|
||||
alsa.setEnum("RX3 MIX1 INP1", 0);
|
||||
alsa.setInteger("RX3 Digital Volume", 0);
|
||||
alsa.setEnum("RX1 MIX1 INP1", 3);
|
||||
alsa.setEnum("RDAC2 MUX", 0);
|
||||
alsa.setEnum("EAR_S", 1);
|
||||
alsa.setBoolean("RX1 Mute Switch", false);
|
||||
alsa.setInteger("RX1 Digital Volume", hwVol);
|
||||
statusNotice = "Audio routed to Earpiece";
|
||||
}
|
||||
@@ -516,10 +572,10 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
|
||||
auto adjustVolume = [&](int deltaPct) {
|
||||
callVolumePct = std::clamp(callVolumePct + deltaPct, 5, 100);
|
||||
long hwVol = std::lround((callVolumePct * 84.0) / 100.0);
|
||||
alsa.setInteger("RX1 Digital Volume", hwVol);
|
||||
writeIntFile("/tmp/sxmo_call_vol", callVolumePct);
|
||||
alsa.setInteger("RX1 Digital Volume", pctToHwVol(callVolumePct, false));
|
||||
if (speakerOn) {
|
||||
alsa.setInteger("RX3 Digital Volume", hwVol);
|
||||
alsa.setInteger("RX3 Digital Volume", pctToHwVol(callVolumePct, true));
|
||||
}
|
||||
char cmd[128];
|
||||
std::snprintf(cmd, sizeof(cmd),
|
||||
@@ -528,6 +584,24 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
runAsyncCmd(cmd);
|
||||
};
|
||||
|
||||
auto finishRecordingFile = [&]() {
|
||||
alsa.setBoolean("MultiMedia2 Mixer TERT_MI2S_TX", false);
|
||||
finalizeWavHeader(recordFilePath);
|
||||
struct stat st {};
|
||||
if (!recordFilePath.empty() && ::stat(recordFilePath.c_str(), &st) == 0 &&
|
||||
st.st_size > 44) {
|
||||
int durFromBytes = static_cast<int>((st.st_size - 44 + 8000) / 16000);
|
||||
int durFromWall = static_cast<int>((nowMs() - recordStartMs + 500) / 1000);
|
||||
lastRecordedSec = std::max(1, std::max(durFromBytes, durFromWall));
|
||||
++recordedClipCount;
|
||||
statusNotice = "SAVED (" + formatTimerSec(lastRecordedSec) + ") " + recordFileName;
|
||||
statusNoticeUntilMs = nowMs() + 8000;
|
||||
} else if (!recordFileName.empty()) {
|
||||
statusNotice = "Recording failed";
|
||||
statusNoticeUntilMs = nowMs() + 4000;
|
||||
}
|
||||
};
|
||||
|
||||
auto checkRecordingAlive = [&]() -> bool {
|
||||
if (recordPid <= 0) {
|
||||
return false;
|
||||
@@ -536,7 +610,7 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
pid_t r = ::waitpid(recordPid, &status, WNOHANG);
|
||||
if (r == recordPid || (r < 0 && ::kill(recordPid, 0) != 0)) {
|
||||
recordPid = -1;
|
||||
finalizeWavHeader(recordFilePath);
|
||||
finishRecordingFile();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -565,11 +639,7 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
}
|
||||
recordPid = -1;
|
||||
}
|
||||
finalizeWavHeader(recordFilePath);
|
||||
if (!recordFileName.empty()) {
|
||||
statusNotice = "Saved: " + recordFileName;
|
||||
statusNoticeUntilMs = nowMs() + 3500;
|
||||
}
|
||||
finishRecordingFile();
|
||||
};
|
||||
|
||||
auto startRecording = [&]() -> bool {
|
||||
@@ -581,18 +651,19 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
struct tm tmNow {};
|
||||
localtime_r(&nowSec, &tmNow);
|
||||
char tsBuf[32];
|
||||
std::strftime(tsBuf, sizeof(tsBuf), "%Y%m%d_%H%M%S", &tmNow);
|
||||
std::strftime(tsBuf, sizeof(tsBuf), "%H%M%S", &tmNow);
|
||||
|
||||
std::string tag = sanitizeForFilename(
|
||||
!contactName.empty() ? contactName : (!callNumber.empty() ? callNumber : "voice"));
|
||||
recordFileName = "call_" + tag + "_" + tsBuf + ".wav";
|
||||
recordFileName = std::string("rec_") + tsBuf + "_" + tag + ".wav";
|
||||
recordFilePath = dir + "/" + recordFileName;
|
||||
|
||||
// Ensure MultiMedia2 capture mixer and primary mic path are active for PipeWire recording
|
||||
// Enable MultiMedia2 capture mixer at 8 kHz mono to match active VoiceMMode1 AFE port
|
||||
alsa.setEnum("CIC1 MUX", 0);
|
||||
if (!micMuted) {
|
||||
alsa.setEnum("DEC1 MUX", savedMicMux > 0 ? savedMicMux : 1);
|
||||
alsa.setInteger("ADC1 Volume", 6);
|
||||
alsa.setInteger("ADC1 Volume", 8);
|
||||
alsa.setInteger("TX1 Digital Volume", 88);
|
||||
}
|
||||
alsa.setBoolean("MultiMedia2 Mixer TERT_MI2S_TX", true);
|
||||
|
||||
@@ -602,12 +673,6 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
}
|
||||
if (pid == 0) {
|
||||
::setsid();
|
||||
std::string runtimeDir = "/run/user/" + std::to_string(::getuid());
|
||||
const char* xdg = std::getenv("XDG_RUNTIME_DIR");
|
||||
struct stat stRuntime {};
|
||||
if (!xdg || !*xdg || ::stat(xdg, &stRuntime) != 0) {
|
||||
::setenv("XDG_RUNTIME_DIR", runtimeDir.c_str(), 1);
|
||||
}
|
||||
int nullfd = ::open("/dev/null", O_RDWR);
|
||||
if (nullfd >= 0) {
|
||||
::dup2(nullfd, STDIN_FILENO);
|
||||
@@ -617,19 +682,17 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
::close(nullfd);
|
||||
}
|
||||
}
|
||||
execlp("pw-record", "pw-record", "--format", "s16", "--rate", "16000",
|
||||
"--channels", "1", recordFilePath.c_str(),
|
||||
execlp("arecord", "arecord", "-q", "-D", "hw:0,1", "-f", "S16_LE",
|
||||
"-r", "8000", "-c", "1", recordFilePath.c_str(),
|
||||
static_cast<char*>(nullptr));
|
||||
execlp("parecord", "parecord", "--rate=16000", "--channels=1",
|
||||
"--file-format=wav", recordFilePath.c_str(), static_cast<char*>(nullptr));
|
||||
execlp("arecord", "arecord", "-f", "S16_LE", "-r", "16000", "-c", "1",
|
||||
execlp("arecord", "arecord", "-q", "-f", "S16_LE", "-r", "8000", "-c", "1",
|
||||
recordFilePath.c_str(), static_cast<char*>(nullptr));
|
||||
_exit(1);
|
||||
}
|
||||
recordPid = pid;
|
||||
recordStartMs = nowMs();
|
||||
statusNotice = "Recording to " + recordFileName;
|
||||
statusNoticeUntilMs = nowMs() + 2500;
|
||||
statusNotice = "REC -> " + recordFileName;
|
||||
statusNoticeUntilMs = nowMs() + 3000;
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -804,9 +867,17 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
drawPango(layoutSmall, &colLight, 8, 88, 26,
|
||||
"[Call] Answer [*] Mute [End] Reject", 224);
|
||||
} else if (callState == CallState::Ended) {
|
||||
drawPango(layoutSmall, &colMuted, 8, 88, 26,
|
||||
recordFileName.empty() ? "Call finished" : ("Saved: " + recordFileName),
|
||||
224);
|
||||
if (recordedClipCount > 0 && !recordFileName.empty()) {
|
||||
std::string savedStr = "SAVED (" + formatTimerSec(lastRecordedSec) + ") " +
|
||||
recordFileName;
|
||||
drawPango(layoutSmall, &colGreen, 8, 88, 26, savedStr, 224);
|
||||
} else {
|
||||
drawPango(layoutSmall, &colMuted, 8, 88, 26, "Call finished", 224);
|
||||
}
|
||||
} else if (recordedClipCount > 0 && !recordFileName.empty()) {
|
||||
std::string savedStr = "SAVED (" + formatTimerSec(lastRecordedSec) + ") " +
|
||||
recordFileName;
|
||||
drawPango(layoutSmall, &colGreen, 8, 88, 26, savedStr, 224);
|
||||
} else {
|
||||
drawPango(layoutSmall, &colMuted, 8, 88, 26,
|
||||
"[1] Mic [2] Rec [3] Route [0] DTMF", 224);
|
||||
@@ -883,16 +954,21 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
"[1] Microphone", std::max(80, kWinWidth - vw - 48));
|
||||
drawPango(layoutMain, icCol, kWinWidth - vw - 8, ry, rowH, valStr);
|
||||
} else if (i == 1) {
|
||||
XftColor* icCol = isRec ? &colRed : &colMuted;
|
||||
std::string valStr = isRec
|
||||
? ("REC " + formatTimerSec(static_cast<int>((now - recordStartMs) / 1000)))
|
||||
: "OFF";
|
||||
XftColor* icCol = isRec ? &colRed
|
||||
: (recordedClipCount > 0 ? &colGreen : &colMuted);
|
||||
std::string valStr;
|
||||
if (isRec) {
|
||||
valStr = "REC " + formatTimerSec(static_cast<int>((now - recordStartMs) / 1000));
|
||||
} else if (recordedClipCount > 0) {
|
||||
valStr = "SAVED " + formatTimerSec(lastRecordedSec);
|
||||
} else {
|
||||
valStr = "OFF";
|
||||
}
|
||||
int vw = pangoWidth(layoutMain, valStr);
|
||||
drawPango(layoutIcon, icCol, 10, ry, rowH, kIconRecord);
|
||||
drawPango(layoutMain, isSel ? &colWhite : &colLight, 34, ry, rowH,
|
||||
"[2] Record", std::max(80, kWinWidth - vw - 44));
|
||||
drawPango(layoutMain, isRec ? &colRed : &colMuted,
|
||||
kWinWidth - vw - 8, ry, rowH, valStr);
|
||||
"[2] Record", std::max(72, kWinWidth - vw - 44));
|
||||
drawPango(layoutMain, icCol, kWinWidth - vw - 8, ry, rowH, valStr);
|
||||
} else if (i == 2) {
|
||||
const char* ic = speakerOn ? kIconSpeaker : kIconEarpiece;
|
||||
XftColor* icCol = speakerOn ? &colYellow : &colBlue;
|
||||
@@ -1242,9 +1318,13 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
if (foundState == "active" && callState != CallState::Active) {
|
||||
if (callState != CallState::Outgoing) {
|
||||
speakerOn = false;
|
||||
}
|
||||
callState = CallState::Active;
|
||||
callStartMs = nowMs();
|
||||
ringMuted = false;
|
||||
setSpeakerRoute(speakerOn);
|
||||
writeActiveStateFiles();
|
||||
render();
|
||||
} else if (foundState == "ringing-in" && callState == CallState::Idle) {
|
||||
@@ -1359,10 +1439,15 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
callId.clear();
|
||||
callNumber.clear();
|
||||
contactName.clear();
|
||||
recordFileName.clear();
|
||||
recordFilePath.clear();
|
||||
lastRecordedSec = 0;
|
||||
recordedClipCount = 0;
|
||||
updateCallerDetails(arg1, arg2);
|
||||
callState = CallState::Incoming;
|
||||
callStartMs = nowMs();
|
||||
ringMuted = false;
|
||||
speakerOn = false;
|
||||
dtmfMode = false;
|
||||
dtmfSent.clear();
|
||||
selectedRow = 0;
|
||||
@@ -1372,24 +1457,39 @@ int callscreen_run_main(int argc, char** argv) {
|
||||
callId.clear();
|
||||
callNumber.clear();
|
||||
contactName.clear();
|
||||
recordFileName.clear();
|
||||
recordFilePath.clear();
|
||||
lastRecordedSec = 0;
|
||||
recordedClipCount = 0;
|
||||
updateCallerDetails(arg1, arg2);
|
||||
callState = CallState::Outgoing;
|
||||
callStartMs = nowMs();
|
||||
ringMuted = false;
|
||||
speakerOn = false;
|
||||
dtmfMode = false;
|
||||
dtmfSent.clear();
|
||||
selectedRow = 0;
|
||||
setSpeakerRoute(false);
|
||||
statusNotice.clear();
|
||||
statusNoticeUntilMs = 0;
|
||||
writeActiveStateFiles();
|
||||
showWindow();
|
||||
} else if (verb == "ACTIVE") {
|
||||
bool wasActive = (callState == CallState::Active);
|
||||
bool wasOutgoing = (callState == CallState::Outgoing);
|
||||
updateCallerDetails(arg1, arg2);
|
||||
callState = CallState::Active;
|
||||
if (!wasActive) {
|
||||
callStartMs = nowMs();
|
||||
if (!wasOutgoing) {
|
||||
speakerOn = false;
|
||||
}
|
||||
}
|
||||
ringMuted = false;
|
||||
selectedRow = 0;
|
||||
setSpeakerRoute(speakerOn);
|
||||
statusNotice.clear();
|
||||
statusNoticeUntilMs = 0;
|
||||
writeActiveStateFiles();
|
||||
showWindow();
|
||||
} else if (verb == "OPEN") {
|
||||
|
||||
@@ -1003,7 +1003,9 @@ int runNetworksApp(int argc, char** argv) {
|
||||
}
|
||||
std::string cmd = "nmcli connection add type gsm con-name " +
|
||||
shellQuote(cleanApn) + " ifname '*' apn " +
|
||||
shellQuote(cleanApn) + " connection.autoconnect yes";
|
||||
shellQuote(cleanApn) +
|
||||
" ipv4.method auto ipv6.method ignore"
|
||||
" connection.autoconnect yes";
|
||||
if (!trimStr(apnUser).empty()) {
|
||||
cmd += " gsm.username " + shellQuote(trimStr(apnUser));
|
||||
}
|
||||
@@ -1148,9 +1150,15 @@ int runNetworksApp(int argc, char** argv) {
|
||||
bool turnOn = !cellInfo.gsmActive;
|
||||
setBanner(turnOn ? "Enabling Mobile Data..." : "Disabling Mobile Data...");
|
||||
render();
|
||||
if (turnOn) {
|
||||
runCaptureCmd("nmcli connection modify " +
|
||||
shellQuote(cellInfo.gsmConName) +
|
||||
" ipv4.method auto ipv6.method ignore >/dev/null 2>&1");
|
||||
}
|
||||
runCaptureCmd("nmcli connection " + std::string(turnOn ? "up " : "down ") +
|
||||
shellQuote(cellInfo.gsmConName) + " >/dev/null 2>&1");
|
||||
refreshCurrentTab(false);
|
||||
runAsyncCmd("sxmo_hook_statusbar.sh modem >/dev/null 2>&1");
|
||||
}
|
||||
} else if (cellRowSel == 1) {
|
||||
apnName = cellInfo.apn;
|
||||
|
||||
@@ -46,7 +46,7 @@ struct RingSettings {
|
||||
int ringVol = 100;
|
||||
int ringTimeSec = 20;
|
||||
int ringLoops = 10;
|
||||
int vibrateStrength = 1;
|
||||
int vibrateStrength = 2;
|
||||
};
|
||||
|
||||
constexpr int kRingTimeSteps[] = {10, 15, 20, 25, 30, 45, 60};
|
||||
@@ -164,6 +164,10 @@ std::string loadActiveRingtonePath() {
|
||||
}
|
||||
const char* envRing = std::getenv("SXMO_RINGTONE");
|
||||
if (envRing && *envRing) return envRing;
|
||||
struct stat stDef {};
|
||||
if (::stat("/usr/share/sxmo/ringtones/Banana_Chime.wav", &stDef) == 0) {
|
||||
return "/usr/share/sxmo/ringtones/Banana_Chime.wav";
|
||||
}
|
||||
return "/usr/share/sxmo/ring.ogg";
|
||||
}
|
||||
|
||||
@@ -386,15 +390,22 @@ int runRingtonesApp(int argc, char** argv) {
|
||||
settings = loadRingSettings();
|
||||
int rvol = std::clamp(settings.ringVol, 0, 100);
|
||||
int rx3 = (rvol <= 0) ? 0 : (60 + rvol * 34 / 100);
|
||||
int mpvVol = rvol * 130 / 100;
|
||||
int mpvVol = rvol;
|
||||
std::string volArg = "--volume=" + std::to_string(mpvVol);
|
||||
runAsyncCmd("amixer -c 0 cset \"name='EAR_S'\" 0 >/dev/null 2>&1; "
|
||||
"amixer -c 0 cset \"name='RX1 MIX1 INP1'\" 0 >/dev/null 2>&1; "
|
||||
"amixer -c 0 cset \"name='PRI_MI2S_RX Audio Mixer MultiMedia1'\" 1 >/dev/null 2>&1; "
|
||||
"amixer -c 0 cset \"name='SPK DAC Switch'\" 1 >/dev/null 2>&1; "
|
||||
"amixer -c 0 cset \"name='RX3 MIX1 INP1'\" 3 >/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");
|
||||
"wpctl set-volume @DEFAULT_AUDIO_SINK@ " +
|
||||
std::to_string(rvol) + "% >/dev/null 2>&1; "
|
||||
"(sleep 0.25; amixer -c 0 cset \"name='RX3 Digital Volume'\" " +
|
||||
std::to_string(rx3) + " >/dev/null 2>&1; "
|
||||
"wpctl set-volume @DEFAULT_AUDIO_SINK@ " +
|
||||
std::to_string(rvol) + "% >/dev/null 2>&1) &");
|
||||
pid_t pid = ::fork();
|
||||
if (pid == 0) {
|
||||
std::string runtimeDir = "/run/user/" + std::to_string(::getuid());
|
||||
@@ -410,7 +421,8 @@ int runRingtonesApp(int argc, char** argv) {
|
||||
::dup2(devNull, STDERR_FILENO);
|
||||
if (devNull > 2) ::close(devNull);
|
||||
}
|
||||
::execlp("mpv", "mpv", "--no-resume-playback", "--quiet",
|
||||
::execlp("mpv", "mpv", "--no-config", "--no-terminal",
|
||||
"--no-resume-playback", "--quiet", "--vo=null",
|
||||
"--no-video", "--ao=pipewire,pulse,alsa",
|
||||
"--audio-channels=stereo", "--audio-samplerate=48000",
|
||||
"--volume-max=150", volArg.c_str(),
|
||||
@@ -564,6 +576,8 @@ int runRingtonesApp(int argc, char** argv) {
|
||||
"Vibrate Only",
|
||||
"Silent",
|
||||
};
|
||||
const char* vibLabels[3] = {"1 (Light)", "2 (Medium)", "3 (Strong)"};
|
||||
int vIdx = std::clamp(settings.vibrateStrength, 1, 3) - 1;
|
||||
std::string rows[6][2] = {
|
||||
{"Ringer Mode",
|
||||
"< " + std::string(modeLabels[settings.ringerMode % 4]) + " >"},
|
||||
@@ -574,7 +588,7 @@ int runRingtonesApp(int argc, char** argv) {
|
||||
{"Ring Loop Count",
|
||||
"< " + std::to_string(settings.ringLoops) + " loops >"},
|
||||
{"Vibrate Strength",
|
||||
"< Level " + std::to_string(settings.vibrateStrength) + " >"},
|
||||
"< Level " + std::string(vibLabels[vIdx]) + " >"},
|
||||
{"Test Vibration", "Press OK to vibrate"},
|
||||
};
|
||||
|
||||
@@ -665,6 +679,13 @@ int runRingtonesApp(int argc, char** argv) {
|
||||
} else if (settingsRow == 1) {
|
||||
settings.ringVol = std::clamp(settings.ringVol + delta * 5, 0, 100);
|
||||
saveRingSettings(settings);
|
||||
if (previewPid > 0) {
|
||||
int rx3 = (settings.ringVol <= 0) ? 0 : (60 + settings.ringVol * 34 / 100);
|
||||
runAsyncCmd("amixer -c 0 cset \"name='RX3 Digital Volume'\" " +
|
||||
std::to_string(rx3) + " >/dev/null 2>&1; "
|
||||
"wpctl set-volume @DEFAULT_AUDIO_SINK@ " +
|
||||
std::to_string(settings.ringVol) + "% >/dev/null 2>&1");
|
||||
}
|
||||
setBanner("Ring volume: " + std::to_string(settings.ringVol) + "%");
|
||||
} else if (settingsRow == 2) {
|
||||
int curIdx = 2;
|
||||
@@ -692,17 +713,14 @@ int runRingtonesApp(int argc, char** argv) {
|
||||
settings.vibrateStrength =
|
||||
((settings.vibrateStrength - 1 + 3 + delta) % 3) + 1;
|
||||
saveRingSettings(settings);
|
||||
runAsyncCmd("SXMO_VIBRATE_FORCE=1 sxmo_vibrate 450 " +
|
||||
std::to_string(settings.vibrateStrength) + " >/dev/null 2>&1");
|
||||
setBanner("Vibration level: " + std::to_string(settings.vibrateStrength));
|
||||
} else if (settingsRow == 5) {
|
||||
int mag = (settings.vibrateStrength == 1)
|
||||
? 8000
|
||||
: (settings.vibrateStrength == 3 ? 65535 : 32000);
|
||||
runAsyncCmd(
|
||||
"for d in /dev/input/by-path/platform-vibrator-event "
|
||||
"/dev/input/by-path/*vibrator*-event; do "
|
||||
"[ -e \"$d\" ] && export SXMO_VIBRATE_DEV=\"$d\" && break; done; "
|
||||
"sxmo_vibrate 600 " +
|
||||
std::to_string(mag) + " >/dev/null 2>&1");
|
||||
setBanner("Testing vibration...");
|
||||
runAsyncCmd("SXMO_VIBRATE_FORCE=1 sxmo_vibrate 800 " +
|
||||
std::to_string(settings.vibrateStrength) + " >/dev/null 2>&1");
|
||||
setBanner("Testing vibration (Level " +
|
||||
std::to_string(settings.vibrateStrength) + ")...");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+107
-26
@@ -2,6 +2,7 @@
|
||||
#include <fcntl.h>
|
||||
#include <glob.h>
|
||||
#include <linux/input.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -9,17 +10,42 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int read_configured_level(void) {
|
||||
char path[512];
|
||||
static volatile sig_atomic_t g_stop = 0;
|
||||
|
||||
static void handle_signal(int sig) {
|
||||
(void)sig;
|
||||
g_stop = 1;
|
||||
}
|
||||
|
||||
static void get_sxmo_config_dir(char* out, size_t out_sz) {
|
||||
const char* cfg = getenv("XDG_CONFIG_HOME");
|
||||
const char* home = getenv("HOME");
|
||||
if (cfg && *cfg) {
|
||||
snprintf(path, sizeof(path), "%s/sxmo/ringsettings", cfg);
|
||||
snprintf(out, out_sz, "%s/sxmo", cfg);
|
||||
} else if (home && *home) {
|
||||
snprintf(path, sizeof(path), "%s/.config/sxmo/ringsettings", home);
|
||||
snprintf(out, out_sz, "%s/.config/sxmo", home);
|
||||
} else {
|
||||
snprintf(path, sizeof(path), "/home/user/.config/sxmo/ringsettings");
|
||||
snprintf(out, out_sz, "/home/user/.config/sxmo");
|
||||
}
|
||||
}
|
||||
|
||||
static int is_vibrate_muted(void) {
|
||||
const char* force = getenv("SXMO_VIBRATE_FORCE");
|
||||
if (force && *force == '1') {
|
||||
return 0;
|
||||
}
|
||||
char dir[448];
|
||||
char path[512];
|
||||
get_sxmo_config_dir(dir, sizeof(dir));
|
||||
snprintf(path, sizeof(path), "%s/.novibrate", dir);
|
||||
return access(path, F_OK) == 0;
|
||||
}
|
||||
|
||||
static int read_configured_level(void) {
|
||||
char dir[448];
|
||||
char path[512];
|
||||
get_sxmo_config_dir(dir, sizeof(dir));
|
||||
snprintf(path, sizeof(path), "%s/ringsettings", dir);
|
||||
|
||||
FILE* fp = fopen(path, "r");
|
||||
if (!fp) return 0;
|
||||
@@ -32,24 +58,45 @@ static int read_configured_level(void) {
|
||||
if (strncmp(p, "SXMO_VIBRATE_STRENGTH=", 22) == 0) {
|
||||
int v = atoi(p + 22);
|
||||
if (v >= 1 && v <= 3) level = v;
|
||||
else if (v > 3 && v <= 16000) level = 1;
|
||||
else if (v > 16000 && v <= 45000) level = 2;
|
||||
else if (v > 45000) level = 3;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
return level;
|
||||
}
|
||||
|
||||
static int resolve_magnitude(int raw_arg, int has_arg) {
|
||||
static int resolve_level_and_magnitude(int raw_arg, int has_arg, int* level_out) {
|
||||
int val = raw_arg;
|
||||
if (!has_arg || (val == 1 && !getenv("SXMO_VIBRATE_STRENGTH"))) {
|
||||
const char* env_str = getenv("SXMO_VIBRATE_STRENGTH");
|
||||
if (!has_arg || (val == 1 && (!env_str || !*env_str))) {
|
||||
int cfg = read_configured_level();
|
||||
if (cfg >= 1 && cfg <= 3) val = cfg;
|
||||
if (cfg >= 1 && cfg <= 3) {
|
||||
val = cfg;
|
||||
} else if (env_str && *env_str) {
|
||||
int ev = atoi(env_str);
|
||||
if (ev >= 1) val = ev;
|
||||
} else if (!has_arg) {
|
||||
val = 2;
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
int level = 2;
|
||||
int mag = 32000;
|
||||
if (val <= 1 || (val >= 256 && val <= 16000)) {
|
||||
level = 1;
|
||||
mag = 14000;
|
||||
} else if (val == 2 || (val > 16000 && val <= 45000)) {
|
||||
level = 2;
|
||||
mag = 32000;
|
||||
} else {
|
||||
level = 3;
|
||||
mag = 65535;
|
||||
}
|
||||
|
||||
if (level_out) *level_out = level;
|
||||
return mag;
|
||||
}
|
||||
|
||||
static int open_vibrator_device(void) {
|
||||
@@ -77,6 +124,15 @@ static int open_vibrator_device(void) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void sleep_ms_interruptible(long ms) {
|
||||
if (ms <= 0 || g_stop) return;
|
||||
struct timespec req;
|
||||
req.tv_sec = ms / 1000;
|
||||
req.tv_nsec = (ms % 1000) * 1000000L;
|
||||
while (!g_stop && nanosleep(&req, &req) == -1 && errno == EINTR) {
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2 || argc > 3) {
|
||||
fprintf(stderr, "Usage: sxmo_vibrate duration_ms [strength_number]\n");
|
||||
@@ -91,6 +147,10 @@ int main(int argc, char** argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (is_vibrate_muted()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int raw_strength = 2;
|
||||
int has_strength = 0;
|
||||
if (argc == 3) {
|
||||
@@ -103,7 +163,16 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
int magnitude = resolve_magnitude(raw_strength, has_strength);
|
||||
int level = 2;
|
||||
int magnitude = resolve_level_and_magnitude(raw_strength, has_strength, &level);
|
||||
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = handle_signal;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
sigaction(SIGTERM, &sa, NULL);
|
||||
sigaction(SIGHUP, &sa, NULL);
|
||||
|
||||
int fd = open_vibrator_device();
|
||||
if (fd < 0) {
|
||||
@@ -128,18 +197,30 @@ int main(int argc, char** argv) {
|
||||
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) {
|
||||
if (level >= 3 || duration_ms <= 90) {
|
||||
long actual_ms = (level == 1 && duration_ms > 55) ? 55 : duration_ms;
|
||||
play.value = 1;
|
||||
if (write(fd, &play, sizeof(play)) >= 0) {
|
||||
sleep_ms_interruptible(actual_ms);
|
||||
}
|
||||
} else {
|
||||
long on_ms = (level == 1) ? 75 : 160;
|
||||
long off_ms = (level == 1) ? 125 : 80;
|
||||
long rem = duration_ms;
|
||||
while (rem > 0 && !g_stop) {
|
||||
long cur_on = (rem < on_ms) ? rem : on_ms;
|
||||
play.value = 1;
|
||||
if (write(fd, &play, sizeof(play)) < 0) break;
|
||||
sleep_ms_interruptible(cur_on);
|
||||
rem -= cur_on;
|
||||
play.value = 0;
|
||||
(void)write(fd, &play, sizeof(play));
|
||||
if (rem <= 0 || g_stop) break;
|
||||
long cur_off = (rem < off_ms) ? rem : off_ms;
|
||||
sleep_ms_interruptible(cur_off);
|
||||
rem -= cur_off;
|
||||
}
|
||||
}
|
||||
|
||||
play.value = 0;
|
||||
|
||||
+63
-16
@@ -43,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 = 9;
|
||||
constexpr int kRowCount = 10;
|
||||
|
||||
// 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
|
||||
@@ -242,11 +242,12 @@ enum class ControlId {
|
||||
EarpieceVol = 1,
|
||||
RingVol = 2,
|
||||
RingerMode = 3,
|
||||
Flashlight = 4,
|
||||
SoundMute = 5,
|
||||
MicMute = 6,
|
||||
ScreenBright = 7,
|
||||
KbdBright = 8,
|
||||
VibrateStrength = 4,
|
||||
Flashlight = 5,
|
||||
SoundMute = 6,
|
||||
MicMute = 7,
|
||||
ScreenBright = 8,
|
||||
KbdBright = 9,
|
||||
};
|
||||
|
||||
struct ControlItem {
|
||||
@@ -300,15 +301,15 @@ class PhoneController {
|
||||
items_.push_back({ControlId::SpeakerVol, kIconSpeaker, kIconSoundOff,
|
||||
"Speaker", true, 0, 100, 5, spkPct});
|
||||
|
||||
// 2. Earpiece volume (0..100%, mapped to ALSA RX1 Digital Volume 58..90)
|
||||
long rx1 = alsa_.getInteger("RX1 Digital Volume", 84);
|
||||
int earPct = 100;
|
||||
// 2. Earpiece volume (0..100%, mapped to ALSA RX1 Digital Volume 68..92)
|
||||
long rx1 = alsa_.getInteger("RX1 Digital Volume", 88);
|
||||
int earPct = 85;
|
||||
if (rx1 <= 0) {
|
||||
earPct = 0;
|
||||
} else if (rx1 < 84) {
|
||||
earPct = std::clamp(static_cast<int>(std::lround((rx1 * 100.0) / 84.0)), 5, 100);
|
||||
} else if (rx1 >= 68) {
|
||||
earPct = std::clamp(static_cast<int>(std::lround(((rx1 - 68) * 100.0) / 24.0)), 5, 100);
|
||||
}
|
||||
earPct = (earPct / 5) * 5;
|
||||
earPct = ((earPct + 2) / 5) * 5;
|
||||
items_.push_back({ControlId::EarpieceVol, kIconEarpiece, kIconEarpiece,
|
||||
"Earpiece", true, 0, 100, 5, earPct});
|
||||
|
||||
@@ -322,6 +323,11 @@ class PhoneController {
|
||||
items_.push_back({ControlId::RingerMode, kIconBell, kIconBellSlash,
|
||||
"Ringer", false, 0, 3, 1, ringerVal});
|
||||
|
||||
// 5. Vibration Strength (1=Light, 2=Medium, 3=Strong)
|
||||
int vibVal = readVibrateStrength();
|
||||
items_.push_back({ControlId::VibrateStrength, kIconBell, kIconBell,
|
||||
"Vibration", false, 1, 3, 1, vibVal});
|
||||
|
||||
// 5. Flashlight on/off (toggle)
|
||||
int torchCur = readIntFile(torchDir_ + "/brightness", 0);
|
||||
items_.push_back({ControlId::Flashlight, kIconFlashlight, kIconFlashlight,
|
||||
@@ -373,6 +379,8 @@ class PhoneController {
|
||||
it.value = std::clamp(it.value + dir * it.stepVal, it.minVal, it.maxVal);
|
||||
} else if (it.id == ControlId::RingerMode) {
|
||||
it.value = (it.value + dir + 4) % 4;
|
||||
} else if (it.id == ControlId::VibrateStrength) {
|
||||
it.value = ((it.value - 1 + dir + 3) % 3) + 1;
|
||||
} else {
|
||||
it.value = it.value ? 0 : 1;
|
||||
}
|
||||
@@ -387,6 +395,9 @@ class PhoneController {
|
||||
if (it.id == ControlId::RingerMode) {
|
||||
it.value = (it.value + 1) % 4;
|
||||
apply(it);
|
||||
} else if (it.id == ControlId::VibrateStrength) {
|
||||
it.value = (it.value % 3) + 1;
|
||||
apply(it);
|
||||
} else if (!it.isSlider) {
|
||||
it.value = it.value ? 0 : 1;
|
||||
apply(it);
|
||||
@@ -432,13 +443,30 @@ class PhoneController {
|
||||
return 100;
|
||||
}
|
||||
|
||||
static void writeRingVolume(int ringVol) {
|
||||
static int readVibrateStrength() {
|
||||
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_VIBRATE_STRENGTH") {
|
||||
int val = std::atoi(v.c_str());
|
||||
if (val >= 1 && val <= 3) return val;
|
||||
}
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
static void writeRingAndVibSettings(int newRingVol, int newVibStrength) {
|
||||
std::string cfg = getSxmoCfgDir();
|
||||
::mkdir(cfg.c_str(), 0755);
|
||||
std::string path = cfg + "/ringsettings";
|
||||
int ringTime = 20;
|
||||
int ringLoops = 10;
|
||||
int vibStrength = 2;
|
||||
int ringVol = 100;
|
||||
{
|
||||
std::ifstream ifs(path);
|
||||
std::string line;
|
||||
@@ -450,14 +478,17 @@ class PhoneController {
|
||||
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;
|
||||
else if (k == "SXMO_RINGVOL" && val >= 0 && val <= 100) ringVol = val;
|
||||
}
|
||||
}
|
||||
if (newRingVol >= 0) ringVol = std::clamp(newRingVol, 0, 100);
|
||||
if (newVibStrength >= 1 && newVibStrength <= 3) vibStrength = newVibStrength;
|
||||
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";
|
||||
ofs << "SXMO_RINGVOL=" << ringVol << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,12 +515,21 @@ class PhoneController {
|
||||
break;
|
||||
}
|
||||
case ControlId::EarpieceVol: {
|
||||
long hw = (it.value <= 0) ? 0 : (58 + std::lround((it.value * 32.0) / 100.0));
|
||||
long hw = (it.value <= 0) ? 0 : (68 + std::lround((it.value * 24.0) / 100.0));
|
||||
alsa_.setInteger("RX1 Digital Volume", hw);
|
||||
break;
|
||||
}
|
||||
case ControlId::RingVol: {
|
||||
writeRingVolume(it.value);
|
||||
writeRingAndVibSettings(it.value, -1);
|
||||
break;
|
||||
}
|
||||
case ControlId::VibrateStrength: {
|
||||
writeRingAndVibSettings(-1, it.value);
|
||||
char cmd[96];
|
||||
std::snprintf(cmd, sizeof(cmd),
|
||||
"SXMO_VIBRATE_FORCE=1 sxmo_vibrate 400 %d >/dev/null 2>&1",
|
||||
it.value);
|
||||
runAsyncCmd(cmd);
|
||||
break;
|
||||
}
|
||||
case ControlId::Flashlight: {
|
||||
@@ -792,6 +832,13 @@ static int xdrawer_run_main(int argc, char** argv) {
|
||||
XftColor* rCol = (it.value == 0) ? (isSel ? &colYellow : &colGreen)
|
||||
: ((it.value == 1) ? &colYellow : &colMuted);
|
||||
drawPango(layoutSmall, rCol, kWinWidth - stW - 6, ry, rowH, stStr);
|
||||
} else if (it.id == ControlId::VibrateStrength) {
|
||||
const char* vModes[3] = {"Light", "Medium", "Strong"};
|
||||
int vIdx = std::clamp(it.value, 1, 3) - 1;
|
||||
std::string stStr = std::string("< ") + vModes[vIdx] + " >";
|
||||
int stW = pangoWidth(layoutSmall, stStr);
|
||||
XftColor* vCol = isSel ? &colYellow : &colGreen;
|
||||
drawPango(layoutSmall, vCol, kWinWidth - stW - 6, ry, rowH, stStr);
|
||||
} else {
|
||||
bool on = (it.value != 0);
|
||||
std::string stStr = on ? "ON" : "OFF";
|
||||
|
||||
Reference in New Issue
Block a user