pinephone-screenlock/screenlock.c
Jonathan Hodgson fb63acbdf7 Removes pending unlock screen due to dificulty with incoming call
Ideally I'd determine what the unlock source is and if it's the modem,
disable the pending screen
2021-03-31 21:30:00 +01:00

551 lines
14 KiB
C

#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <X11/keysym.h>
#include <X11/XF86keysym.h>
#include <X11/XKBlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <linux/rtc.h>
// Types
enum State {
StateNoInput, // Screen on / input lock
StateNoInputNoScreen, // Screen off / input lock
StateSuspend, // Deep sleep
StateSuspendPending, // Suspend 'woken up', must leave state in <5s, or kicks to StateSuspend
StateDead // Exit the appliation
};
enum Color {
Red,
Blue,
Purple,
Off
};
// Fn declarations
int checkrtcwake();
void configuresuspendsettingsandwakeupsources();
time_t convert_rtc_time(struct rtc_time * rtc);
void die(const char *err, ...);
void getoldbrightness();
void init_rtc();
void lockscreen(Display *dpy, int screen, int blank);
void unblankscreen();
void blankscreen();
void readinputloop(Display *dpy, int screen);
int presuspend();
void postwake();
void setpineled(enum Color c);
int setup_rtc_wakeup();
void sigterm();
void syncstate();
void usage();
void writefile(char *filepath, char *str);
// Variables
Display *dpy;
Window root;
enum State state = StateNoInput;
int suspendtimeouts = 35;
int suspendpendingsceenon = 0;
int suspendpendingtimeouts = 0;
KeySym lastkeysym = XK_Cancel;
int lastkeyn = 0;
char oldbrightness[10] = "200";
char * brightnessfile = "/sys/devices/platform/backlight/backlight/backlight/brightness";
char * powerstatefile = "/sys/power/state";
int rtc_fd = 0; //file descriptor
time_t wakeinterval = 0; //wake every x seconds
time_t waketime = 0; //next wakeup time according to the RTC clock
int slept = 0; //indicates whether the process has slept (crust) or not
int blanked = 0; //indicated whether the display blanked or not
// Real time clock device
#define RTC_DEVICE "/dev/rtc0"
time_t
convert_rtc_time(struct rtc_time * rtc) {
struct tm tm;
memset(&tm, 0, sizeof tm);
tm.tm_sec = rtc->tm_sec;
tm.tm_min = rtc->tm_min;
tm.tm_hour = rtc->tm_hour;
tm.tm_mday = rtc->tm_mday;
tm.tm_mon = rtc->tm_mon;
tm.tm_year = rtc->tm_year;
tm.tm_isdst = -1; /* assume the system knows better than the RTC */
return mktime(&tm);
}
int setup_rtc_wakeup() {
//(code adapted from util-linux's rtcwake)
struct tm *tm;
struct rtc_wkalrm wake;
struct rtc_time now_rtc;
if (ioctl(rtc_fd, RTC_RD_TIME, &now_rtc) < 0) {
fprintf(stderr, "Error reading rtc time\n");
}
const time_t now = convert_rtc_time(&now_rtc);
waketime = now + wakeinterval;
tm = localtime(&waketime);
wake.time.tm_sec = tm->tm_sec;
wake.time.tm_min = tm->tm_min;
wake.time.tm_hour = tm->tm_hour;
wake.time.tm_mday = tm->tm_mday;
wake.time.tm_mon = tm->tm_mon;
wake.time.tm_year = tm->tm_year;
/* wday, yday, and isdst fields are unused by Linux */
wake.time.tm_wday = -1;
wake.time.tm_yday = -1;
wake.time.tm_isdst = -1;
fprintf(stderr, "Setting RTC wakeup to %ld: (UTC) %s", waketime, asctime(tm));
if (ioctl(rtc_fd, RTC_ALM_SET, &wake.time) < 0) {
fprintf(stderr, "error setting rtc alarm\n");
return -1;
}
if (ioctl(rtc_fd, RTC_AIE_ON, 0) < 0) {
fprintf(stderr, "error enabling rtc alarm\n");
return -1;
}
return 0;
}
void
configuresuspendsettingsandwakeupsources()
{
// Disable all wakeup sources
struct dirent *wakeupsource;
char wakeuppath[100];
DIR *wakeupsources = opendir("/sys/class/wakeup");
if (wakeupsources == NULL)
die("Couldn't open directory /sys/class/wakeup\n");
while ((wakeupsource = readdir(wakeupsources)) != NULL) {
sprintf(
wakeuppath,
"/sys/class/wakeup/%.50s/device/power/wakeup",
wakeupsource->d_name
);
fprintf(stderr, "Disabling wakeup source: %s", wakeupsource->d_name);
writefile(wakeuppath, "disabled");
fprintf(stderr, ".. ok\n");
}
closedir(wakeupsources);
// Enable powerbutton wakeup source
fprintf(stderr, "Enable powerbutton wakeup source\n");
writefile(
"/sys/devices/platform/soc/1f03400.rsb/sunxi-rsb-3a3/axp221-pek/power/wakeup",
"enabled"
);
// Enable IRQ wakeup source (incoming call) 5.10
fprintf(stderr, "Enable 5.10 IRQ wakeup source\n");
writefile(
"/sys/devices/platform/gpio-keys/power/wakeup",
"enabled"
);
// Enable IRQ wakeup source (incoming call) 5.9 (no longer exists in 5.10)
fprintf(stderr, "Enable 5.9 IRQ wakeup source\n");
writefile(
"/sys/devices/platform/soc/1c28c00.serial/serial1/serial1-0/power/wakeup",
"enabled"
);
// Enable rtc wakeup source
fprintf(stderr, "Enable rtc wakeup source\n");
writefile(
"/sys/devices/platform/soc/1f00000.rtc/power/wakeup",
"enabled"
);
//set RTC wake
if (wakeinterval > 0) setup_rtc_wakeup();
// E.g. make sure we're using CRUST
fprintf(stderr, "Flip mem_sleep setting to use crust\n");
writefile("/sys/power/mem_sleep", "deep");
}
void
die(const char *err, ...)
{
fprintf(stderr, "Screenlock error: %s\n", err);
state = StateDead;
syncstate();
exit(1);
}
void
sigterm()
{
state = StateDead;
syncstate();
if (wakeinterval) close(rtc_fd);
fprintf(stderr, "Screenlock terminating on signal\n");
exit(0);
}
void
getoldbrightness() {
char * buffer = 0;
long length;
FILE * f = fopen(brightnessfile, "r");
if (f) {
fseek(f, 0, SEEK_END);
length = ftell(f);
fseek(f, 0, SEEK_SET);
buffer = malloc(length);
if (buffer) {
fread(buffer, 1, length, f);
}
fclose(f);
}
if (buffer) {
sprintf(oldbrightness, "%d", atoi(buffer));
}
}
void
lockscreen(Display *dpy, int screen, int blank)
{
// Loosely derived from suckless' slock's lockscreen binding logic but
// alot more coarse, intentionally so can be triggered while grab_key
// for dwm multikey path already holding..
int i, ptgrab, kbgrab;
root = RootWindow(dpy, screen);
if (blank == 1) {
blankscreen();
}
for (i = 0, ptgrab = kbgrab = -1; i < 9999999; i++) {
if (ptgrab != GrabSuccess) {
ptgrab = XGrabPointer(dpy, root, False,
ButtonPressMask | ButtonReleaseMask |
PointerMotionMask, GrabModeAsync,
GrabModeAsync, None, None, CurrentTime);
}
if (kbgrab != GrabSuccess) {
kbgrab = XGrabKeyboard(dpy, root, True,
GrabModeAsync, GrabModeAsync, CurrentTime);
}
if (ptgrab == GrabSuccess && kbgrab == GrabSuccess) {
XSelectInput(dpy, root, SubstructureNotifyMask);
return;
}
usleep(100000);
}
}
void
blankscreen()
{
if (!blanked) {
system("xset dpms force off");
blanked = 1;
}
}
void
unblankscreen()
{
if (blanked) {
system("xset dpms force on");
blanked = 0;
}
}
void
readinputloop(Display *dpy, int screen) {
KeySym keysym;
XEvent ev;
char buf[32];
fd_set fdset;
int xfd;
int selectresult;
struct timeval xeventtimeout = {1, 0};
xfd = ConnectionNumber(dpy);
for (;;) {
FD_ZERO(&fdset);
FD_SET(xfd, &fdset);
if (state == StateSuspendPending)
selectresult = select(FD_SETSIZE, &fdset, NULL, NULL, &xeventtimeout);
else
selectresult = select(FD_SETSIZE, &fdset, NULL, NULL, NULL);
if (FD_ISSET(xfd, &fdset) && XPending(dpy)) {
XNextEvent(dpy, &ev);
if (ev.type == KeyRelease) {
XLookupString(&ev.xkey, buf, sizeof(buf), &keysym, 0);
if (lastkeysym == keysym) {
lastkeyn++;
} else {
lastkeysym = keysym;
lastkeyn = 1;
}
if (lastkeyn < 3)
continue;
lastkeyn = 0;
lastkeysym = XK_Cancel;
if (slept) postwake();
switch (keysym) {
case XF86XK_AudioRaiseVolume:
suspendpendingsceenon = state == StateNoInput;
suspendpendingtimeouts = 0;
state = StateSuspend;
break;
case XF86XK_AudioLowerVolume:
if (state == StateNoInput) state = StateNoInputNoScreen;
else if (state == StateNoInputNoScreen) state = StateNoInput;
else if (state == StateSuspendPending && suspendpendingsceenon) state = StateNoInputNoScreen;
else state = StateNoInput;
break;
case XF86XK_PowerOff:
waketime = 0;
state = StateDead;
break;
}
syncstate();
}
} else if (state == StateSuspendPending) {
suspendpendingtimeouts++;
// # E.g. after suspendtimeouts seconds kick back into suspend
if (suspendpendingtimeouts > suspendtimeouts) state = StateSuspend;
syncstate();
}
if (state == StateDead) break;
}
}
void
setpineled(enum Color c)
{
if (c == Red) {
writefile("/sys/class/leds/red:indicator/brightness", "1");
writefile("/sys/class/leds/blue:indicator/brightness", "0");
} else if (c == Blue) {
writefile("/sys/class/leds/red:indicator/brightness", "0");
writefile("/sys/class/leds/blue:indicator/brightness", "1");
} else if (c == Purple) {
writefile("/sys/class/leds/red:indicator/brightness", "1");
writefile("/sys/class/leds/blue:indicator/brightness", "1");
} else if (c == Off) {
writefile("/sys/class/leds/red:indicator/brightness", "0");
writefile("/sys/class/leds/blue:indicator/brightness", "0");
}
}
int
presuspend() {
//called prior to suspension, a non-zero return value cancels suspension
return system("sxmo_presuspend.sh || true");
}
void
postwake() {
//called after fully waking up (not used for temporary rtc wakeups)
system("sxmo_postwake.sh");
slept = 0;
}
int
checkrtcwake()
{
struct rtc_time now;
if (ioctl(rtc_fd, RTC_RD_TIME, &now) < 0) {
fprintf(stderr, "Error reading rtc time\n");
return -1;
}
const long int timediff = convert_rtc_time(&now) - waketime;
fprintf(stderr, "Checking rtc wake? timediff=%ld\n", timediff);
if (timediff >= 0 && timediff <= 3) {
fprintf(stderr, "Calling RTC wake script\n");
setpineled(Blue);
return system("sxmo_rtcwake.sh");
}
return 0;
}
void
syncstate()
{
int rtcresult;
if (state == StateSuspend) {
if (presuspend() != 0) {
state = StateDead;
} else {
fprintf(stderr, "Screenlock entering suspend state (pred mode)\n");
writefile(brightnessfile, "0");
blankscreen();
slept = 1;
setpineled(Red);
configuresuspendsettingsandwakeupsources();
writefile(powerstatefile, "mem");
//---- program blocks here due to sleep ----- //
// Just woke up again
fprintf(stderr, "Screenlock woke up\n");
fprintf(stderr, "Resetting usb connection to the modem\n");
writefile("/sys/bus/usb/drivers/usb/unbind", "3-1");
writefile("/sys/bus/usb/drivers/usb/bind", "3-1");
fprintf(stderr, "Lower scan interval for quicker reconnection to wireless network\n");
writefile("/sys/module/8723cs/parameters/rtw_scan_interval_thr", "1200"); //ms
//^-- this will be undone again by a networkmanager hook after connection has been established
// or by a delayed script if no connection can be established after a while (to conserve battery)
if (waketime > 0) {
rtcresult = checkrtcwake();
} else {
rtcresult = 0;
}
//if (rtcresult == 0) {
// state = StateSuspendPending;
// suspendpendingtimeouts = 0;
//} else {
postwake();
state = StateDead;
//}
}
syncstate();
} else if (state == StateNoInput) {
fprintf(stderr, "Screenlock in no Input state (blue mode)\n");
setpineled(Blue);
unblankscreen();
writefile(brightnessfile, oldbrightness);
} else if (state == StateNoInputNoScreen) {
fprintf(stderr, "Screenlock in no screen state (purple mode)\n");
setpineled(Purple);
writefile(brightnessfile, "0");
blankscreen();
} else if (state == StateSuspendPending) {
fprintf(stderr, "Screenlock is pending suspension\n");
if (suspendpendingsceenon) unblankscreen();
writefile(brightnessfile, suspendpendingsceenon ? oldbrightness : "0");
if (!suspendpendingsceenon) blankscreen();
setpineled(Off);
usleep(1000 * 100);
setpineled(suspendpendingsceenon ? Blue : Purple);
} else if (state == StateDead) {
unblankscreen();
writefile(brightnessfile, oldbrightness);
setpineled(Off);
}
}
void
writefile(char *filepath, char *str)
{
int f;
f = open(filepath, O_WRONLY);
if (f != -1) {
write(f, str, strlen(str));
close(f);
} else {
fprintf(stderr, "Couldn't open filepath <%s>\n", filepath);
}
}
void usage() {
fprintf(stderr, "Usage: sxmo_screenlock [--screen-off] [--suspend] [--wake-interval n] [--setuid]\n");
}
void init_rtc() {
rtc_fd = open(RTC_DEVICE, O_RDONLY);
if (rtc_fd < 0) {
die("Unable to open rtc device");
exit(EXIT_FAILURE);
}
}
int
main(int argc, char **argv) {
int screen;
int i;
enum State target = StateNoInput;
signal(SIGTERM, sigterm);
const char* suspendtimeouts_str = getenv("SXMO_SUSPENDTIMEOUTS");
if (suspendtimeouts_str != NULL) suspendtimeouts = atoi(suspendtimeouts_str);
const char* rtcwakeinterval = getenv("SXMO_RTCWAKEINTERVAL");
if (rtcwakeinterval != NULL) wakeinterval = atoi(rtcwakeinterval);
const char* screen_off = getenv("SXMO_LOCK_SCREEN_OFF");
if (screen_off != NULL && atoi(screen_off)) target = StateNoInputNoScreen;
const char* suspend = getenv("SXMO_LOCK_SUSPEND");
if (suspend != NULL && atoi(suspend)) target = StateSuspend;
//parse command line arguments
for (i = 1; i < argc; i++) {
if(!strcmp(argv[i], "-h")) {
usage();
return 0;
} else if(!strcmp(argv[i], "--screen-off")) {
target = StateNoInputNoScreen;
} else if(!strcmp(argv[i], "--suspend")) {
target = StateSuspend;
} else if(!strcmp(argv[i], "--wake-interval")) {
wakeinterval = (time_t) atoi(argv[++i]);
} else if(!strcmp(argv[i], "--setuid")) {
if (setuid(0))
die("setuid(0) failed");
} else {
fprintf(stderr, "Invalid argument: %s\n", argv[i]);
return 2;
}
}
if (!(dpy = XOpenDisplay(NULL)))
die("Cannot open display");
if (wakeinterval) init_rtc();
fprintf(stderr, "Screenlock starting\n");
XkbSetDetectableAutoRepeat(dpy, True, NULL);
screen = XDefaultScreen(dpy);
XSync(dpy, 0);
getoldbrightness();
syncstate();
lockscreen(dpy, screen, target == StateNoInputNoScreen || target == StateSuspend);
if ((target == StateNoInputNoScreen) || (target == StateSuspend)) {
state = StateNoInputNoScreen;
syncstate();
}
if (target == StateSuspend) {
state = StateSuspend;
syncstate();
}
readinputloop(dpy, screen);
if (wakeinterval) {
ioctl(rtc_fd, RTC_AIE_OFF, 0);
close(rtc_fd);
}
fprintf(stderr, "Screenlock terminating normally\n");
return 0;
}