Ported live CD wizard to Python

This commit is contained in:
Kevin Lange 2017-01-15 00:45:05 +09:00
parent 439ea94431
commit 446f7934c8
3 changed files with 242 additions and 365 deletions

View File

@ -27,7 +27,7 @@ int main(int argc, char * argv[]) {
setuid(1000);
toaru_auth_set_vars();
char * args[] = {"/bin/live-wizard", NULL};
char * args[] = {"/bin/wizard.py", NULL};
execvp(args[0], args);
TRACE("wizard start failed?");
}

View File

@ -1,364 +0,0 @@
/* vim: tabstop=4 shiftwidth=4 noexpandtab
* This file is part of ToaruOS and is released under the terms
* of the NCSA / University of Illinois License - see LICENSE.md
* Copyright (C) 2015 Kevin Lange
*
* Live CD Welcome Program
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <syscall.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include "lib/yutani.h"
#include "lib/graphics.h"
#include "lib/decorations.h"
#include "gui/ttk/ttk.h"
#include "lib/trace.h"
#define TRACE_APP_NAME "live-wizard"
#define WIZARD_WIDTH 640
#define WIZARD_HEIGHT 480
static yutani_t * yctx;
static yutani_window_t * win_hints;
static gfx_context_t * ctx_hints;
static yutani_window_t * win_wizard;
static gfx_context_t * ctx_wizard;
static cairo_surface_t * surface_hints;
static cairo_surface_t * surface_wizard;
static cairo_t * cr_hints;
static cairo_t * cr_wizard;
static int should_exit = 0;
static int current_frame = 0;
static int center_x(int x) {
return ((int)yctx->display_width - x) / 2;
}
static int center_y(int y) {
return ((int)yctx->display_height - y) / 2;
}
static int center_win_x(int x) {
return (win_wizard->width - x) / 2;
}
static int button_width = 100;
static int button_height = 32;
static int button_focused = 0;
static void draw_next_button(int is_exit) {
if (button_focused == 1) {
/* hover */
_ttk_draw_button_hover(cr_wizard, center_win_x(button_width), 400, button_width, button_height, is_exit ? "Exit" : "Next");
} else if (button_focused == 2) {
/* Down */
_ttk_draw_button_select(cr_wizard, center_win_x(button_width), 400, button_width, button_height, is_exit ? "Exit" : "Next");
} else {
/* Something else? */
_ttk_draw_button(cr_wizard, center_win_x(button_width), 400, button_width, button_height, is_exit ? "Exit" : "Next");
}
}
static void draw_centered_label(int y, int size, char * label) {
set_font_face(FONT_SANS_SERIF);
set_font_size(size);
int x = center_win_x(draw_string_width(label));
draw_string(ctx_wizard, x, y, rgb(0,0,0), label);
}
static char * LOGO = "/usr/share/logo_login.png";
static void draw_logo(void) {
sprite_t logo;
load_sprite_png(&logo, LOGO);
draw_sprite(ctx_wizard, &logo, center_win_x(logo.width), 50);
}
static void draw_arrow(int x, int y, int angle) {
cairo_save(cr_hints);
cairo_surface_t * arrow = cairo_image_surface_create_from_png("/usr/share/wizard-arrow.png");
int w, h;
w = cairo_image_surface_get_width(arrow);
h = cairo_image_surface_get_height(arrow);
cairo_translate(cr_hints, x, y);
cairo_rotate(cr_hints, (double)angle * M_PI / 179.0);
cairo_translate(cr_hints, -w, -h/2);
cairo_set_source_surface(cr_hints, arrow, 0, 0);
cairo_paint(cr_hints);
cairo_surface_destroy(arrow);
cairo_restore(cr_hints);
}
static void redraw(void) {
draw_fill(ctx_hints, premultiply(rgba(0,0,0,100)));
draw_fill(ctx_wizard, rgb(TTK_BACKGROUND_DEFAULT));
/* Draw the current tutorial frame */
render_decorations(win_wizard, ctx_wizard, "Welcome to とあるOS");
switch (current_frame) {
case 0:
/* Labels for Welcome to とあるOS! */
draw_logo();
draw_centered_label(100+70, 20, "Welcome to とあるOS!");
draw_centered_label(100+88, 12, "This tutorial will guide you through the features");
draw_centered_label(100+102,12, "of the operating system, as well as give you a feel");
draw_centered_label(100+116,12, "for the UI and design principles.");
draw_centered_label(100+180,12, "When you're ready to continue, press \"Next\".");
draw_centered_label(120+200,12, "https://github.com/klange/toaruos - http://toaruos.org");
draw_centered_label(120+220,12, "とあるOS is free software, released under the terms");
draw_centered_label(120+234,12, "of the NCSA/University of Illinois license.");
draw_next_button(0);
break;
case 1:
draw_logo();
draw_arrow(center_x(WIZARD_WIDTH) + 620, center_y(WIZARD_HEIGHT) - 5, 90);
draw_centered_label(100+70,12,"If you wish to exit the tutorial at any time, you can");
draw_centered_label(100+84,12,"click the × in the upper right corner of this window.");
draw_next_button(0);
break;
case 2:
draw_logo();
draw_centered_label(100+70, 12,"As a reminder, とあるOS is a hobby project with few developers.");
draw_centered_label(100+84, 12,"As such, do not expect things to work perfectly, or in some cases,");
draw_centered_label(100+98, 12,"at all, as the kernel and drivers are very much \"work-in-progress\".");
draw_next_button(0);
break;
case 3:
draw_arrow(110, 120, -135);
cairo_save(cr_hints);
cairo_set_operator(cr_hints, CAIRO_OPERATOR_SOURCE);
cairo_set_source_rgba(cr_hints, 0.0, 0.0, 0.0, 0.0);
cairo_translate(cr_hints, 70.5, 80.5);
cairo_arc(cr_hints, 0, 0, 50, 0, 2 * M_PI);
cairo_fill(cr_hints);
cairo_restore(cr_hints);
draw_centered_label(100+10, 12,"とあるOS aims to provide a Unix-like environment.");
draw_centered_label(100+24, 12,"You can find familiar command-line tools by opening a terminal.");
draw_centered_label(100+38, 12,"Application shortcuts on the desktop are opened with a single click.");
draw_centered_label(100+52, 12,"You can also find more graphical applications in the Applications menu.");
draw_next_button(0);
break;
case 4:
draw_logo();
draw_centered_label(100+70,12,"That's it for now!");
draw_centered_label(100+88,12,"You've finished the tutorial.");
draw_centered_label(100+102,12,"More guides will be added to this tutorial in the future, but that's");
draw_centered_label(100+116,12,"all for now. Press 'Exit' to close the tutorial.");
draw_next_button(1);
break;
default:
exit(0);
break;
}
flip(ctx_hints);
flip(ctx_wizard);
yutani_flip(yctx, win_hints);
yutani_flip(yctx, win_wizard);
}
static void do_click_callback(void) {
current_frame += 1;
redraw();
}
static int previous_buttons = 0;
static void do_mouse_stuff(struct yutani_msg_window_mouse_event * me) {
if (button_focused == 2) {
/* See if we released and are still inside. */
if (me->command == YUTANI_MOUSE_EVENT_RAISE || me->command == YUTANI_MOUSE_EVENT_CLICK) {
if (!(me->buttons & YUTANI_MOUSE_BUTTON_LEFT)) {
if (me->new_x > center_win_x(button_width)
&& me->new_x < center_win_x(button_width) + button_width
&& me->new_y > 400
&& me->new_y < 400 + button_height) {
button_focused = 1;
do_click_callback();
} else {
button_focused = 0;
redraw();
}
}
}
} else {
if (me->new_x > center_win_x(button_width)
&& me->new_x < center_win_x(button_width) + button_width
&& me->new_y > 400
&& me->new_y < 400 + button_height) {
if (!button_focused) {
button_focused = 1;
redraw();
}
if (me->command == YUTANI_MOUSE_EVENT_DOWN && (me->buttons & YUTANI_MOUSE_BUTTON_LEFT)) {
button_focused = 2;
redraw();
}
} else {
if (button_focused) {
button_focused = 0;
redraw();
}
}
}
previous_buttons = me->buttons;
}
static void resize_finish(int xwidth, int xheight) {
yutani_window_resize_accept(yctx, win_hints, xwidth, xheight);
cairo_destroy(cr_hints);
cairo_surface_destroy(surface_hints);
reinit_graphics_yutani(ctx_hints, win_hints);
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, win_hints->width);
surface_hints = cairo_image_surface_create_for_data(ctx_hints->backbuffer, CAIRO_FORMAT_ARGB32, win_hints->width, win_hints->height, stride);
cr_hints = cairo_create(surface_hints);
yutani_window_resize_done(yctx, win_hints);
/* Re-center the wizard */
yutani_window_move(yctx, win_wizard, center_x(WIZARD_WIDTH), center_y(WIZARD_HEIGHT));
redraw();
}
int main(int argc, char * argv[]) {
TRACE("Opening some windows...");
yctx = yutani_init();
init_decorations();
win_hints = yutani_window_create_flags(yctx, yctx->display_width, yctx->display_height,
YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS | YUTANI_WINDOW_FLAG_DISALLOW_DRAG |
YUTANI_WINDOW_FLAG_DISALLOW_RESIZE | YUTANI_WINDOW_FLAG_ALT_ANIMATION);
yutani_window_move(yctx, win_hints, 0, 0);
yutani_window_update_shape(yctx, win_hints, YUTANI_SHAPE_THRESHOLD_CLEAR);
ctx_hints = init_graphics_yutani_double_buffer(win_hints);
win_wizard = yutani_window_create_flags(yctx, WIZARD_WIDTH, WIZARD_HEIGHT,
YUTANI_WINDOW_FLAG_DISALLOW_DRAG | YUTANI_WINDOW_FLAG_DISALLOW_RESIZE);
yutani_window_move(yctx, win_wizard, center_x(WIZARD_WIDTH), center_y(WIZARD_HEIGHT));
ctx_wizard = init_graphics_yutani_double_buffer(win_wizard);
int stride;
stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, win_hints->width);
surface_hints = cairo_image_surface_create_for_data(ctx_hints->backbuffer, CAIRO_FORMAT_ARGB32, win_hints->width, win_hints->height, stride);
cr_hints = cairo_create(surface_hints);
stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, win_wizard->width);
surface_wizard = cairo_image_surface_create_for_data(ctx_wizard->backbuffer, CAIRO_FORMAT_ARGB32, win_wizard->width, win_wizard->height, stride);
cr_wizard = cairo_create(surface_wizard);
yutani_window_advertise_icon(yctx, win_wizard, "Welcome Tutorial", "live-welcome");
redraw();
yutani_focus_window(yctx, win_wizard->wid);
while (!should_exit) {
yutani_msg_t * m = yutani_poll(yctx);
if (m) {
switch (m->type) {
case YUTANI_MSG_KEY_EVENT:
{
struct yutani_msg_key_event * ke = (void*)m->data;
if (ke->event.key == 'q' && ke->event.action == KEY_ACTION_DOWN) {
should_exit = 1;
}
}
break;
case YUTANI_MSG_WINDOW_FOCUS_CHANGE:
{
struct yutani_msg_window_focus_change * wf = (void*)m->data;
if (wf->wid == win_hints->wid) {
yutani_focus_window(yctx, win_wizard->wid);
} else if (wf->wid == win_wizard->wid) {
win_wizard->focused = wf->focused;
redraw();
}
}
break;
case YUTANI_MSG_WINDOW_MOVE:
{
struct yutani_msg_window_move * wm = (void*)m->data;
if (wm->wid == win_hints->wid) {
if (wm->x != 0 || wm->y != 0) {
/* force us back to 0,0 */
yutani_window_move(yctx, win_hints, 0, 0);
}
} else if (wm->wid == win_wizard->wid) {
if (wm->x != center_x(WIZARD_WIDTH) || wm->y != center_y(WIZARD_HEIGHT)) {
yutani_window_move(yctx, win_wizard, center_x(WIZARD_WIDTH), center_y(WIZARD_HEIGHT));
}
}
}
break;
case YUTANI_MSG_WELCOME:
{
struct yutani_msg_welcome * mw = (void*)m->data;
fprintf(stderr, "ct display_width: %d\ndisplay_height: %d\n", yctx->display_width, yctx->display_height);
fprintf(stderr, "mw display_width: %d\ndisplay_height: %d\n", mw->display_width, mw->display_height);
yutani_window_resize(yctx, win_hints, mw->display_width, mw->display_height);
}
break;
case YUTANI_MSG_RESIZE_OFFER:
{
/* When we request a resize from the display-size-changed, we need
* to respond to the offer we'll get from the server to finish it */
struct yutani_msg_window_resize * wr = (void*)m->data;
if (wr->wid == win_hints->wid) {
resize_finish(wr->width, wr->height);
} /* Else, ignore resize offers for the main window */
}
break;
case YUTANI_MSG_WINDOW_MOUSE_EVENT:
{
struct yutani_msg_window_mouse_event * me = (void*)m->data;
if (me->wid != win_wizard->wid) break;
int result = decor_handle_event(yctx, m);
switch (result) {
case DECOR_CLOSE:
should_exit = 1;
break;
default:
/* Other actions */
do_mouse_stuff(me);
break;
}
}
break;
case YUTANI_MSG_SESSION_END:
should_exit = 1;
break;
default:
break;
}
free(m);
}
}
return 0;
}

241
userspace/py/bin/wizard.py Executable file
View File

@ -0,0 +1,241 @@
#!/usr/bin/python3
"""
Live CD wizard / tutorial
"""
import math
import os
import sys
import cairo
import yutani
import text_region
import toaru_fonts
from button import Button
class HintHole(object):
"""Draws a hole in the hints window."""
def __init__(self, x, y, radius):
self.x = x
self.y = y
self.radius = radius
def draw(self, ctx):
ctx.save();
ctx.set_operator(cairo.OPERATOR_SOURCE);
ctx.set_source_rgba(0.0, 0.0, 0.0, 0.0);
ctx.translate(self.x,self.y)
ctx.arc(0, 0, self.radius, 0, 2 * math.pi);
ctx.fill();
ctx.restore();
_arrow = cairo.ImageSurface.create_from_png('/usr/share/wizard-arrow.png')
class HintArrow(object):
def __init__(self, x, y, angle, wizard_relative=False):
self.x = x
self.y = y
self.angle = angle
self.wizard_relative = wizard_relative
def draw(self, ctx):
ctx.save()
w,h = _arrow.get_width(), _arrow.get_height()
if self.wizard_relative:
ctx.translate((hints.width-window.width)/2+self.x,(hints.height-window.height)/2+self.y)
else:
ctx.translate(self.x,self.y)
ctx.rotate(self.angle * math.pi / 179.0)
ctx.translate(-w,-h/2)
ctx.set_source_surface(_arrow,0,0)
ctx.paint()
ctx.restore()
class HintWindow(yutani.Window):
def __init__(self):
flags = yutani.WindowFlag.FLAG_NO_STEAL_FOCUS | yutani.WindowFlag.FLAG_DISALLOW_DRAG | yutani.WindowFlag.FLAG_DISALLOW_RESIZE | yutani.WindowFlag.FLAG_ALT_ANIMATION
super(HintWindow, self).__init__(yutani.yutani_ctx._ptr.contents.display_width, yutani.yutani_ctx._ptr.contents.display_height, doublebuffer=True, flags=flags)
self.move(0,0)
self.page = 0
def draw(self):
surface = self.get_cairo_surface()
ctx = cairo.Context(surface)
ctx.rectangle(0,0,self.width,self.height)
ctx.set_operator(cairo.OPERATOR_SOURCE)
ctx.set_source_rgba(0,0,0,100/255)
ctx.fill()
ctx.set_operator(cairo.OPERATOR_OVER)
for widget in pages[self.page][1]:
widget.draw(ctx)
self.flip()
def finish_resize(self, msg):
self.resize_accept(msg.width, msg.height)
self.reinit()
self.draw()
self.resize_done()
self.flip()
logo = "/usr/share/logo_login.png"
pages = [
(f"<img src=\"{logo}\"></img>\n\n<h1>Welcome to とあるOS!</h1>\n\nThis tutorial will guide you through the features of the operating system, as well as give you a feel for the UI and design principles.\n\n\nWhen you're ready to continue, press \"Next\".\n\n<link target=\"\">https://github.com/klange/toaruos</link> - <link target=\"\">http://toaruos.org</link>\n\nとあるOS is free software, released under the terms of the NCSA/University of Illinois license.",[]),
(f"<img src=\"{logo}\"></img>\n\nIf you wish to exit the tutorial at any time, you can click the × in the upper right corner of the window.",[HintArrow(620,-5,90,True)]),
(f"<img src=\"{logo}\"></img>\n\nAs a reminder, とあるOS is a hobby project with few developers.\nAs such, do not expect things to work perfectly, or in some cases, at all, as the kernel and drivers are very much \"work-in-progress\".",[]),
(f"\n\n\n\nとあるOS aims to provide a Unix-like environment. You can find familiar command-line tools by opening a terminal. Application shorctus on the desktop, as well as files in the file browser, are opened with a single click. You can also find more applications in the Applications menu.",[HintHole(70.5,80.5,50),HintArrow(110,120,-135)]),
(f"<img src=\"{logo}\"></img>\n\nThat's it for now!\n\nYou've finished the tutorial. More guides will be added to this tutorial in the future, but that's all for now. Press 'Exit' to close the tutorial and get started using the OS.",[]),
]
class WizardWindow(yutani.Window):
text_offset = 110
def __init__(self, decorator, title="Welcome to ToaruOS!", icon="star"):
flags = yutani.WindowFlag.FLAG_DISALLOW_DRAG | yutani.WindowFlag.FLAG_DISALLOW_RESIZE
super(WizardWindow, self).__init__(640,480, title=title, icon=icon, doublebuffer=True, flags=flags)
self.center()
self.decorator = decorator
hpad = 100
self.page = 0
self.button = Button("Next",self.button_click)
self.hover_widget = None
self.down_button = None
self.tr = text_region.TextRegion(hpad+self.decorator.left_width(),10+self.decorator.top_height(),self.width-self.decorator.width()-hpad*2,self.height-self.decorator.height()-20)
self.tr.line_height = 15
self.tr.set_alignment(2)
self.load_page()
def button_click(self, button):
self.page += 1
if self.page >= len(pages):
sys.exit(0)
self.load_page()
def load_page(self):
self.tr.set_richtext(pages[self.page][0])
if self.page == len(pages)-1:
self.button.text = "Exit"
hints.page = self.page
hints.draw()
def center(self):
self.move(int((yutani.yutani_ctx._ptr.contents.display_width-self.width)/2),int((yutani.yutani_ctx._ptr.contents.display_height-self.height)/2))
def draw(self):
surface = self.get_cairo_surface()
WIDTH, HEIGHT = self.width - self.decorator.width(), self.height - self.decorator.height()
ctx = cairo.Context(surface)
ctx.translate(self.decorator.left_width(), self.decorator.top_height())
ctx.rectangle(0,0,WIDTH,HEIGHT)
ctx.set_source_rgb(204/255,204/255,204/255)
ctx.fill()
self.tr.draw(self)
self.button.draw(self,ctx,int((WIDTH-100)/2),380,100,38)
self.decorator.render(self)
self.flip()
def mouse_event(self, msg):
if d.handle_event(msg) == yutani.Decor.EVENT_CLOSE:
window.close()
sys.exit(0)
x,y = msg.new_x - self.decorator.left_width(), msg.new_y - self.decorator.top_height()
w,h = self.width - self.decorator.width(), self.height - self.decorator.height()
redraw = False
if self.down_button:
if msg.command == yutani.MouseEvent.RAISE or msg.command == yutani.MouseEvent.CLICK:
if not (msg.buttons & yutani.MouseButton.BUTTON_LEFT):
if x >= self.down_button.x and \
x < self.down_button.x + self.down_button.width and \
y >= self.down_button.y and \
y < self.down_button.y + self.down_button.height:
self.down_button.focus_enter()
self.down_button.callback(self.down_button)
self.down_button = None
redraw = True
else:
self.down_button.focus_leave()
self.down_button = None
redraw = True
else:
if x >= self.button.x and x < self.button.x + self.button.width and y >= self.button.y and y < self.button.y + self.button.height:
if self.button != self.hover_widget:
if self.hover_widget:
self.hover_widget.leave()
self.button.focus_enter()
self.hover_widget = self.button
redraw = True
if msg.command == yutani.MouseEvent.DOWN:
self.button.hilight = 2
self.down_button = self.button
redraw = True
else:
if self.hover_widget == self.button:
self.button.focus_leave()
redraw = True
if redraw:
self.draw()
def keyboard_event(self, msg):
if msg.event.action != 0x01:
return # Ignore anything that isn't a key down.
if msg.event.key == b"q":
self.close()
sys.exit(0)
if msg.event.key == b' ':
self.page += 1
if self.page >= len(pages):
self.close()
sys.exit(0)
self.load_page()
self.draw()
if __name__ == '__main__':
yctx = yutani.Yutani()
d = yutani.Decor()
hints = HintWindow()
hints.draw()
window = WizardWindow(d)
window.draw()
while 1:
# Poll for events.
msg = yutani.yutani_ctx.poll()
if msg.type == yutani.Message.MSG_SESSION_END:
window.close()
hints.close()
break
elif msg.type == yutani.Message.MSG_WELCOME:
hints.resize(msg.display_width,msg.display_height)
window.center()
elif msg.type == yutani.Message.MSG_RESIZE_OFFER:
if msg.wid == hints.wid:
hints.finish_resize(msg)
elif msg.type == yutani.Message.MSG_KEY_EVENT:
if msg.wid == window.wid:
window.keyboard_event(msg)
elif msg.type == yutani.Message.MSG_WINDOW_FOCUS_CHANGE:
if msg.wid == hints.wid:
yctx.focus_window(window.wid)
if msg.wid == window.wid:
window.focused = msg.focused
window.draw()
elif msg.type == yutani.Message.MSG_WINDOW_MOUSE_EVENT:
if msg.wid == window.wid:
window.mouse_event(msg)