fixed window layout
[instimg] / src / main.c
index 68b4c98..c2dce3b 100644 (file)
 #include <stdio.h>
 #include <stdlib.h>
+#include <ctype.h>
+#include <malloc.h>
 #include <windows.h>
+#include <winioctl.h>
 #include "widgets.h"
 #include "rawdisk.h"
 
+#define IMG_FNAME      "disk.img"
+
+#define WIN_XPAD       10
+#define WIN_YPAD       15
+
+static int instimg(const char *devpath);
+static void update_disks(void);
+static void onclick(struct wgt_widget *w);
+static void onmodify(struct wgt_widget *w);
+static void ck_usbonly_handler(struct wgt_widget *w);
+
+
 static struct wgt_window *win;
-static struct wgt_widget *lb_instto, *bn_inst, *bn_cancel, *cb_devs, *ck_usbonly;
+static struct wgt_widget *lb_instto, *bn_inst, *bn_cancel, *cb_devs, *ck_usbonly, *bn_cancel_inst;
+
 static struct rawdisk_device rawdev[64];
+static const char *items[64];
 static int num_rawdev;
 
-static void onclick(struct wgt_widget *w);
-static void onmodify(struct wgt_widget *w);
+static FILE *imgfile;
+static long imgfile_size;
+static const char *err_noimg_fmt =
+       "Failed to open 256boss disk image: " IMG_FNAME "\n"
+       "%s\n"
+       "\n"
+       "Please run this installer from the 256boss distribution directory";
+static const char *fail_err_str;
 
 int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hprevinst, char *cmdline, int showcmd)
 {
-       int i, x, y;
+       int x, y, width, height, max_width;
        MSG msg;
-       static const char *items[64];
+       char *msgbuf, *errmsg;
 
-       if((num_rawdev = rawdisk_detect(rawdev, sizeof rawdev / sizeof *rawdev)) == -1) {
+       if(!(imgfile = fopen(IMG_FNAME, "rb"))) {
+               errmsg = strerror(errno);
+               msgbuf = alloca(strlen(err_noimg_fmt) + strlen(errmsg) + 1);
+               sprintf(msgbuf, err_noimg_fmt, errmsg);
+               MessageBox(0, msgbuf, "Missing disk image!", MB_OK | MB_ICONSTOP | MB_APPLMODAL);
                return 1;
        }
-       for(i=0; i<num_rawdev; i++) {
-               items[i] = rawdev[i].name;
-       }
+       fseek(imgfile, 0, SEEK_END);
+       imgfile_size = ftell(imgfile);
 
-       if(!(win = wgt_window("256boss USB stick installer", 400, 300))) {
+       if(!(win = wgt_window("256boss USB stick installer", 420, 170))) {
+               fclose(imgfile);
                return 1;
        }
-       lb_instto = wgt_label(win, "Install to device:", 10, 10);
+
+       max_width = 0;
+       y = WIN_YPAD;
+       lb_instto = wgt_label(win, "Install to device:", WIN_XPAD, WIN_YPAD);
 
        x = wgt_xpos_after(lb_instto, WGT_AUTO);
-       cb_devs = wgt_combo(win, items, num_rawdev, 0, x, 10, WGT_AUTO, WGT_AUTO,
-                       onmodify);
+       cb_devs = wgt_combo(win, items, num_rawdev, 0, x, WIN_YPAD, 270, WGT_AUTO, onmodify);
+       if((width = wgt_xpos_after(cb_devs, WIN_XPAD)) > max_width) max_width = width;
 
        y = wgt_ypos_after(cb_devs, 16);
        ck_usbonly = wgt_checkbox(win, "only show USB devices", 1, x, y,
-                       WGT_AUTO, WGT_AUTO, 0);
+                       WGT_AUTO, WGT_AUTO, ck_usbonly_handler);
+       if((width = wgt_xpos_after(ck_usbonly, WIN_XPAD)) > max_width) max_width = width;
 
-       y = wgt_ypos_after(ck_usbonly, 16);
-       bn_inst = wgt_button(win, "Install", 10, y, WGT_AUTO, WGT_AUTO, onclick);
+       y = wgt_ypos_after(ck_usbonly, 32);
+       bn_inst = wgt_button(win, "Install", 70, y, 100, WGT_AUTO, onclick);
+       bn_cancel = wgt_button(win, "Cancel", 230, y, 100, WGT_AUTO, onclick);
 
-       x = wgt_xpos_after(bn_inst, WGT_AUTO);
-       bn_cancel = wgt_button(win, "Cancel", x, y, WGT_AUTO, WGT_AUTO, onclick);
+       height = wgt_ypos_after(bn_inst, WIN_YPAD);
+
+       wgt_resize_window(win, max_width, height);
+
+       update_disks();
 
        while(GetMessage(&msg, 0, 0, 0)) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
        }
 
-       wgt_destroy_window(win);
+       fclose(imgfile);
+       wgt_free_window(win);
+       return 0;
+}
+
+#define MIN_BS         (4 * 1024 * 1024)
+
+static int instimg(const char *devpath)
+{
+       MSG msg;
+       HANDLE hdev;
+       DWORD wrcount;
+       DISK_GEOMETRY geom;
+       int width, max_width, height, y, sz, blksz = 512, status = -1;
+       char *blkbuf;
+       struct wgt_window *win_inst;
+       struct wgt_widget *w, *pbar, *ptext;
+       struct wgt_rect *rect;
+       long num_blk_done, total_blk;
+       int progr;
+
+       if((hdev = CreateFile(devpath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
+                       0, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH, 0)) == INVALID_HANDLE_VALUE) {
+               DWORD err = GetLastError();
+               FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, err, 0,
+                               &fail_err_str, 0, 0);
+               return -1;
+       }
+
+       /*if(rawdisk_eject(hdev) == 0) {
+               rawdisk_load(hdev);
+       }*/
+
+       if(DeviceIoControl(hdev, IOCTL_DISK_GET_DRIVE_GEOMETRY, 0, 0, &geom, sizeof geom, &wrcount, 0)) {
+               blksz = geom.BytesPerSector;
+       }
+
+       while(blksz < MIN_BS) blksz <<= 1;
+
+       num_blk_done = 0;
+       total_blk = (imgfile_size + blksz - 1) / blksz;
+
+       if(!(blkbuf = malloc(blksz))) {
+               CloseHandle(hdev);
+               fail_err_str = "failed to allocate block buffer";
+               return -1;
+       }
+
+       max_width = 0;
+
+       win_inst = wgt_window("Installing ...", 320, 140);
+       wgt_quit_on_close(win_inst, 0);
+       
+       y = WIN_YPAD;
+       sprintf(blkbuf, "Device sector size: %d bytes", (int)geom.BytesPerSector);
+       w = wgt_label(win_inst, blkbuf, WIN_XPAD, y);
+       if((width = wgt_xpos_after(w, WIN_XPAD)) > max_width) max_width = width;
+
+       y = wgt_ypos_after(w, 8);
+       sprintf(blkbuf, "Copying in blocks of: %d bytes", blksz);
+       w = wgt_label(win_inst, blkbuf, WIN_XPAD, y);
+       if((width = wgt_xpos_after(w, WIN_XPAD)) > max_width) max_width = width;
+       
+       y = wgt_ypos_after(w, 8);
+       sprintf(blkbuf, "Image size: %ld bytes (%ld blocks)", imgfile_size, total_blk);
+       w = wgt_label(win_inst, blkbuf, WIN_XPAD, y);
+       if((width = wgt_xpos_after(w, WIN_XPAD)) > max_width) max_width = width;
+       
+       y = wgt_ypos_after(w, 8);
+       ptext = wgt_label(win_inst, "-/- (0%)", WIN_XPAD, y);
+       
+       y = wgt_ypos_after(ptext, 8);
+       pbar = wgt_progbar(win_inst, WIN_XPAD, y, width - WIN_XPAD * 2, WGT_AUTO, 0);
+       if((width = wgt_xpos_after(pbar, WIN_XPAD)) > max_width) max_width = width;
+
+       y = wgt_ypos_after(pbar, 8);
+       bn_cancel_inst = wgt_button(win_inst, "Cancel", 100, y, WGT_AUTO, WGT_AUTO, onclick);
+       rect = wgt_widget_rect(bn_cancel_inst, 0);
+       wgt_move_widget(bn_cancel_inst, WIN_XPAD + (width - rect->width) / 2, rect->y);
+
+       height = wgt_ypos_after(bn_cancel_inst, WIN_YPAD);
+
+       wgt_resize_window(win_inst, max_width, height);
+
+       /* go through any pending messages once first, to allow the window to get drawn correctly */
+       while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
+               if(msg.message == WM_CLOSE || msg.message == WM_DESTROY) {
+                       goto end;
+               }
+               TranslateMessage(&msg);
+               DispatchMessage(&msg);
+       }
+
+       rewind(imgfile);
+       while((sz = fread(blkbuf, 1, blksz, imgfile)) > 0) {
+               if(sz < blksz) {
+                       memset(blkbuf + sz, 0, blksz - sz);
+               }
+               SetFilePointer(hdev, num_blk_done * blksz, 0, FILE_BEGIN);
+               if(!WriteFile(hdev, blkbuf, blksz, &wrcount, 0)) {
+                       DWORD err = GetLastError();
+                       FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, err, 0,
+                                       &fail_err_str, 0, 0);
+                       goto end;
+               }
+
+               num_blk_done++;
+               progr = num_blk_done * 100 / total_blk;
+               sprintf(blkbuf, "%ld/%ld (%d%%)", num_blk_done, total_blk, progr);
+               wgt_set_text(ptext, blkbuf);
+               wgt_set_progress(pbar, progr);
+
+               while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
+                       if(msg.message == WM_CLOSE || msg.message == WM_DESTROY) {
+                               goto end;
+                       }
+                       TranslateMessage(&msg);
+                       DispatchMessage(&msg);
+               }
+       }
+
+       rawdisk_refresh(hdev);
+
+       status = 0;
+end:
+       wgt_free_window(win_inst);
+
+       free(blkbuf);
+       CloseHandle(hdev);
+       return status;
+}
+
+static int isusbdev(struct rawdisk_device *dev)
+{
+       char *ptr = dev->path;
+       while(*ptr) {
+               int i;
+               char buf[3];
+               for(i=0; i<3; i++) {
+                       buf[i] = tolower(ptr[i]);
+               }
+               if(memcmp(buf, "usb", 3) == 0) {
+                       return 1;
+               }
+               ptr++;
+       }
        return 0;
 }
 
+static void update_disks(void)
+{
+       int i;
+
+       for(i=0; i<num_rawdev; i++) {
+               free(rawdev[i].name);
+               free(rawdev[i].path);
+       }
+
+       if((num_rawdev = rawdisk_detect(rawdev, sizeof rawdev / sizeof *rawdev)) == -1) {
+               MessageBox(0, "Failed to detect storage devices!", 0, MB_OK);
+               exit(1);
+       }
+
+       if(wgt_checkbox_checked(ck_usbonly)) {
+               i = 0;
+               while(i < num_rawdev) {
+                       if(!isusbdev(rawdev + i)) {
+                               free(rawdev[i].name);
+                               free(rawdev[i].path);
+                               rawdev[i] = rawdev[--num_rawdev];
+                       } else {
+                               i++;
+                       }
+               }
+       }
+
+       for(i=0; i<num_rawdev; i++) {
+               items[i] = rawdev[i].name;
+       }
+
+       wgt_combo_setitems(cb_devs, items, num_rawdev);
+
+       if(num_rawdev) {
+               wgt_enable_widget(cb_devs);
+               wgt_enable_widget(bn_inst);
+       } else {
+               wgt_disable_widget(cb_devs);
+               wgt_disable_widget(bn_inst);
+       }
+}
+
+static const char *verify_fmt = \
+       "You are about to install 256boss to: %s\n"
+       "\n"
+       "Device path:\n"
+       "%s\n"
+       "\n"
+       "Any existing data in the selected device will be lost!\n"
+       "Please verify you have selected the intended device.\n"
+       "\n"
+       "Are you certain you wish to proceed with the installation?\n";
+
+static const char *success_str = \
+       "Installation complete!\n"
+       "\n"
+       "If the 256boss partition doesn't show up immediately,\n"
+       "please eject and re-attach the device\n";
+
+static const char *fail_fmt = \
+       "Installation failed!\n"
+       "Error: %s\n";
+
 static void onclick(struct wgt_widget *w)
 {
-       MessageBox(0, "clicked", "clicked", MB_OK);
+       int sel, len;
+       unsigned int flags;
+       char *msgbuf;
+       static int inst_running;
+
+       if(w == bn_inst) {
+               if(inst_running) return;
+
+               sel = wgt_combo_selected(cb_devs);
+               if(sel < 0 || sel >= num_rawdev) {
+                       return;
+               }
+
+               len = strlen(verify_fmt) + strlen(rawdev[sel].name) + strlen(rawdev[sel].path);
+               msgbuf = alloca(len + 1);
+               sprintf(msgbuf, verify_fmt, rawdev[sel].name, rawdev[sel].path);
+
+               flags = MB_YESNO | MB_ICONWARNING | MB_APPLMODAL | MB_SETFOREGROUND;
+               if(MessageBox(0, msgbuf, "Proceed with installation?", flags) == IDYES) {
+                       inst_running = 1;
+                       if(instimg(rawdev[sel].path) == -1) {
+                               int prev_len = len;
+                               len = strlen(fail_fmt) + (fail_err_str ? strlen(fail_err_str) : strlen("unknown"));
+                               if(len > prev_len) {
+                                       alloca(len - prev_len);
+                               }
+                               sprintf(msgbuf, fail_fmt, fail_err_str ? fail_err_str : "unknown");
+                               MessageBox(0, msgbuf, 0, MB_OK | MB_ICONSTOP | MB_APPLMODAL | MB_SETFOREGROUND);
+                       } else {
+                               MessageBox(0, success_str, "Done", MB_OK | MB_ICONASTERISK | MB_APPLMODAL | MB_SETFOREGROUND);
+                       }
+                       inst_running = 0;
+               }
+
+       } else if(w == bn_cancel) {
+               PostQuitMessage(0);
+
+       } else if(w == bn_cancel_inst) {
+               struct wgt_window *win = wgt_widget_window(w);
+               PostMessage(wgt_window_handle(win), WM_CLOSE, 0, 0);
+       }
 }
 
 static void onmodify(struct wgt_widget *w)
@@ -65,4 +348,15 @@ static void onmodify(struct wgt_widget *w)
                const char *selstr = wgt_get_combo_item(w, sel);
                MessageBox(0, selstr, "selected item", MB_OK);
        }
+}
+
+static void ck_usbonly_handler(struct wgt_widget *w)
+{
+       update_disks();
+}
+
+static int close_action(struct wgt_window *w)
+{
+       if(w != win) return 0;
+       return 1;
 }
\ No newline at end of file