Will also set overlay to lockscreen so we don't have inadvetent screen selections. Touching the screen will bring the display back up. add back check script for poweroff move diff time function to twrp-functions.cpp make sure we chmod after copy_file add read_file and write_file functions to twrp-functions.cpp make single thread try to force update screen add forceRender drop caches after tar processing Change-Id: I3c5c509dd39dbb05451bbfe5d8b56d53c90d8d1b
748 lines
15 KiB
C++
748 lines
15 KiB
C++
/*
|
|
* Copyright (C) 2007 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <linux/input.h>
|
|
#include <pthread.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <sys/reboot.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mount.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
|
|
extern "C"
|
|
{
|
|
#include "../common.h"
|
|
#include "../roots.h"
|
|
#include "../minuitwrp/minui.h"
|
|
#include "../recovery_ui.h"
|
|
#include "../minzip/Zip.h"
|
|
#include <pixelflinger/pixelflinger.h>
|
|
}
|
|
|
|
#include "rapidxml.hpp"
|
|
#include "objects.hpp"
|
|
#include "../data.hpp"
|
|
#include "../variables.h"
|
|
#include "../partitions.hpp"
|
|
#include "../twrp-functions.hpp"
|
|
#include "blanktimer.hpp"
|
|
|
|
const static int CURTAIN_FADE = 32;
|
|
|
|
using namespace rapidxml;
|
|
|
|
// Global values
|
|
static gr_surface gCurtain = NULL;
|
|
static int gGuiInitialized = 0;
|
|
static int gGuiConsoleRunning = 0;
|
|
static int gGuiConsoleTerminate = 0;
|
|
static int gForceRender = 0;
|
|
pthread_mutex_t gForceRendermutex;
|
|
static int gNoAnimation = 1;
|
|
static int gGuiInputRunning = 0;
|
|
blanktimer blankTimer;
|
|
|
|
// Needed by pages.cpp too
|
|
int gGuiRunning = 0;
|
|
|
|
static int gRecorder = -1;
|
|
|
|
extern "C" void gr_write_frame_to_file (int fd);
|
|
|
|
void
|
|
flip (void)
|
|
{
|
|
if (gRecorder != -1)
|
|
{
|
|
timespec time;
|
|
clock_gettime (CLOCK_MONOTONIC, &time);
|
|
write (gRecorder, &time, sizeof (timespec));
|
|
gr_write_frame_to_file (gRecorder);
|
|
}
|
|
gr_flip ();
|
|
return;
|
|
}
|
|
|
|
void
|
|
rapidxml::parse_error_handler (const char *what, void *where)
|
|
{
|
|
fprintf (stderr, "Parser error: %s\n", what);
|
|
fprintf (stderr, " Start of string: %s\n", (char *) where);
|
|
abort ();
|
|
}
|
|
|
|
static void
|
|
curtainSet ()
|
|
{
|
|
gr_color (0, 0, 0, 255);
|
|
gr_fill (0, 0, gr_fb_width (), gr_fb_height ());
|
|
gr_blit (gCurtain, 0, 0, gr_get_width (gCurtain), gr_get_height (gCurtain),
|
|
0, 0);
|
|
gr_flip ();
|
|
return;
|
|
}
|
|
|
|
static void
|
|
curtainRaise (gr_surface surface)
|
|
{
|
|
int sy = 0;
|
|
int h = gr_get_height (gCurtain) - 1;
|
|
int w = gr_get_width (gCurtain);
|
|
int fy = 1;
|
|
|
|
int msw = gr_get_width (surface);
|
|
int msh = gr_get_height (surface);
|
|
int CURTAIN_RATE = msh / 30;
|
|
|
|
if (gNoAnimation == 0)
|
|
{
|
|
for (; h > 0; h -= CURTAIN_RATE, sy += CURTAIN_RATE, fy += CURTAIN_RATE)
|
|
{
|
|
gr_blit (surface, 0, 0, msw, msh, 0, 0);
|
|
gr_blit (gCurtain, 0, sy, w, h, 0, 0);
|
|
gr_flip ();
|
|
}
|
|
}
|
|
gr_blit (surface, 0, 0, msw, msh, 0, 0);
|
|
flip ();
|
|
return;
|
|
}
|
|
|
|
void
|
|
curtainClose ()
|
|
{
|
|
#if 0
|
|
int w = gr_get_width (gCurtain);
|
|
int h = 1;
|
|
int sy = gr_get_height (gCurtain) - 1;
|
|
int fbh = gr_fb_height ();
|
|
int CURTAIN_RATE = fbh / 30;
|
|
|
|
if (gNoAnimation == 0)
|
|
{
|
|
for (; h < fbh; h += CURTAIN_RATE, sy -= CURTAIN_RATE)
|
|
{
|
|
gr_blit (gCurtain, 0, sy, w, h, 0, 0);
|
|
gr_flip ();
|
|
}
|
|
gr_blit (gCurtain, 0, 0, gr_get_width (gCurtain),
|
|
gr_get_height (gCurtain), 0, 0);
|
|
gr_flip ();
|
|
|
|
if (gRecorder != -1)
|
|
close (gRecorder);
|
|
|
|
int fade;
|
|
for (fade = 16; fade < 255; fade += CURTAIN_FADE)
|
|
{
|
|
gr_blit (gCurtain, 0, 0, gr_get_width (gCurtain),
|
|
gr_get_height (gCurtain), 0, 0);
|
|
gr_color (0, 0, 0, fade);
|
|
gr_fill (0, 0, gr_fb_width (), gr_fb_height ());
|
|
gr_flip ();
|
|
}
|
|
gr_color (0, 0, 0, 255);
|
|
gr_fill (0, 0, gr_fb_width (), gr_fb_height ());
|
|
gr_flip ();
|
|
}
|
|
#else
|
|
gr_blit (gCurtain, 0, 0, gr_get_width (gCurtain), gr_get_height (gCurtain),
|
|
0, 0);
|
|
gr_flip ();
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
static void *
|
|
input_thread (void *cookie)
|
|
{
|
|
int drag = 0;
|
|
static int touch_and_hold = 0, dontwait = 0, touch_repeat = 0, x = 0, y =
|
|
0, lshift = 0, rshift = 0, key_repeat = 0;
|
|
static struct timeval touchStart;
|
|
HardwareKeyboard kb;
|
|
|
|
//start screen timeout threads
|
|
blankTimer.setTimerThread();
|
|
|
|
for (;;)
|
|
{
|
|
|
|
// wait for the next event
|
|
struct input_event ev;
|
|
int state = 0, ret = 0;
|
|
|
|
ret = ev_get (&ev, dontwait);
|
|
|
|
if (ret < 0)
|
|
{
|
|
struct timeval curTime;
|
|
gettimeofday (&curTime, NULL);
|
|
long mtime, seconds, useconds;
|
|
|
|
seconds = curTime.tv_sec - touchStart.tv_sec;
|
|
useconds = curTime.tv_usec - touchStart.tv_usec;
|
|
|
|
mtime = ((seconds) * 1000 + useconds / 1000.0) + 0.5;
|
|
if (touch_and_hold && mtime > 500)
|
|
{
|
|
touch_and_hold = 0;
|
|
touch_repeat = 1;
|
|
gettimeofday (&touchStart, NULL);
|
|
#ifdef _EVENT_LOGGING
|
|
LOGE ("TOUCH_HOLD: %d,%d\n", x, y);
|
|
#endif
|
|
PageManager::NotifyTouch (TOUCH_HOLD, x, y);
|
|
blankTimer.resetTimerAndUnblank();
|
|
}
|
|
else if (touch_repeat && mtime > 100)
|
|
{
|
|
#ifdef _EVENT_LOGGING
|
|
LOGE ("TOUCH_REPEAT: %d,%d\n", x, y);
|
|
#endif
|
|
gettimeofday (&touchStart, NULL);
|
|
PageManager::NotifyTouch (TOUCH_REPEAT, x, y);
|
|
blankTimer.resetTimerAndUnblank();
|
|
}
|
|
else if (key_repeat == 1 && mtime > 500)
|
|
{
|
|
#ifdef _EVENT_LOGGING
|
|
LOGE ("KEY_HOLD: %d,%d\n", x, y);
|
|
#endif
|
|
gettimeofday (&touchStart, NULL);
|
|
key_repeat = 2;
|
|
kb.KeyRepeat ();
|
|
blankTimer.resetTimerAndUnblank();
|
|
}
|
|
else if (key_repeat == 2 && mtime > 100)
|
|
{
|
|
#ifdef _EVENT_LOGGING
|
|
LOGE ("KEY_REPEAT: %d,%d\n", x, y);
|
|
#endif
|
|
gettimeofday (&touchStart, NULL);
|
|
kb.KeyRepeat ();
|
|
blankTimer.resetTimerAndUnblank();
|
|
}
|
|
}
|
|
else if (ev.type == EV_ABS)
|
|
{
|
|
|
|
x = ev.value >> 16;
|
|
y = ev.value & 0xFFFF;
|
|
|
|
if (ev.code == 0)
|
|
{
|
|
if (state == 0)
|
|
{
|
|
#ifdef _EVENT_LOGGING
|
|
LOGE ("TOUCH_RELEASE: %d,%d\n", x, y);
|
|
#endif
|
|
PageManager::NotifyTouch (TOUCH_RELEASE, x, y);
|
|
blankTimer.resetTimerAndUnblank();
|
|
touch_and_hold = 0;
|
|
touch_repeat = 0;
|
|
if (!key_repeat)
|
|
dontwait = 0;
|
|
}
|
|
state = 0;
|
|
drag = 0;
|
|
}
|
|
else
|
|
{
|
|
if (!drag)
|
|
{
|
|
#ifdef _EVENT_LOGGING
|
|
LOGE ("TOUCH_START: %d,%d\n", x, y);
|
|
#endif
|
|
if (PageManager::NotifyTouch (TOUCH_START, x, y) > 0)
|
|
state = 1;
|
|
drag = 1;
|
|
touch_and_hold = 1;
|
|
dontwait = 1;
|
|
key_repeat = 0;
|
|
gettimeofday (&touchStart, NULL);
|
|
blankTimer.resetTimerAndUnblank();
|
|
}
|
|
else
|
|
{
|
|
if (state == 0)
|
|
{
|
|
#ifdef _EVENT_LOGGING
|
|
LOGE ("TOUCH_DRAG: %d,%d\n", x, y);
|
|
#endif
|
|
if (PageManager::NotifyTouch (TOUCH_DRAG, x, y) > 0)
|
|
state = 1;
|
|
key_repeat = 0;
|
|
blankTimer.resetTimerAndUnblank();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (ev.type == EV_KEY)
|
|
{
|
|
// Handle key-press here
|
|
#ifdef _EVENT_LOGGING
|
|
LOGE ("TOUCH_KEY: %d\n", ev.code);
|
|
#endif
|
|
if (ev.value != 0)
|
|
{
|
|
// This is a key press
|
|
if (kb.KeyDown (ev.code))
|
|
{
|
|
key_repeat = 1;
|
|
touch_and_hold = 0;
|
|
touch_repeat = 0;
|
|
dontwait = 1;
|
|
gettimeofday (&touchStart, NULL);
|
|
blankTimer.resetTimerAndUnblank();
|
|
}
|
|
else
|
|
{
|
|
key_repeat = 0;
|
|
touch_and_hold = 0;
|
|
touch_repeat = 0;
|
|
dontwait = 0;
|
|
blankTimer.resetTimerAndUnblank();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This is a key release
|
|
kb.KeyUp (ev.code);
|
|
key_repeat = 0;
|
|
touch_and_hold = 0;
|
|
touch_repeat = 0;
|
|
dontwait = 0;
|
|
blankTimer.resetTimerAndUnblank();
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// This special function will return immediately the first time, but then
|
|
// always returns 1/30th of a second (or immediately if called later) from
|
|
// the last time it was called
|
|
static void
|
|
loopTimer (void)
|
|
{
|
|
static timespec lastCall;
|
|
static int initialized = 0;
|
|
|
|
if (!initialized)
|
|
{
|
|
clock_gettime (CLOCK_MONOTONIC, &lastCall);
|
|
initialized = 1;
|
|
return;
|
|
}
|
|
|
|
do
|
|
{
|
|
timespec curTime;
|
|
clock_gettime (CLOCK_MONOTONIC, &curTime);
|
|
|
|
timespec diff = TWFunc::timespec_diff (lastCall, curTime);
|
|
|
|
// This is really 30 times per second
|
|
if (diff.tv_sec || diff.tv_nsec > 33333333)
|
|
{
|
|
lastCall = curTime;
|
|
return;
|
|
}
|
|
|
|
// We need to sleep some period time microseconds
|
|
unsigned int sleepTime = 33333 - (diff.tv_nsec / 1000);
|
|
usleep (sleepTime);
|
|
}
|
|
while (1);
|
|
return;
|
|
}
|
|
|
|
static int
|
|
runPages (void)
|
|
{
|
|
// Raise the curtain
|
|
if (gCurtain != NULL)
|
|
{
|
|
gr_surface surface;
|
|
|
|
PageManager::Render ();
|
|
gr_get_surface (&surface);
|
|
curtainRaise (surface);
|
|
gr_free_surface (surface);
|
|
}
|
|
|
|
gGuiRunning = 1;
|
|
|
|
DataManager::SetValue ("tw_loaded", 1);
|
|
|
|
for (;;)
|
|
{
|
|
loopTimer ();
|
|
|
|
if (!gForceRender)
|
|
{
|
|
int ret;
|
|
|
|
ret = PageManager::Update ();
|
|
if (ret > 1)
|
|
PageManager::Render ();
|
|
|
|
if (ret > 0)
|
|
flip ();
|
|
}
|
|
else
|
|
{
|
|
pthread_mutex_lock(&gForceRendermutex);
|
|
gForceRender = 0;
|
|
pthread_mutex_unlock(&gForceRendermutex);
|
|
PageManager::Render ();
|
|
flip ();
|
|
}
|
|
}
|
|
|
|
gGuiRunning = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
runPage (const char *page_name)
|
|
{
|
|
gui_changePage (page_name);
|
|
|
|
// Raise the curtain
|
|
if (gCurtain != NULL)
|
|
{
|
|
gr_surface surface;
|
|
|
|
PageManager::Render ();
|
|
gr_get_surface (&surface);
|
|
curtainRaise (surface);
|
|
gr_free_surface (surface);
|
|
}
|
|
|
|
gGuiRunning = 1;
|
|
|
|
DataManager::SetValue ("tw_loaded", 1);
|
|
|
|
for (;;)
|
|
{
|
|
loopTimer ();
|
|
|
|
if (!gForceRender)
|
|
{
|
|
int ret;
|
|
|
|
ret = PageManager::Update ();
|
|
if (ret > 1)
|
|
PageManager::Render ();
|
|
|
|
if (ret > 0)
|
|
flip ();
|
|
}
|
|
else
|
|
{
|
|
pthread_mutex_lock(&gForceRendermutex);
|
|
gForceRender = 0;
|
|
pthread_mutex_unlock(&gForceRendermutex);
|
|
PageManager::Render ();
|
|
flip ();
|
|
}
|
|
if (DataManager::GetIntValue ("tw_page_done") != 0)
|
|
{
|
|
gui_changePage ("main");
|
|
break;
|
|
}
|
|
}
|
|
|
|
gGuiRunning = 0;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
gui_forceRender (void)
|
|
{
|
|
pthread_mutex_lock(&gForceRendermutex);
|
|
gForceRender = 1;
|
|
pthread_mutex_unlock(&gForceRendermutex);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
gui_changePage (std::string newPage)
|
|
{
|
|
LOGI ("Set page: '%s'\n", newPage.c_str ());
|
|
PageManager::ChangePage (newPage);
|
|
pthread_mutex_lock(&gForceRendermutex);
|
|
gForceRender = 1;
|
|
pthread_mutex_unlock(&gForceRendermutex);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
gui_changeOverlay (std::string overlay)
|
|
{
|
|
PageManager::ChangeOverlay (overlay);
|
|
pthread_mutex_lock(&gForceRendermutex);
|
|
gForceRender = 1;
|
|
pthread_mutex_unlock(&gForceRendermutex);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
gui_changePackage (std::string newPackage)
|
|
{
|
|
PageManager::SelectPackage (newPackage);
|
|
pthread_mutex_lock(&gForceRendermutex);
|
|
gForceRender = 1;
|
|
pthread_mutex_unlock(&gForceRendermutex);
|
|
return 0;
|
|
}
|
|
|
|
std::string gui_parse_text (string inText)
|
|
{
|
|
// Copied from std::string GUIText::parseText(void)
|
|
// This function parses text for DataManager values encompassed by %value% in the XML
|
|
static int counter = 0;
|
|
std::string str = inText;
|
|
size_t pos = 0;
|
|
size_t next = 0, end = 0;
|
|
|
|
while (1)
|
|
{
|
|
next = str.find ('%', pos);
|
|
if (next == std::string::npos)
|
|
return str;
|
|
end = str.find ('%', next + 1);
|
|
if (end == std::string::npos)
|
|
return str;
|
|
|
|
// We have a block of data
|
|
std::string var = str.substr (next + 1, (end - next) - 1);
|
|
str.erase (next, (end - next) + 1);
|
|
|
|
if (next + 1 == end)
|
|
{
|
|
str.insert (next, 1, '%');
|
|
}
|
|
else
|
|
{
|
|
std::string value;
|
|
if (DataManager::GetValue (var, value) == 0)
|
|
str.insert (next, value);
|
|
}
|
|
|
|
pos = next + 1;
|
|
}
|
|
}
|
|
|
|
extern "C" int
|
|
gui_init ()
|
|
{
|
|
int fd;
|
|
|
|
gr_init ();
|
|
|
|
if (res_create_surface ("/res/images/curtain.jpg", &gCurtain))
|
|
{
|
|
printf
|
|
("Unable to locate '/res/images/curtain.jpg'\nDid you set a DEVICE_RESOLUTION in your config files?\n");
|
|
return -1;
|
|
}
|
|
|
|
curtainSet ();
|
|
|
|
ev_init ();
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
gui_loadResources ()
|
|
{
|
|
// unlink("/sdcard/video.last");
|
|
// rename("/sdcard/video.bin", "/sdcard/video.last");
|
|
// gRecorder = open("/sdcard/video.bin", O_CREAT | O_WRONLY);
|
|
|
|
int check = 0;
|
|
DataManager::GetValue (TW_IS_ENCRYPTED, check);
|
|
if (check)
|
|
{
|
|
if (PageManager::LoadPackage ("TWRP", "/res/ui.xml", "decrypt"))
|
|
{
|
|
LOGE ("Failed to load base packages.\n");
|
|
goto error;
|
|
}
|
|
else
|
|
check = 1;
|
|
}
|
|
if (check == 0
|
|
&& PageManager::LoadPackage ("TWRP", "/script/ui.xml", "main"))
|
|
{
|
|
std::string theme_path;
|
|
|
|
theme_path = DataManager::GetSettingsStoragePath ();
|
|
if (!PartitionManager.Mount_Settings_Storage (false))
|
|
{
|
|
int retry_count = 5;
|
|
while (retry_count > 0
|
|
&& !PartitionManager.Mount_Settings_Storage (false))
|
|
{
|
|
usleep (500000);
|
|
retry_count--;
|
|
}
|
|
if (!PartitionManager.Mount_Settings_Storage (false))
|
|
{
|
|
LOGE ("Unable to mount %s during GUI startup.\n",
|
|
theme_path.c_str ());
|
|
check = 1;
|
|
}
|
|
}
|
|
|
|
theme_path += "/TWRP/theme/ui.zip";
|
|
if (check || PageManager::LoadPackage ("TWRP", theme_path, "main"))
|
|
{
|
|
if (PageManager::LoadPackage ("TWRP", "/res/ui.xml", "main"))
|
|
{
|
|
LOGE ("Failed to load base packages.\n");
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the default package
|
|
PageManager::SelectPackage ("TWRP");
|
|
|
|
gGuiInitialized = 1;
|
|
return 0;
|
|
|
|
error:
|
|
LOGE ("An internal error has occurred.\n");
|
|
gGuiInitialized = 0;
|
|
return -1;
|
|
}
|
|
|
|
extern "C" int
|
|
gui_start ()
|
|
{
|
|
if (!gGuiInitialized)
|
|
return -1;
|
|
|
|
gGuiConsoleTerminate = 1;
|
|
while (gGuiConsoleRunning)
|
|
loopTimer ();
|
|
|
|
// Set the default package
|
|
PageManager::SelectPackage ("TWRP");
|
|
|
|
if (!gGuiInputRunning)
|
|
{
|
|
// Start by spinning off an input handler.
|
|
pthread_t t;
|
|
pthread_create (&t, NULL, input_thread, NULL);
|
|
gGuiInputRunning = 1;
|
|
}
|
|
|
|
return runPages ();
|
|
}
|
|
|
|
extern "C" int
|
|
gui_startPage (const char *page_name)
|
|
{
|
|
if (!gGuiInitialized)
|
|
return -1;
|
|
|
|
gGuiConsoleTerminate = 1;
|
|
while (gGuiConsoleRunning)
|
|
loopTimer ();
|
|
|
|
// Set the default package
|
|
PageManager::SelectPackage ("TWRP");
|
|
|
|
if (!gGuiInputRunning)
|
|
{
|
|
// Start by spinning off an input handler.
|
|
pthread_t t;
|
|
pthread_create (&t, NULL, input_thread, NULL);
|
|
gGuiInputRunning = 1;
|
|
}
|
|
|
|
DataManager::SetValue ("tw_page_done", 0);
|
|
return runPage (page_name);
|
|
}
|
|
|
|
static void *
|
|
console_thread (void *cookie)
|
|
{
|
|
PageManager::SwitchToConsole ();
|
|
|
|
while (!gGuiConsoleTerminate)
|
|
{
|
|
loopTimer ();
|
|
|
|
if (!gForceRender)
|
|
{
|
|
int ret;
|
|
|
|
ret = PageManager::Update ();
|
|
if (ret > 1)
|
|
PageManager::Render ();
|
|
|
|
if (ret > 0)
|
|
flip ();
|
|
|
|
if (ret < 0)
|
|
LOGE ("An update request has failed.\n");
|
|
}
|
|
else
|
|
{
|
|
pthread_mutex_lock(&gForceRendermutex);
|
|
gForceRender = 0;
|
|
pthread_mutex_unlock(&gForceRendermutex);
|
|
PageManager::Render ();
|
|
flip ();
|
|
}
|
|
}
|
|
gGuiConsoleRunning = 0;
|
|
return NULL;
|
|
}
|
|
|
|
extern "C" int
|
|
gui_console_only ()
|
|
{
|
|
if (!gGuiInitialized)
|
|
return -1;
|
|
|
|
gGuiConsoleTerminate = 0;
|
|
gGuiConsoleRunning = 1;
|
|
|
|
// Start by spinning off an input handler.
|
|
pthread_t t;
|
|
pthread_create (&t, NULL, console_thread, NULL);
|
|
|
|
return 0;
|
|
}
|