Add preloaded SMS, File Manager, and Camera (live viewfinder) DWM plugins
This commit is contained in:
@@ -347,15 +347,7 @@ int calllog_run_main(int argc, char** argv) {
|
||||
XSync(dpy, False);
|
||||
|
||||
auto showWindow = [&]() {
|
||||
if (isPidFileAlive(getRuntimeFile("xdialer.pid"))) {
|
||||
sendPluginCommand("dialer", "CLOSE");
|
||||
}
|
||||
if (isPidFileAlive(getRuntimeFile("xcontacts.pid"))) {
|
||||
sendPluginCommand("contacts", "CLOSE");
|
||||
}
|
||||
if (isPidFileAlive(getRuntimeFile("xdrawer.pid"))) {
|
||||
sendPluginCommand("xdrawer", "CLOSE");
|
||||
}
|
||||
closeOtherPlugins("calllog");
|
||||
if (!visible) {
|
||||
openStartMs = nowMs();
|
||||
startupIgnoreUntilMs = openStartMs + 90;
|
||||
@@ -457,11 +449,6 @@ int calllog_run_main(int argc, char** argv) {
|
||||
if (ev.xexpose.count == 0) {
|
||||
render();
|
||||
}
|
||||
} else if (ev.type == VisibilityNotify) {
|
||||
if (ev.xvisibility.state != VisibilityUnobscured) {
|
||||
XRaiseWindow(dpy, win);
|
||||
XFlush(dpy);
|
||||
}
|
||||
} else if (ev.type == KeyRelease) {
|
||||
KeySym sym = XLookupKeysym(&ev.xkey, 0);
|
||||
if (sym == XK_Menu || sym == XF86XK_KbdLcdMenu1 ||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -692,15 +692,7 @@ int runContactsApp(int argc, char** argv) {
|
||||
};
|
||||
|
||||
auto showWindow = [&](const std::string& arg) {
|
||||
if (isPidFileAlive(getRuntimeFile("xdialer.pid"))) {
|
||||
sendPluginCommand("dialer", "CLOSE");
|
||||
}
|
||||
if (isPidFileAlive(getRuntimeFile("xcalllog.pid"))) {
|
||||
sendPluginCommand("calllog", "CLOSE");
|
||||
}
|
||||
if (isPidFileAlive(getRuntimeFile("xdrawer.pid"))) {
|
||||
sendPluginCommand("xdrawer", "CLOSE");
|
||||
}
|
||||
closeOtherPlugins("contacts");
|
||||
bool openNewMode = (arg.rfind("new:", 0) == 0);
|
||||
std::string prefill = openNewMode ? arg.substr(4) : "";
|
||||
|
||||
@@ -844,13 +836,17 @@ int runContactsApp(int argc, char** argv) {
|
||||
break;
|
||||
case 1: // Send SMS
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
hideWindow(true);
|
||||
runAsyncCmd("sxmo_modemtext.sh sendtextmenu '" + c.number + "'");
|
||||
hideWindow(false);
|
||||
if (!sendPluginCommand("sms", "OPEN send:" + c.number)) {
|
||||
runAsyncArgv("xsms", "sendtextmenu", c.number.c_str());
|
||||
}
|
||||
break;
|
||||
case 2: // Read Messages
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
hideWindow(true);
|
||||
runAsyncCmd("sxmo_hook_tailtextlog.sh '" + c.number + "'");
|
||||
hideWindow(false);
|
||||
if (!sendPluginCommand("sms", "OPEN thread:" + c.number)) {
|
||||
runAsyncArgv("xsms", "tailtextlog", c.number.c_str());
|
||||
}
|
||||
break;
|
||||
case 3: // Open in Dialer
|
||||
writeSoftkeySuppress(nowMs() + 300);
|
||||
@@ -961,11 +957,6 @@ int runContactsApp(int argc, char** argv) {
|
||||
if (ev.xexpose.count == 0) {
|
||||
render();
|
||||
}
|
||||
} else if (ev.type == VisibilityNotify) {
|
||||
if (ev.xvisibility.state != VisibilityUnobscured) {
|
||||
XRaiseWindow(dpy, win);
|
||||
XFlush(dpy);
|
||||
}
|
||||
} else if (ev.type == KeyRelease) {
|
||||
KeySym sym = XLookupKeysym(&ev.xkey, 0);
|
||||
if (ev.xkey.keycode == 19 || sym == XK_0) {
|
||||
|
||||
@@ -386,15 +386,7 @@ int dialer_run_main(int argc, char** argv) {
|
||||
XSync(dpy, False);
|
||||
|
||||
auto showWindow = [&](const std::string& arg) {
|
||||
if (isPidFileAlive(getRuntimeFile("xcalllog.pid"))) {
|
||||
sendPluginCommand("calllog", "CLOSE");
|
||||
}
|
||||
if (isPidFileAlive(getRuntimeFile("xcontacts.pid"))) {
|
||||
sendPluginCommand("contacts", "CLOSE");
|
||||
}
|
||||
if (isPidFileAlive(getRuntimeFile("xdrawer.pid"))) {
|
||||
sendPluginCommand("xdrawer", "CLOSE");
|
||||
}
|
||||
closeOtherPlugins("dialer");
|
||||
std::string cleanArg = normalizePhone(arg);
|
||||
if (!visible) {
|
||||
openStartMs = nowMs();
|
||||
@@ -536,11 +528,6 @@ int dialer_run_main(int argc, char** argv) {
|
||||
if (ev.xexpose.count == 0) {
|
||||
render();
|
||||
}
|
||||
} else if (ev.type == VisibilityNotify) {
|
||||
if (ev.xvisibility.state != VisibilityUnobscured) {
|
||||
XRaiseWindow(dpy, win);
|
||||
XFlush(dpy);
|
||||
}
|
||||
} else if (ev.type == KeyRelease) {
|
||||
KeySym sym = XLookupKeysym(&ev.xkey, 0);
|
||||
if (ev.xkey.keycode == 19 || sym == XK_0) {
|
||||
|
||||
@@ -41,6 +41,14 @@ static int try_send_plugin_fifo(const char *plugin_name, int sub_argc, char **su
|
||||
} else if (sub_argc >= 2 && sub_argv[1] && strcmp(sub_argv[1], "newcontact") == 0) {
|
||||
const char *num = (sub_argc >= 3 && sub_argv[2]) ? sub_argv[2] : "";
|
||||
len = snprintf(cmd, sizeof(cmd), "OPEN new:%s\n", num);
|
||||
} else if (sub_argc >= 2 && sub_argv[1] && strcmp(sub_argv[1], "sendtextmenu") == 0) {
|
||||
const char *num = (sub_argc >= 3 && sub_argv[2]) ? sub_argv[2] : "";
|
||||
len = snprintf(cmd, sizeof(cmd), "OPEN send:%s\n", num);
|
||||
} else if (sub_argc >= 2 && sub_argv[1] &&
|
||||
(strcmp(sub_argv[1], "tailtextlog") == 0 ||
|
||||
strcmp(sub_argv[1], "conversationloop") == 0)) {
|
||||
const char *num = (sub_argc >= 3 && sub_argv[2]) ? sub_argv[2] : "";
|
||||
len = snprintf(cmd, sizeof(cmd), "OPEN thread:%s\n", num);
|
||||
} else if (sub_argc >= 2 && sub_argv[1] && sub_argv[1][0]) {
|
||||
len = snprintf(cmd, sizeof(cmd), "OPEN %s\n", sub_argv[1]);
|
||||
} else {
|
||||
@@ -73,6 +81,15 @@ static const char *resolve_plugin_name_from_argv0(const char *argv0) {
|
||||
if (strcasecmp(base, "xcontacts") == 0 || strcasecmp(base, "contacts") == 0) {
|
||||
return "contacts";
|
||||
}
|
||||
if (strcasecmp(base, "xsms") == 0 || strcasecmp(base, "sms") == 0) {
|
||||
return "sms";
|
||||
}
|
||||
if (strcasecmp(base, "xfiles") == 0 || strcasecmp(base, "files") == 0) {
|
||||
return "files";
|
||||
}
|
||||
if (strcasecmp(base, "xcamera") == 0 || strcasecmp(base, "camera") == 0) {
|
||||
return "camera";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,269 @@
|
||||
#ifndef DWM_PLUGINS_MEDIA_UTILS_H
|
||||
#define DWM_PLUGINS_MEDIA_UTILS_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <csetjmp>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <jpeglib.h>
|
||||
#include <png.h>
|
||||
|
||||
namespace bananarchy {
|
||||
|
||||
struct JpegErrMgr {
|
||||
struct jpeg_error_mgr pub;
|
||||
jmp_buf setjmpBuf;
|
||||
};
|
||||
|
||||
inline void jpegErrorExit(j_common_ptr cinfo) {
|
||||
JpegErrMgr* myerr = reinterpret_cast<JpegErrMgr*>(cinfo->err);
|
||||
longjmp(myerr->setjmpBuf, 1);
|
||||
}
|
||||
|
||||
inline bool saveRgb24AsJpeg(const std::string& path,
|
||||
const uint8_t* rgb,
|
||||
int width,
|
||||
int height,
|
||||
int quality = 92) {
|
||||
if (!rgb || width <= 0 || height <= 0) {
|
||||
return false;
|
||||
}
|
||||
FILE* fp = std::fopen(path.c_str(), "wb");
|
||||
if (!fp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct jpeg_compress_struct cinfo {};
|
||||
JpegErrMgr jerr {};
|
||||
cinfo.err = jpeg_std_error(&jerr.pub);
|
||||
jerr.pub.error_exit = jpegErrorExit;
|
||||
if (setjmp(jerr.setjmpBuf)) {
|
||||
jpeg_destroy_compress(&cinfo);
|
||||
std::fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
jpeg_create_compress(&cinfo);
|
||||
jpeg_stdio_dest(&cinfo, fp);
|
||||
cinfo.image_width = static_cast<JDIMENSION>(width);
|
||||
cinfo.image_height = static_cast<JDIMENSION>(height);
|
||||
cinfo.input_components = 3;
|
||||
cinfo.in_color_space = JCS_RGB;
|
||||
|
||||
jpeg_set_defaults(&cinfo);
|
||||
jpeg_set_quality(&cinfo, quality, TRUE);
|
||||
jpeg_start_compress(&cinfo, TRUE);
|
||||
|
||||
int rowStride = width * 3;
|
||||
while (cinfo.next_scanline < cinfo.image_height) {
|
||||
JSAMPROW rowPtr =
|
||||
const_cast<JSAMPROW>(rgb + cinfo.next_scanline * rowStride);
|
||||
jpeg_write_scanlines(&cinfo, &rowPtr, 1);
|
||||
}
|
||||
|
||||
jpeg_finish_compress(&cinfo);
|
||||
jpeg_destroy_compress(&cinfo);
|
||||
std::fclose(fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool loadJpegRgb24(const std::string& path,
|
||||
std::vector<uint8_t>* outRgb,
|
||||
int* outW,
|
||||
int* outH) {
|
||||
FILE* fp = std::fopen(path.c_str(), "rb");
|
||||
if (!fp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct jpeg_decompress_struct cinfo {};
|
||||
JpegErrMgr jerr {};
|
||||
cinfo.err = jpeg_std_error(&jerr.pub);
|
||||
jerr.pub.error_exit = jpegErrorExit;
|
||||
if (setjmp(jerr.setjmpBuf)) {
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
std::fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
jpeg_create_decompress(&cinfo);
|
||||
jpeg_stdio_src(&cinfo, fp);
|
||||
if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
std::fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use libjpeg fast DCT downscaling for large images so decoding is instant
|
||||
if (cinfo.image_width > 960 || cinfo.image_height > 960) {
|
||||
cinfo.scale_num = 1;
|
||||
cinfo.scale_denom = 2;
|
||||
}
|
||||
cinfo.out_color_space = JCS_RGB;
|
||||
jpeg_start_decompress(&cinfo);
|
||||
|
||||
int w = static_cast<int>(cinfo.output_width);
|
||||
int h = static_cast<int>(cinfo.output_height);
|
||||
if (w <= 0 || h <= 0 || w > 8192 || h > 8192) {
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
std::fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
outRgb->resize(static_cast<size_t>(w) * h * 3);
|
||||
int rowStride = w * 3;
|
||||
while (cinfo.output_scanline < cinfo.output_height) {
|
||||
JSAMPROW rowPtr = outRgb->data() + cinfo.output_scanline * rowStride;
|
||||
jpeg_read_scanlines(&cinfo, &rowPtr, 1);
|
||||
}
|
||||
|
||||
jpeg_finish_decompress(&cinfo);
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
std::fclose(fp);
|
||||
*outW = w;
|
||||
*outH = h;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool loadPngRgb24(const std::string& path,
|
||||
std::vector<uint8_t>* outRgb,
|
||||
int* outW,
|
||||
int* outH) {
|
||||
png_image image {};
|
||||
image.version = PNG_IMAGE_VERSION;
|
||||
if (!png_image_begin_read_from_file(&image, path.c_str())) {
|
||||
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,
|
||||
int* outH) {
|
||||
std::string low = path;
|
||||
for (char& c : low) {
|
||||
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
}
|
||||
if (low.size() >= 4 && low.substr(low.size() - 4) == ".png") {
|
||||
if (loadPngRgb24(path, outRgb, outW, outH)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (loadJpegRgb24(path, outRgb, outW, outH)) {
|
||||
return true;
|
||||
}
|
||||
if (loadPngRgb24(path, outRgb, outW, outH)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void scaleRgb24ToBgrxCanvas(const uint8_t* srcRgb,
|
||||
int srcW,
|
||||
int srcH,
|
||||
uint32_t* dstBgrx,
|
||||
int canvasW,
|
||||
int canvasH,
|
||||
uint32_t bgPixel = 0x111111) {
|
||||
for (int i = 0; i < canvasW * canvasH; ++i) {
|
||||
dstBgrx[i] = bgPixel;
|
||||
}
|
||||
if (!srcRgb || srcW <= 0 || srcH <= 0 || canvasW <= 0 || canvasH <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int drawW = canvasW;
|
||||
int drawH = (srcH * canvasW) / srcW;
|
||||
if (drawH > canvasH) {
|
||||
drawH = canvasH;
|
||||
drawW = (srcW * canvasH) / srcH;
|
||||
}
|
||||
drawW = std::max(1, drawW);
|
||||
drawH = std::max(1, drawH);
|
||||
|
||||
int offX = (canvasW - drawW) / 2;
|
||||
int offY = (canvasH - drawH) / 2;
|
||||
|
||||
for (int dy = 0; dy < drawH; ++dy) {
|
||||
int sy = std::min(srcH - 1, (dy * srcH) / drawH);
|
||||
const uint8_t* srow = srcRgb + static_cast<size_t>(sy) * srcW * 3;
|
||||
uint32_t* drow = dstBgrx + static_cast<size_t>(offY + dy) * canvasW + offX;
|
||||
for (int dx = 0; dx < drawW; ++dx) {
|
||||
int sx = std::min(srcW - 1, (dx * srcW) / drawW);
|
||||
const uint8_t* px = srow + sx * 3;
|
||||
drow[dx] = (static_cast<uint32_t>(px[0]) << 16) |
|
||||
(static_cast<uint32_t>(px[1]) << 8) |
|
||||
static_cast<uint32_t>(px[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void downscaleViewfinder800x600To240x180(const uint8_t* srcRgb,
|
||||
uint32_t* dstBgrx,
|
||||
int zoomFactor = 1) {
|
||||
if (!srcRgb || !dstBgrx) {
|
||||
return;
|
||||
}
|
||||
zoomFactor = std::clamp(zoomFactor, 1, 4);
|
||||
int cropW = 800 / zoomFactor;
|
||||
int cropH = 600 / zoomFactor;
|
||||
int startX = (800 - cropW) / 2;
|
||||
int startY = (600 - cropH) / 2;
|
||||
|
||||
for (int dy = 0; dy < 180; ++dy) {
|
||||
int sy = startY + (dy * cropH) / 180;
|
||||
const uint8_t* srow = srcRgb + static_cast<size_t>(sy) * 800 * 3;
|
||||
uint32_t* drow = dstBgrx + static_cast<size_t>(dy) * 240;
|
||||
for (int dx = 0; dx < 240; ++dx) {
|
||||
int sx = startX + (dx * cropW) / 240;
|
||||
const uint8_t* px = srow + sx * 3;
|
||||
drow[dx] = (static_cast<uint32_t>(px[0]) << 16) |
|
||||
(static_cast<uint32_t>(px[1]) << 8) |
|
||||
static_cast<uint32_t>(px[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string formatFileSize(off_t bytes) {
|
||||
char buf[32];
|
||||
if (bytes >= 1024LL * 1024LL * 1024LL) {
|
||||
std::snprintf(buf, sizeof(buf), "%.1fG",
|
||||
static_cast<double>(bytes) / (1024.0 * 1024.0 * 1024.0));
|
||||
} else if (bytes >= 1024LL * 1024LL) {
|
||||
std::snprintf(buf, sizeof(buf), "%.1fM",
|
||||
static_cast<double>(bytes) / (1024.0 * 1024.0));
|
||||
} else if (bytes >= 1024LL) {
|
||||
std::snprintf(buf, sizeof(buf), "%.1fK",
|
||||
static_cast<double>(bytes) / 1024.0);
|
||||
} else {
|
||||
std::snprintf(buf, sizeof(buf), "%lldB", static_cast<long long>(bytes));
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
} // namespace bananarchy
|
||||
|
||||
#endif // DWM_PLUGINS_MEDIA_UTILS_H
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
@@ -53,6 +54,21 @@ constexpr const char* kIconTrash = "\xef\x87\xb8"; // U+F1F8 delete/trash
|
||||
constexpr const char* kIconSms = "\xef\x83\xa0"; // U+F0E0 envelope/SMS
|
||||
constexpr const char* kIconList = "\xef\x80\xba"; // U+F03A list
|
||||
constexpr const char* kIconCheck = "\xef\x80\x8c"; // U+F00C check
|
||||
constexpr const char* kIconFolder = "\xef\x81\xbb"; // U+F07B folder
|
||||
constexpr const char* kIconFolderOpen = "\xef\x81\xbc"; // U+F07C folder-open
|
||||
constexpr const char* kIconFile = "\xef\x80\x96"; // U+F016 file
|
||||
constexpr const char* kIconFileText = "\xef\x85\x9c"; // U+F15C file-text
|
||||
constexpr const char* kIconImage = "\xef\x80\xbe"; // U+F03E image
|
||||
constexpr const char* kIconMusic = "\xef\x80\x81"; // U+F001 music/audio
|
||||
constexpr const char* kIconTerm = "\xef\x84\xa0"; // U+F120 terminal
|
||||
constexpr const char* kIconCamera = "\xef\x80\xb0"; // U+F030 camera
|
||||
constexpr const char* kIconFlash = "\xef\x83\xa7"; // U+F0E7 bolt/flash
|
||||
constexpr const char* kIconSend = "\xef\x87\x98"; // U+F1D8 paper-plane
|
||||
constexpr const char* kIconCopy = "\xef\x83\x85"; // U+F0C5 copy
|
||||
constexpr const char* kIconInfo = "\xef\x81\x9a"; // U+F05A info-circle
|
||||
constexpr const char* kIconRefresh = "\xef\x80\xa1"; // U+F021 refresh
|
||||
constexpr const char* kIconSort = "\xef\x83\x9c"; // U+F0DC sort
|
||||
constexpr const char* kIconEye = "\xef\x81\xae"; // U+F06E eye/view
|
||||
|
||||
inline int64_t nowMs() {
|
||||
struct timespec ts {};
|
||||
@@ -249,7 +265,9 @@ inline void runAsyncCmd(const std::string& cmd) {
|
||||
}
|
||||
}
|
||||
|
||||
inline void runAsyncArgv(const char* prog, const char* arg1 = nullptr) {
|
||||
inline void runAsyncArgv(const char* prog,
|
||||
const char* arg1 = nullptr,
|
||||
const char* arg2 = nullptr) {
|
||||
pid_t pid = ::fork();
|
||||
if (pid == 0) {
|
||||
::setsid();
|
||||
@@ -262,7 +280,13 @@ inline void runAsyncArgv(const char* prog, const char* arg1 = nullptr) {
|
||||
::close(nullfd);
|
||||
}
|
||||
}
|
||||
if (arg1) {
|
||||
if (arg1 && arg2) {
|
||||
execlp(prog, prog, arg1, arg2, static_cast<char*>(nullptr));
|
||||
std::string localBin = std::string("/home/user/.local/bin/") + prog;
|
||||
execl(localBin.c_str(), prog, arg1, arg2, static_cast<char*>(nullptr));
|
||||
std::string usrBin = std::string("/usr/bin/") + prog;
|
||||
execl(usrBin.c_str(), prog, arg1, arg2, static_cast<char*>(nullptr));
|
||||
} else if (arg1) {
|
||||
execlp(prog, prog, arg1, static_cast<char*>(nullptr));
|
||||
std::string localBin = std::string("/home/user/.local/bin/") + prog;
|
||||
execl(localBin.c_str(), prog, arg1, static_cast<char*>(nullptr));
|
||||
@@ -896,6 +920,296 @@ inline void placePhoneCall(const std::string& rawNumber) {
|
||||
runAsyncArgv("sxmo_modemdial.sh", num.c_str());
|
||||
}
|
||||
|
||||
inline void closeOtherPlugins(const char* selfPluginName) {
|
||||
struct PluginRef {
|
||||
const char* pidFile;
|
||||
const char* name;
|
||||
};
|
||||
static const PluginRef kAllPlugins[] = {
|
||||
{"xdrawer.pid", "xdrawer"},
|
||||
{"xdialer.pid", "dialer"},
|
||||
{"xcalllog.pid", "calllog"},
|
||||
{"xcontacts.pid", "contacts"},
|
||||
{"xsms.pid", "sms"},
|
||||
{"xfiles.pid", "files"},
|
||||
{"xcamera.pid", "camera"},
|
||||
};
|
||||
for (const auto& p : kAllPlugins) {
|
||||
if (selfPluginName && std::strcmp(p.name, selfPluginName) == 0) {
|
||||
continue;
|
||||
}
|
||||
if (isPidFileAlive(getRuntimeFile(p.pidFile))) {
|
||||
sendPluginCommand(p.name, "CLOSE");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string getModemLogDirPath() {
|
||||
const char* env = std::getenv("SXMO_LOGDIR");
|
||||
if (env && env[0]) {
|
||||
return env;
|
||||
}
|
||||
const char* xdg = std::getenv("XDG_DATA_HOME");
|
||||
if (xdg && xdg[0]) {
|
||||
return std::string(xdg) + "/sxmo/modem";
|
||||
}
|
||||
const char* home = std::getenv("HOME");
|
||||
return std::string(home && home[0] ? home : "/home/user") +
|
||||
"/.local/share/sxmo/modem";
|
||||
}
|
||||
|
||||
struct SmsMessage {
|
||||
bool incoming = true;
|
||||
std::string sender;
|
||||
std::string isoTime;
|
||||
std::string displayTime;
|
||||
time_t epoch = 0;
|
||||
std::string text;
|
||||
};
|
||||
|
||||
struct SmsThread {
|
||||
std::string number;
|
||||
std::string normNumber;
|
||||
std::string contactName;
|
||||
std::string t9Name;
|
||||
std::string lowerName;
|
||||
std::string lastPreview;
|
||||
std::string lastDisplayTime;
|
||||
time_t lastEpoch = 0;
|
||||
bool lastIncoming = true;
|
||||
bool hasDraft = false;
|
||||
int messageCount = 0;
|
||||
};
|
||||
|
||||
inline bool parseSmsHeaderLine(const std::string& line,
|
||||
bool* outIncoming,
|
||||
std::string* outSender,
|
||||
std::string* outIsoTime) {
|
||||
if (line.empty() || line.back() != ':') {
|
||||
return false;
|
||||
}
|
||||
if (line.rfind("Received at ", 0) == 0 && line.size() > 13) {
|
||||
*outIncoming = true;
|
||||
outSender->clear();
|
||||
*outIsoTime = line.substr(12, line.size() - 13);
|
||||
return true;
|
||||
}
|
||||
if (line.rfind("Received from ", 0) == 0) {
|
||||
size_t atPos = line.find(" at ");
|
||||
if (atPos != std::string::npos && atPos + 4 < line.size() - 1) {
|
||||
*outIncoming = true;
|
||||
*outSender = line.substr(14, atPos - 14);
|
||||
*outIsoTime = line.substr(atPos + 4, line.size() - (atPos + 4) - 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (line.rfind("Sent at ", 0) == 0 && line.size() > 9) {
|
||||
*outIncoming = false;
|
||||
outSender->clear();
|
||||
*outIsoTime = line.substr(8, line.size() - 9);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline std::vector<SmsMessage> loadSmsThreadMessages(const std::string& number) {
|
||||
std::vector<SmsMessage> msgs;
|
||||
std::string path = getModemLogDirPath() + "/" + number + "/sms.txt";
|
||||
std::ifstream in(path);
|
||||
if (!in.is_open()) {
|
||||
return msgs;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
bool hasCur = false;
|
||||
SmsMessage cur;
|
||||
|
||||
auto flushCur = [&]() {
|
||||
if (!hasCur) {
|
||||
return;
|
||||
}
|
||||
while (!cur.text.empty() &&
|
||||
(cur.text.back() == '\n' || cur.text.back() == '\r')) {
|
||||
cur.text.pop_back();
|
||||
}
|
||||
msgs.push_back(cur);
|
||||
cur = SmsMessage{};
|
||||
hasCur = false;
|
||||
};
|
||||
|
||||
while (std::getline(in, line)) {
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.pop_back();
|
||||
}
|
||||
bool inc = true;
|
||||
std::string sender;
|
||||
std::string iso;
|
||||
if (parseSmsHeaderLine(line, &inc, &sender, &iso)) {
|
||||
flushCur();
|
||||
hasCur = true;
|
||||
cur.incoming = inc;
|
||||
cur.sender = sender.empty() ? number : sender;
|
||||
cur.isoTime = iso;
|
||||
cur.displayTime = formatShortTime(iso);
|
||||
cur.epoch = parseIsoTime(iso);
|
||||
cur.text.clear();
|
||||
} else if (hasCur) {
|
||||
if (!cur.text.empty()) {
|
||||
cur.text.push_back('\n');
|
||||
}
|
||||
cur.text += line;
|
||||
} else if (!trimStr(line).empty()) {
|
||||
hasCur = true;
|
||||
cur.incoming = true;
|
||||
cur.sender = number;
|
||||
cur.text = line;
|
||||
}
|
||||
}
|
||||
flushCur();
|
||||
return msgs;
|
||||
}
|
||||
|
||||
inline std::string loadSmsDraft(const std::string& number) {
|
||||
if (number.empty()) {
|
||||
return "";
|
||||
}
|
||||
std::string path = getModemLogDirPath() + "/" + number + "/draft.txt";
|
||||
std::ifstream in(path);
|
||||
if (!in.is_open()) {
|
||||
return "";
|
||||
}
|
||||
std::ostringstream ss;
|
||||
ss << in.rdbuf();
|
||||
std::string s = ss.str();
|
||||
while (!s.empty() && (s.back() == '\n' || s.back() == '\r')) {
|
||||
s.pop_back();
|
||||
}
|
||||
if (s == "Enter message here") {
|
||||
return "";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
inline bool saveSmsDraft(const std::string& number, const std::string& text) {
|
||||
if (number.empty()) {
|
||||
return false;
|
||||
}
|
||||
std::string dir = getModemLogDirPath() + "/" + number;
|
||||
std::string path = dir + "/draft.txt";
|
||||
if (trimStr(text).empty()) {
|
||||
::unlink(path.c_str());
|
||||
return true;
|
||||
}
|
||||
ContactBook::ensureParentDir(path);
|
||||
std::ofstream out(path, std::ios::trunc);
|
||||
if (!out.is_open()) {
|
||||
return false;
|
||||
}
|
||||
out << text << "\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void removeSmsDraft(const std::string& number) {
|
||||
if (number.empty()) {
|
||||
return;
|
||||
}
|
||||
std::string path = getModemLogDirPath() + "/" + number + "/draft.txt";
|
||||
::unlink(path.c_str());
|
||||
}
|
||||
|
||||
inline bool deleteSmsThread(const std::string& number) {
|
||||
if (number.empty()) {
|
||||
return false;
|
||||
}
|
||||
std::string dir = getModemLogDirPath() + "/" + number;
|
||||
std::string smsPath = dir + "/sms.txt";
|
||||
std::string draftPath = dir + "/draft.txt";
|
||||
::unlink(smsPath.c_str());
|
||||
::unlink(draftPath.c_str());
|
||||
::rmdir(dir.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::vector<SmsThread> loadSmsThreads(const ContactBook& book) {
|
||||
std::vector<SmsThread> threads;
|
||||
std::string baseDir = getModemLogDirPath();
|
||||
DIR* d = ::opendir(baseDir.c_str());
|
||||
if (!d) {
|
||||
return threads;
|
||||
}
|
||||
|
||||
struct dirent* ent;
|
||||
while ((ent = ::readdir(d)) != nullptr) {
|
||||
std::string name = ent->d_name;
|
||||
if (name.empty() || name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
std::string subDir = baseDir + "/" + name;
|
||||
struct stat st {};
|
||||
if (::stat(subDir.c_str(), &st) != 0 || !S_ISDIR(st.st_mode)) {
|
||||
continue;
|
||||
}
|
||||
std::string smsPath = subDir + "/sms.txt";
|
||||
std::string draftPath = subDir + "/draft.txt";
|
||||
bool hasSms = (::access(smsPath.c_str(), R_OK) == 0);
|
||||
std::string draft = loadSmsDraft(name);
|
||||
bool hasDraft = !draft.empty();
|
||||
if (!hasSms && !hasDraft) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto msgs = loadSmsThreadMessages(name);
|
||||
if (msgs.empty() && !hasDraft) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SmsThread th;
|
||||
th.number = name;
|
||||
th.normNumber = normalizePhone(name);
|
||||
th.contactName = book.resolveName(name);
|
||||
std::string label = th.contactName.empty() ? name : th.contactName;
|
||||
th.t9Name = toT9Digits(label);
|
||||
th.lowerName = toLowerStr(label);
|
||||
th.hasDraft = hasDraft;
|
||||
th.messageCount = static_cast<int>(msgs.size());
|
||||
|
||||
if (!msgs.empty()) {
|
||||
const auto& last = msgs.back();
|
||||
th.lastIncoming = last.incoming;
|
||||
th.lastDisplayTime = last.displayTime;
|
||||
th.lastEpoch = last.epoch;
|
||||
std::string prev = last.text;
|
||||
for (char& c : prev) {
|
||||
if (c == '\n' || c == '\r' || c == '\t') {
|
||||
c = ' ';
|
||||
}
|
||||
}
|
||||
th.lastPreview = hasDraft ? ("[Draft] " + draft) : prev;
|
||||
} else {
|
||||
th.lastIncoming = false;
|
||||
th.lastPreview = "[Draft] " + draft;
|
||||
struct stat dst {};
|
||||
if (::stat(draftPath.c_str(), &dst) == 0) {
|
||||
th.lastEpoch = dst.st_mtim.tv_sec;
|
||||
}
|
||||
}
|
||||
if (th.lastEpoch == 0 && ::stat(smsPath.c_str(), &st) == 0) {
|
||||
th.lastEpoch = st.st_mtim.tv_sec;
|
||||
}
|
||||
threads.push_back(th);
|
||||
}
|
||||
::closedir(d);
|
||||
|
||||
std::sort(threads.begin(), threads.end(),
|
||||
[](const SmsThread& a, const SmsThread& b) {
|
||||
if (a.lastEpoch != b.lastEpoch) {
|
||||
return a.lastEpoch > b.lastEpoch;
|
||||
}
|
||||
return a.number < b.number;
|
||||
});
|
||||
return threads;
|
||||
}
|
||||
|
||||
} // namespace bananarchy
|
||||
|
||||
#endif // DWM_PLUGINS_PHONE_DATA_H
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -103,7 +103,7 @@ static Key keys[] = {
|
||||
|
||||
/* Quick Navigation */
|
||||
{ KeyPress, MODKEY, XK_p, spawn, SHCMD("sxmo_appmenu.sh") },
|
||||
{ KeyPress, MODKEY, XK_d, spawn, SHCMD("sxmo_files.sh") },
|
||||
{ KeyPress, MODKEY, XK_d, spawn, SHCMD("xfiles") },
|
||||
{ KeyPress, MODKEY|ShiftMask, XK_Return, spawn, SHCMD("st") },
|
||||
{ KeyPress, MODKEY, XK_b, togglebar, {0} },
|
||||
{ KeyPress, MODKEY, XK_j, focusstack, {.i = +1 } },
|
||||
|
||||
@@ -103,7 +103,7 @@ static Key keys[] = {
|
||||
|
||||
/* Quick Navigation */
|
||||
{ KeyPress, MODKEY, XK_p, spawn, SHCMD("sxmo_appmenu.sh") },
|
||||
{ KeyPress, MODKEY, XK_d, spawn, SHCMD("sxmo_files.sh") },
|
||||
{ KeyPress, MODKEY, XK_d, spawn, SHCMD("xfiles") },
|
||||
{ KeyPress, MODKEY|ShiftMask, XK_Return, spawn, SHCMD("st") },
|
||||
{ KeyPress, MODKEY, XK_b, togglebar, {0} },
|
||||
{ KeyPress, MODKEY, XK_j, focusstack, {.i = +1 } },
|
||||
|
||||
@@ -704,6 +704,9 @@ static int xdrawer_run_main(int argc, char** argv) {
|
||||
closeOtherPlugin("xdialer.pid", "/tmp/dwm_plugin_dialer.fifo");
|
||||
closeOtherPlugin("xcalllog.pid", "/tmp/dwm_plugin_calllog.fifo");
|
||||
closeOtherPlugin("xcontacts.pid", "/tmp/dwm_plugin_contacts.fifo");
|
||||
closeOtherPlugin("xsms.pid", "/tmp/dwm_plugin_sms.fifo");
|
||||
closeOtherPlugin("xfiles.pid", "/tmp/dwm_plugin_files.fifo");
|
||||
closeOtherPlugin("xcamera.pid", "/tmp/dwm_plugin_camera.fifo");
|
||||
openStartMs = nowMs();
|
||||
startupIgnoreUntilMs = openStartMs + 180;
|
||||
initialKeysReleased = (querySoftkeysDown(dpy) == 0);
|
||||
@@ -795,11 +798,6 @@ static int xdrawer_run_main(int argc, char** argv) {
|
||||
if (ev.xexpose.count == 0) {
|
||||
render();
|
||||
}
|
||||
} else if (ev.type == VisibilityNotify) {
|
||||
if (ev.xvisibility.state != VisibilityUnobscured) {
|
||||
XRaiseWindow(dpy, win);
|
||||
XFlush(dpy);
|
||||
}
|
||||
} else if (ev.type == KeyRelease) {
|
||||
KeySym sym = XLookupKeysym(&ev.xkey, 0);
|
||||
if (sym == XK_Menu || sym == XF86XK_KbdLcdMenu1 ||
|
||||
|
||||
Reference in New Issue
Block a user