~kennylevinsen/gtkgreet

bae52043cb3ea53298451592091d4dd19ceae38f — Kenny Levinsen 4 years ago 89e2f5d new-proto
WIP
4 files changed, 316 insertions(+), 126 deletions(-)

M proto.c
M proto.h
M window.c
M window.h
M proto.c => proto.c +188 -22
@@ 8,6 8,9 @@
#include <stdio.h>
#include <errno.h>

#include "proto.h"
#include "window.h"

struct header {
    uint32_t magic;
    uint32_t version;


@@ 16,19 19,14 @@ struct header {

static int write_req(int fd, struct json_object* req) {
    const char* reqstr = json_object_get_string(req);
    struct header header = {
        .magic = 0xAFBFCFDF,
        .version = 1,
        .payload_len = strlen(reqstr),
    };

    if (write(fd, &header, sizeof(struct header)) != sizeof(struct header)) {
    uint32_t len = strlen(reqstr);
    if (write(fd, &len, 4) != 4) {
        return -1;
    }

    ssize_t off = 0;
    while (off < header.payload_len) {
        ssize_t n = write(fd, &reqstr[off], header.payload_len-off);
    while (off < len) {
        ssize_t n = write(fd, &reqstr[off], len-off);
        if (n < 0) {
            return -1;
        }


@@ 39,30 37,24 @@ static int write_req(int fd, struct json_object* req) {
}

static struct json_object* read_resp(int fd) {
    struct header header = {};
    struct json_object* resp = NULL;
    char *respstr = NULL;
    uint32_t len;
    ssize_t off = 0;

    while (off < sizeof(struct header)) {
        char* headerp = (char*)&header;
        ssize_t n = read(fd, &headerp[off], sizeof(struct header)-off);
    while (off < 4) {
        char* headerp = (char*)&len;
        ssize_t n = read(fd, &headerp[off], 4-off);
        if (n < 0) {
            goto end;
        }
        off += n;
    }


    if (header.magic != 0xAFBFCFDF ||
        header.version != 1) {
        goto end;
    }

    off = 0;
    respstr = (char*)calloc(1, header.payload_len+1);
    while (off < header.payload_len) {
        int n = read(fd, &respstr[off], header.payload_len-off);
    respstr = (char*)calloc(1,len+1);
    while (off <len) {
        int n = read(fd, &respstr[off],len-off);
        if (n < 0) {
            goto end;
        }


@@ 130,6 122,180 @@ end:
    return resp;
}

struct proto_status send_create_session(const char *username) {
    struct json_object* req = json_object_new_object();
    json_object_object_add(req, "type", json_object_new_string("create_session"));
    json_object_object_add(req, "username", json_object_new_string(username));
    struct json_object* resp = roundtrip(req);

    struct proto_status ret;

    struct json_object* type = json_object_object_get(resp, "type");
    const char* typestr = json_object_get_string(type);
    if (typestr == NULL || strcmp(typestr, "error") == 0) {
        ret.success = 0;
        goto done;
    }

    if (strcmp(typestr, "success") == 0) {
        ret.success = 1;
        ret.has_questions = 0;
        goto done;
    }

    if (strcmp(typestr, "auth_question") == 0) {
        ret.success = 1;
        ret.has_questions = 1;
        struct json_object *style = json_object_object_get(resp, "style");
        const char* stylestr = json_object_get_string(style);
        if (strcmp(stylestr, "visible") == 0) {
            ret.question_type = QuestionTypeVisible;
        } else if (strcmp(stylestr, "secret") == 0) {
            ret.question_type = QuestionTypeSecret;
        } else if (strcmp(stylestr, "info") == 0) {
            ret.question_type = QuestionTypeInfo;
        } else if (strcmp(stylestr, "error") == 0) {
            ret.question_type = QuestionTypeError;
        } else {
            ret.success = 0;
            goto done;
        }

        struct json_object *question = json_object_object_get(resp, "question");
        const char* questionstr = json_object_get_string(question);
        strncpy(ret.question, questionstr, 63);
        goto done;
    }

    ret.success = 0;

done:
    json_object_put(resp);
    return ret;
}

struct proto_status send_auth_answer(const char *answer) {
    struct json_object* req = json_object_new_object();
    json_object_object_add(req, "type", json_object_new_string("answer_auth_question"));
    json_object_object_add(req, "answer", json_object_new_string(answer));
    struct json_object* resp = roundtrip(req);

    struct proto_status ret;

    struct json_object* type = json_object_object_get(resp, "type");
    const char* typestr = json_object_get_string(type);
    if (typestr == NULL || strcmp(typestr, "error") == 0) {
        ret.success = 0;
        goto done;
    }

    if (strcmp(typestr, "success") == 0) {
        ret.success = 1;
        ret.has_questions = 0;
        goto done;
    }

    if (strcmp(typestr, "auth_question") == 0) {
        ret.success = 1;
        ret.has_questions = 1;
        struct json_object *style = json_object_object_get(resp, "style");
        const char* stylestr = json_object_get_string(style);
        if (strcmp(stylestr, "visible") == 0) {
            ret.question_type = QuestionTypeVisible;
        } else if (strcmp(stylestr, "secret") == 0) {
            ret.question_type = QuestionTypeSecret;
        } else if (strcmp(stylestr, "info") == 0) {
            ret.question_type = QuestionTypeInfo;
        } else if (strcmp(stylestr, "error") == 0) {
            ret.question_type = QuestionTypeError;
        } else {
            ret.success = 0;
            goto done;
        }

        struct json_object *question = json_object_object_get(resp, "question");
        const char* questionstr = json_object_get_string(question);
        strncpy(ret.question, questionstr, 63);
        goto done;
    }

    ret.success = 0;

done:
    json_object_put(resp);
    return ret;
}


struct proto_status send_start_session(const char *command) {
    struct json_object* req = json_object_new_object();
    json_object_object_add(req, "type", json_object_new_string("start_session"));

    struct json_object* cmd = json_object_new_array();
    json_object_array_add(cmd, json_object_new_string(command));
    json_object_object_add(req, "command", cmd);

    struct json_object* env = json_object_new_array();

    char buf[128];
    snprintf(buf, 128, "XDG_SESSION_DESKTOP=%s", command);
    json_object_array_add(env, json_object_new_string(buf));
    snprintf(buf, 128, "XDG_CURRENT_DESKTOP=%s", command);
    json_object_array_add(env, json_object_new_string(buf));

    json_object_object_add(req, "env", env);

    struct json_object* resp = roundtrip(req);

    struct proto_status ret;

    struct json_object* type = json_object_object_get(resp, "type");
    const char* typestr = json_object_get_string(type);
    if (typestr == NULL || strcmp(typestr, "error") == 0) {
        ret.success = 0;
        goto done;
    }

    if (strcmp(typestr, "success") == 0) {
        ret.success = 1;
        ret.has_questions = 0;
        goto done;
    }

    ret.success = 0;

done:
    json_object_put(resp);
    return ret;
}

struct proto_status send_cancel_session() {
    struct json_object* req = json_object_new_object();
    json_object_object_add(req, "type", json_object_new_string("cancel_session"));
    struct json_object* resp = roundtrip(req);

    struct proto_status ret;

    struct json_object* type = json_object_object_get(resp, "type");
    const char* typestr = json_object_get_string(type);
    if (typestr == NULL || strcmp(typestr, "error") == 0) {
        ret.success = 0;
        goto done;
    }

    if (strcmp(typestr, "success") == 0) {
        ret.success = 1;
        ret.has_questions = 0;
        goto done;
    }

    ret.success = 0;

done:
    json_object_put(resp);
    return ret;
}

int send_login(const char *username, const char * password, const char *command) {
    struct json_object* login_req = json_object_new_object();
    json_object_object_add(login_req, "type", json_object_new_string("login"));

M proto.h => proto.h +14 -0
@@ 1,6 1,20 @@
#ifndef _PROTO_H
#define _PROTO_H

#include "window.h"

struct proto_status {
    int success;
    int has_questions;
    enum QuestionType question_type;
    char question[64];
};

struct proto_status send_create_session(const char *username);
struct proto_status send_start_session(const char *username);
struct proto_status send_auth_answer(const char *answer);
struct proto_status send_cancel_session();

int send_login(const char *username, const char * password, const char *command);
int send_shutdown(const char *action);


M window.c => window.c +102 -101
@@ 57,84 57,120 @@ static gboolean draw_clock(gpointer data) {
    return TRUE;
}

static void login_action(GtkWidget *widget, gpointer data) {
    struct Window *ctx = (struct Window*)data;
    gtk_label_set_markup((GtkLabel*)ctx->info_label, "<span>Logging in</span>");

    const char *username = gtk_entry_get_text((GtkEntry*)ctx->username_entry);
    const char *password = gtk_entry_get_text((GtkEntry*)ctx->password_entry);
    gchar* selection = gtk_combo_box_text_get_active_text((GtkComboBoxText*)ctx->target_combo_box);
static void setup_question(struct Window *ctx, enum QuestionType type, char* question);

    if (send_login(username, password, selection)) {
        exit(0);
static void answer_question(GtkWidget *widget, gpointer data) {
    struct Window *ctx = (struct Window*)data;
    struct proto_status ret;
    switch (ctx->question_type) {
        case QuestionTypeInitial: {
            ret = send_create_session(gtk_entry_get_text((GtkEntry*)widget));
            break;
        }
        case QuestionTypeSecret:
        case QuestionTypeVisible: {
            ret = send_auth_answer(gtk_entry_get_text((GtkEntry*)widget));
            break;
        }
        case QuestionTypeInfo:
        case QuestionTypeError: {
            ret = send_auth_answer(NULL);
            break;
        }
    }
    if (ret.success) {
        if (ret.has_questions) {
            setup_question(ctx, ret.question_type, ret.question);
        } else {
            ret = send_start_session("sway");
            if (ret.success) {
                exit(0);
            } else {
                setup_question(ctx, QuestionTypeInitial, "Username:");
            }
        }
    } else {
        gtk_label_set_markup((GtkLabel*)ctx->info_label, "<span color=\"darkred\">Login failed</span>");
        setup_question(ctx, QuestionTypeInitial, "Username:");
    }

    g_free(selection);
}

static void shutdown_action(struct Window *ctx, char *action) {
    gtk_label_set_markup((GtkLabel*)ctx->info_label, "<span>Executing shutdown action</span>");
    if (send_shutdown(action)) {
        exit(0);
    } else {
        gtk_label_set_markup((GtkLabel*)ctx->info_label, "<span color=\"darkred\">Exit action failed</span>");
static void cancel_initial_action(GtkWidget *widget, gpointer data) {
    exit(0);
}
static void cancel_question_action(GtkWidget *widget, gpointer data) {
    struct Window *ctx = (struct Window*)data;
    struct proto_status ret = send_cancel_session();
    if (!ret.success) {
        exit(1);
    }

    setup_question(ctx, QuestionTypeInitial, "Username:");
}

static void window_create_shutdown_prompt(GtkWidget *widget, gpointer data) {
    struct Window *ctx = (struct Window*)data;
    GtkWidget *dialog = gtk_message_dialog_new(
        GTK_WINDOW(ctx->window),
        GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
        GTK_MESSAGE_QUESTION,
        GTK_BUTTONS_NONE,
        "What do you want to do?");

    gtk_dialog_add_button((GtkDialog*)dialog, "Cancel", GTK_RESPONSE_REJECT);
    gtk_dialog_add_button((GtkDialog*)dialog, "Quit", 1);
    GtkWidget *reboot_button = gtk_dialog_add_button((GtkDialog*)dialog, "Reboot", 2);
    gtk_style_context_add_class(gtk_widget_get_style_context(reboot_button), "destructive-action");
    GtkWidget *poweroff_button = gtk_dialog_add_button((GtkDialog*)dialog, "Power off", 3);
    gtk_style_context_add_class(gtk_widget_get_style_context(poweroff_button), "destructive-action");


    switch (gtk_dialog_run((GtkDialog*)dialog)) {
        case 1:
            exit(0);
            break;
        case 2:
            shutdown_action(ctx, "reboot");
static void setup_question(struct Window *ctx, enum QuestionType type, char* question) {
    if (ctx->input != NULL) {
        gtk_widget_destroy(ctx->input);
        ctx->input = NULL;
    }
    ctx->input = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);

    switch (type) {
        case QuestionTypeInitial:
        case QuestionTypeVisible: {
            GtkWidget *label = gtk_label_new(question);
            gtk_widget_set_halign(label, GTK_ALIGN_START);
            gtk_container_add(GTK_CONTAINER(ctx->input), label);

            GtkWidget *widget = gtk_entry_new();
            // gtk_entry_set_placeholder_text((GtkEntry*)widget, question);
            g_signal_connect(widget, "activate", G_CALLBACK(answer_question), ctx);
            gtk_container_add(GTK_CONTAINER(ctx->input), widget);
            break;
        case 3:
            shutdown_action(ctx, "poweroff");
        }
        case QuestionTypeSecret: {
            GtkWidget *label = gtk_label_new(question);
            gtk_widget_set_halign(label, GTK_ALIGN_START);
            gtk_container_add(GTK_CONTAINER(ctx->input), label);

            GtkWidget *widget = gtk_entry_new();
            // gtk_entry_set_placeholder_text((GtkEntry*)widget, question);
            gtk_entry_set_input_purpose((GtkEntry*)widget, GTK_INPUT_PURPOSE_PASSWORD);
            gtk_entry_set_visibility((GtkEntry*)widget, FALSE);
            g_signal_connect(widget, "activate", G_CALLBACK(answer_question), ctx);
            gtk_container_add(GTK_CONTAINER(ctx->input), widget);
            break;
        default:
        }
        case QuestionTypeInfo:
        case QuestionTypeError:
            break;
    }
    gtk_widget_destroy(dialog);
}

static int update_env_combobox(GtkWidget *combobox) {
    char buffer[255];
    FILE *fp = fopen("/etc/greetd/environments", "r");
    if (fp == NULL) {
        return 0;
    }
    gtk_container_add(GTK_CONTAINER(ctx->input_box), ctx->input);

    int entries = 0;
    while(fgets(buffer, 255, (FILE*) fp)) {
        size_t len = strnlen(buffer, 255);
        if (len > 0 && len < 255 && buffer[len-1] == '\n') {
            buffer[len-1] = '\0';

    GtkWidget *bottom_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
    gtk_widget_set_halign(bottom_box, GTK_ALIGN_END);
    gtk_container_add(GTK_CONTAINER(ctx->input), bottom_box);

    GtkWidget *button = gtk_button_new_with_label("Cancel");
    switch (type) {
        case QuestionTypeInitial: {
            g_signal_connect(button, "clicked", G_CALLBACK(cancel_initial_action), ctx);
            break;
        }
        case QuestionTypeVisible:
        case QuestionTypeSecret:
        case QuestionTypeInfo:
        case QuestionTypeError: {
            g_signal_connect(button, "clicked", G_CALLBACK(cancel_question_action), ctx);
            break;
        }
        gtk_combo_box_text_append((GtkComboBoxText*)combobox, NULL, buffer);
        entries++;
    }

    fclose(fp);
    return entries;
    gtk_widget_set_halign(button, GTK_ALIGN_END);
    gtk_container_add(GTK_CONTAINER(bottom_box), button);

    gtk_widget_show_all(ctx->window);
}

static void window_setup(struct Window *ctx) {


@@ 157,47 193,12 @@ static void window_setup(struct Window *ctx) {
    g_timeout_add(5000, draw_clock, ctx);
    draw_clock(ctx);

    GtkWidget *input_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
    gtk_widget_set_halign(input_box, GTK_ALIGN_CENTER);
    gtk_widget_set_size_request(input_box, 512, -1);
    gtk_container_add(GTK_CONTAINER(window_box), input_box);

    ctx->username_entry = gtk_entry_new();
    gtk_entry_set_placeholder_text((GtkEntry*)ctx->username_entry, "Username");
    gtk_container_add(GTK_CONTAINER(input_box), ctx->username_entry);

    ctx->password_entry = gtk_entry_new();
    g_signal_connect(ctx->password_entry, "activate", G_CALLBACK(login_action), ctx);
    gtk_entry_set_placeholder_text((GtkEntry*)ctx->password_entry, "Password");
    gtk_entry_set_input_purpose((GtkEntry*)ctx->password_entry, GTK_INPUT_PURPOSE_PASSWORD);
    gtk_entry_set_visibility((GtkEntry*)ctx->password_entry, FALSE);
    gtk_container_add(GTK_CONTAINER(input_box), ctx->password_entry);

    ctx->target_combo_box = gtk_combo_box_text_new_with_entry();
    update_env_combobox(ctx->target_combo_box);
    GtkWidget *combo_box_entry = gtk_bin_get_child((GtkBin*)ctx->target_combo_box);
    gtk_entry_set_placeholder_text((GtkEntry*)combo_box_entry, "Command to run");
    gtk_combo_box_set_active((GtkComboBox*)ctx->target_combo_box, 0);
    gtk_container_add(GTK_CONTAINER(input_box), ctx->target_combo_box);

    GtkWidget *bottom_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
    gtk_widget_set_halign(bottom_box, GTK_ALIGN_END);
    gtk_container_add(GTK_CONTAINER(input_box), bottom_box);

    ctx->info_label = gtk_label_new("");
    gtk_widget_set_halign(ctx->info_label, GTK_ALIGN_START);
    g_object_set(ctx->info_label, "margin-right", 10, NULL);
    gtk_container_add(GTK_CONTAINER(bottom_box), ctx->info_label);

    GtkWidget *shutdown_button = gtk_button_new_with_label("Shutdown");
    g_signal_connect(shutdown_button, "clicked", G_CALLBACK(window_create_shutdown_prompt), ctx);
    gtk_widget_set_halign(shutdown_button, GTK_ALIGN_END);
    gtk_container_add(GTK_CONTAINER(bottom_box), shutdown_button);
    ctx->input_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
    gtk_widget_set_halign(ctx->input_box, GTK_ALIGN_CENTER);
    gtk_widget_set_size_request(ctx->input_box, 512, -1);
    gtk_container_add(GTK_CONTAINER(window_box), ctx->input_box);

    GtkWidget *button = gtk_button_new_with_label("Login");
    g_signal_connect(button, "clicked", G_CALLBACK(login_action), ctx);
    gtk_widget_set_halign(button, GTK_ALIGN_END);
    gtk_container_add(GTK_CONTAINER(bottom_box), button);
    setup_question(ctx, QuestionTypeInitial, "Username:");

    gtk_widget_show_all(ctx->window);
}

M window.h => window.h +12 -3
@@ 3,15 3,24 @@

#include <gtk/gtk.h>

enum QuestionType {
	QuestionTypeInitial = 0,
	QuestionTypeVisible = 1,
	QuestionTypeSecret = 2,
	QuestionTypeInfo = 3,
	QuestionTypeError = 4,
};

struct Window {
    GdkMonitor *monitor;

    GtkWidget *window;
    GtkWidget *username_entry;
    GtkWidget *password_entry;
    GtkWidget *input_box;
    GtkWidget *input;
    GtkWidget *info_label;
    GtkWidget *clock_label;
    GtkWidget *target_combo_box;

    enum QuestionType question_type;
};

void create_window(GdkMonitor *monitor);