commit 9186be2ac25c108341d4e886bc6f452c04dc4dc6 Author: Jonathan Hodgson Date: Tue Feb 23 22:12:48 2021 +0000 Initial commit - Copied from SXMO diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..28a8ff7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +screenlock diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ad77384 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +PREFIX = /usr/local + +.PHONY: install + +screenlock: screenlock.c + gcc -o screenlock screenlock.c -lX11 + +install: screenlock + mkdir -p $(PREFIX)/bin + cp -f screenlock $(PREFIX)/bin/screenlock + chmod 755 $(PREFIX)/bin/screenlock diff --git a/README.md b/README.md new file mode 100644 index 0000000..1ffbe45 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# Pinephone screenlock + +Currently, this is simply a carbon copy of the screenlock developed for sxmo + + + +This is MIT licensed. + +I created the repo because I expect to change some things in the future. I also +didn't want to download the whole sxmo utils package onto my pinephone diff --git a/screenlock.c b/screenlock.c new file mode 100644 index 0000000..0535a01 --- /dev/null +++ b/screenlock.c @@ -0,0 +1,550 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// 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, ...); +int 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 + +#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); +} + +int +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"); +} + +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; +}