/* * Copyright (C) 2013 ~ 2018 National University of Defense Technology(NUDT) & Tianjin Kylin Ltd. * * Authors: * Kobe Lee xiangli@ubuntukylin.com/kobe24_lixiang@126.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "processdialog.h" #include "propertiesdialog.h" #include "processdata.h" #include "processcategory.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //for setpriority using std::string; QDataStream &operator<<(QDataStream &dataStream, const ProcDataPtr &object) { auto ptr = object.data(); auto ptrval = reinterpret_cast(ptr); auto var = QVariant::fromValue(ptrval); dataStream << var; return dataStream; } QDataStream &operator>>(QDataStream &dataStream, ProcDataPtr &object) { QVariant var; dataStream >> var; qulonglong ptrval = var.toULongLong(); auto ptr = reinterpret_cast(ptrval); object = ProcDataPtr(ptr); return dataStream; } ProcessDialog::ProcessDialog(QList toBeDisplayedColumns, int currentSortIndex, bool isSort, QSettings *settings, QWidget *parent) :QWidget(parent) ,num_cpus(0) ,frequency(0U) ,proSettings(settings) { setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); setAttribute(Qt::WA_NoMousePropagation); qRegisterMetaType(); qRegisterMetaTypeStreamOperators(); qRegisterMetaType(); qRegisterMetaType>(); actionPids = new QList(); m_layout = new QVBoxLayout(this); m_layout->setContentsMargins(0, 0, 0, 0); m_layout->setSpacing(0); m_processListWidget = new ProcessListWidget(toBeDisplayedColumns); connect(m_processListWidget, SIGNAL(changeColumnVisible(int,bool,QList)), this, SIGNAL(changeColumnVisible(int,bool,QList))); connect(m_processListWidget, SIGNAL(changeSortStatus(int,bool)), this, SIGNAL(changeSortStatus(int,bool))); connect(m_processListWidget, &ProcessListWidget::rightMouseClickedItems, this, &ProcessDialog::popupMenu, Qt::QueuedConnection); m_layout->addWidget(m_processListWidget); whose_processes = "user"; proSettings->beginGroup("PROCESS"); whose_processes = proSettings->value("WhoseProcesses", whose_processes).toString(); proSettings->endGroup(); int tabIndex = 1; if (whose_processes == "active") { tabIndex = 0; } else if (whose_processes == "all") { tabIndex = 2; } else { tabIndex = 1; } QWidget *w = new QWidget; w->setFixedHeight(50); m_categoryLayout = new QHBoxLayout(w); m_categoryLayout->setContentsMargins(0, 0, 6, 3); m_categoryLayout->setSpacing(10); processCategory = new ProcessCategory(tabIndex); connect(processCategory, SIGNAL(activeWhoseProcessList(int)), this, SLOT(onActiveWhoseProcess(int))); m_categoryLayout->addWidget(processCategory, 0, Qt::AlignRight); m_layout->addWidget(w); QList *sortFuncList = new QList(); sortFuncList->append(&ProcessListItem::sortByName); sortFuncList->append(&ProcessListItem::sortByUser); sortFuncList->append(&ProcessListItem::sortByStatus); sortFuncList->append(&ProcessListItem::sortByCPU); sortFuncList->append(&ProcessListItem::sortByPid); sortFuncList->append(&ProcessListItem::sortByCommand); sortFuncList->append(&ProcessListItem::sortByMemory); sortFuncList->append(&ProcessListItem::sortByPriority); m_processListWidget->setProcessSortFunctions(sortFuncList, currentSortIndex, isSort); m_processListWidget->setSearchFunction(&ProcessListItem::doSearch); endProcessDialog = new MyDialog(QString(tr("End process")), QString(tr("Ending a process may destroy data, break the session or introduce a security risk. Only unresponsive processes should be ended.\nAre you sure to continue?"))); endProcessDialog->setWindowFlags(endProcessDialog->windowFlags() | Qt::WindowStaysOnTopHint); endProcessDialog->addButton(QString(tr("Cancel")), false); endProcessDialog->addButton(QString(tr("End process")), true); connect(endProcessDialog, &MyDialog::buttonClicked, this, &ProcessDialog::endDialogButtonClicked); killProcessDialog = new MyDialog(QString(tr("Kill process")), QString(tr("Killing a process may destroy data, break the session or introduce a security risk. Only unresponsive processes should be killed.\nAre you sure to continue?"))); killProcessDialog->setWindowFlags(killProcessDialog->windowFlags() | Qt::WindowStaysOnTopHint); killProcessDialog->addButton(QString(tr("Cancel")), false); killProcessDialog->addButton(QString(tr("Kill process")), true); connect(killProcessDialog, &MyDialog::buttonClicked, this, &ProcessDialog::killDialogButtonClicked); m_menu = new QMenu(); m_stopAction = new QAction(tr("Stop process"), this); connect(m_stopAction, &QAction::triggered, this, &ProcessDialog::stopProcesses); m_continueAction = new QAction(tr("Continue process"), this); connect(m_continueAction, &QAction::triggered, this, &ProcessDialog::continueProcesses); m_endAction = new QAction(tr("End process"), this); connect(m_endAction, &QAction::triggered, this, &ProcessDialog::showEndProcessDialog); m_killAction = new QAction(tr("Kill process"), this); connect(m_killAction, &QAction::triggered, this, &ProcessDialog::showKillProcessDialog); // priorityGroup = new MyActionGroup(this); // veryHighAction = new MyActionGroupItem(this, priorityGroup, "very_high_action", -20); // highAction = new MyActionGroupItem(this, priorityGroup, "high_action", -5); // normalAction = new MyActionGroupItem(this, priorityGroup, "normal_action", 0); // lowAction = new MyActionGroupItem(this, priorityGroup, "low_action", 5); // veryLowAction = new MyActionGroupItem(this, priorityGroup, "very_low_action", 19); // customAction = new MyActionGroupItem(this, priorityGroup, "custom_action", 32); // { // QAction *sep = new QAction(priorityGroup); // sep->setSeparator(true); // } // veryHighAction->change(tr("Very High")); // highAction->change(tr("High")); // normalAction->change(tr("Normal")); // lowAction->change(tr("Low")); // veryLowAction->change(tr("Very Low")); // customAction->change(tr("Custom")); // connect(priorityGroup, SIGNAL(activated(int)), this, SLOT(changeProcPriority(int))); // m_priorityMenu = new QMenu(); // m_priorityMenu->addActions(priorityGroup->actions()); // m_priorityMenu->menuAction()->setText(tr("Change Priority")); m_propertiyAction = new QAction(tr("Properties"), this); connect(m_propertiyAction, &QAction::triggered, this, &ProcessDialog::showPropertiesDialog); m_menu->addAction(m_stopAction);//停止 m_menu->addAction(m_continueAction);//继续进程 m_menu->addAction(m_endAction);//结束 m_menu->addAction(m_killAction);//杀死 // m_menu->addSeparator(); // m_menu->addMenu(m_priorityMenu); m_menu->addSeparator(); m_menu->addAction(m_propertiyAction); glibtop_init(); this->num_cpus = glibtop_get_sysinfo()->ncpu; this->refreshProcessList(); timer = new QTimer(this); connect(timer,SIGNAL(timeout()),this,SLOT(refreshProcessList())); timer->start(3000); } ProcessDialog::~ProcessDialog() { glibtop_close(); this->clearOriginProcList(); if (timer != NULL) { disconnect(timer,SIGNAL(timeout()),this,SLOT(refreshProcessList())); if(timer->isActive()) { timer->stop(); } delete timer; timer = NULL; } delete processCategory; delete endProcessDialog; delete killProcessDialog; delete m_processListWidget; delete m_stopAction; delete m_continueAction; delete m_endAction; delete m_killAction; // delete veryHighAction; // delete highAction; // delete normalAction; // delete lowAction; // delete veryLowAction; // delete customAction; // delete m_priorityMenu; delete m_propertiyAction; delete m_menu; delete actionPids; QLayoutItem *child; while ((child = m_categoryLayout->takeAt(0)) != 0) { if (child->widget()) child->widget()->deleteLater(); delete child; } delete m_layout; } void ProcessDialog::displayAllProcess() { timer->stop(); this->clearOriginProcList(); whose_processes = "all"; this->refreshProcessList(); timer->start(3000); } void ProcessDialog::displayActiveProcess() { timer->stop(); this->clearOriginProcList(); whose_processes = "active"; this->refreshProcessList(); timer->start(3000); } void ProcessDialog::displayCurrentUserProcess() { timer->stop(); this->clearOriginProcList(); whose_processes = "user"; this->refreshProcessList(); timer->start(3000); } void ProcessDialog::onActiveWhoseProcess(int index) { if (index == 0) { if (this->whose_processes != "active") this->displayActiveProcess(); } else if (index == 1) { if (this->whose_processes != "user") this->displayCurrentUserProcess(); } else { if (this->whose_processes != "all") this->displayAllProcess(); } proSettings->beginGroup("PROCESS"); proSettings->setValue("WhoseProcesses", whose_processes); proSettings->endGroup(); proSettings->sync(); } void ProcessDialog::clearOriginProcList() { for (ProcessWorker::Iterator it(ProcessWorker::begin()); it != ProcessWorker::end(); ++it) delete it->second; ProcessWorker::all.clear(); } QString rootCommand(QString command, int value, pid_t pid) { return QString("gksu \"%1 %1 %1\"").arg(command, value, pid); } void startRenice(QString command, int value, pid_t pid) { QProcess::startDetached(rootCommand(command, value, pid)); } void ProcessDialog::changeProcPriority(int nice) { if (nice == 32) { //show renice dialog } else { pid_t cur_pid = -1; for (pid_t pid : *actionPids) { cur_pid = pid; break; } if (cur_pid > -1) { ProcessWorker *info = ProcessWorker::find(cur_pid); if (!info) { actionPids->clear(); return; } if (info->nice == nice) { actionPids->clear(); return; } int saved_errno; int error = setpriority(PRIO_PROCESS, cur_pid, nice); //success if(error != -1) { actionPids->clear(); return; } saved_errno = errno; //need to be root if(errno == EPERM || errno == EACCES) { qDebug() << "Change priority need to be root!!!"; //kobe test //startRenice("renice", nice, cur_pid); bool success = false; QString command = QString("renice %1 %1").arg(nice).arg(cur_pid); QFile file("/usr/bin/pkexec"); if(file.exists()) { // gint *exit_status = NULL; // GError *error = NULL; QString cmd = QString("pkexec --disable-internal-agent /usr/lib/gnome-system-monitor/gnome-system-monitor/gsm-%1").arg(command);//gsm-renice qDebug() << "cmd="<message); // g_error_free(error); // } // else // { // g_debug("pkexec did fine\n"); // ret = TRUE; // } } else { qDebug() << "change to root failed......"; } /*gboolean success; success = procdialog_create_root_password_dialog ( PROCMAN_ACTION_RENICE, args->app, info->pid, nice); if(success) return; if(errno) { saved_errno = errno; }*/ // static char * // procman_action_to_command(ProcmanActionType type, // gint pid, // gint extra_value) // { // switch (type) { // case PROCMAN_ACTION_KILL: // return g_strdup_printf("kill -s %d %d", extra_value, pid); // case PROCMAN_ACTION_RENICE: // return g_strdup_printf("renice %d %d", extra_value, pid); // default: // g_assert_not_reached(); // } // } // gboolean procdialog_create_root_password_dialog(ProcmanActionType type, // GsmApplication *app, // gint pid, // gint nice) // { // char * command; // gboolean ret = FALSE; // command = g_strdup_printf("renice %d %d", nice, pid); // //command = procman_action_to_command(type, pid, nice); // procman_debug("Trying to run '%s' as root", command); // if (g_file_test("/usr/bin/pkexec", G_FILE_TEST_EXISTS)) { // gboolean ret = FALSE; // gint *exit_status = NULL; // GError *error = NULL; // gchar *command_line = g_strdup_printf("pkexec --disable-internal-agent %s/gsm-%s", // GSM_LIBEXEC_DIR, command); // if (!g_spawn_command_line_sync(command_line, NULL, NULL, exit_status, &error)) { // g_critical("Could not run pkexec(\"%s\") : %s\n", // command, error->message); // g_error_free(error); // } // else // { // g_debug("pkexec did fine\n"); // ret = TRUE; // } // g_free (command_line); // return ret; // } // if (procman_has_pkexec()) // ret = gsm_pkexec_create_root_password_dialog(command); // else if (procman_has_gksu()) // ret = gsm_gksu_create_root_password_dialog(command); // else if (procman_has_gnomesu()) // ret = gsm_gnomesu_create_root_password_dialog(command); // g_free(command); // return ret; // } } } } actionPids->clear(); // static void // renice_scale_changed (GtkAdjustment *adj, gpointer data) // { // GtkWidget *label = GTK_WIDGET (data); // new_nice_value = int(gtk_adjustment_get_value (adj)); // gchar* text = g_strdup(procman::get_nice_level_with_priority (new_nice_value)); // gtk_label_set_text (GTK_LABEL (label), text); // g_free(text); // } // static void // renice_dialog_button_pressed (GtkDialog *dialog, gint id, gpointer data) // { // GsmApplication *app = static_cast(data); // if (id == 100) { // if (new_nice_value == -100) // return; // renice(app, new_nice_value); // } // gtk_widget_destroy (GTK_WIDGET (dialog)); // renice_dialog = NULL; // } //renice_scale_changed //renice_dialog_button_pressed } //void ProcessDialog::onCloseButtonClicked() //{ // this->close(); //} //void ProcessDialog::closeEvent(QCloseEvent *event) //{ // event->accept(); //} void ProcessDialog::refreshProcessList() { pid_t* pid_list; glibtop_proclist proclist; glibtop_cpu cpu; int which = 0; int arg = 0; if (whose_processes == "all") { which = GLIBTOP_KERN_PROC_ALL; arg = 0; } else if (whose_processes == "active") { which = GLIBTOP_KERN_PROC_ALL | GLIBTOP_EXCLUDE_IDLE; arg = 0; } else if (whose_processes == "user") { which = GLIBTOP_KERN_PROC_UID; arg = getuid(); } pid_list = glibtop_get_proclist(&proclist, which, arg); /* FIXME: total cpu time elapsed should be calculated on an individual basis here ** should probably have a total_time_last gint in the ProcInfo structure */ glibtop_get_cpu(&cpu); this->frequency = cpu.frequency; this->cpu_total_time = MAX(cpu.total - this->cpu_total_time_last, 1); this->cpu_total_time_last = cpu.total; // FIXME: not sure if glibtop always returns a sorted list of pid // but it is important otherwise refresh_list won't find the parent std::sort(pid_list, pid_list + proclist.number); //---------------start---------------------- typedef std::list ProcList; ProcList addition; guint i; for(i = 0; i < proclist.number; ++i) { ProcessWorker *info = ProcessWorker::find(pid_list[i]); if (!info) {//不存在时创建该进程的对象 info = new ProcessWorker(pid_list[i], this->num_cpus, this->cpu_total_time); ProcessWorker::all[info->pid] = info; } //当进程对象存在时,更新该进程对象的相关数据信息 glibtop_proc_state procstate; glibtop_proc_uid procuid; glibtop_proc_time proctime; glibtop_get_proc_state (&procstate, info->pid); info->status = procstate.state; glibtop_get_proc_uid (&procuid, info->pid); glibtop_get_proc_time (&proctime, info->pid); glibtop_proc_mem procmem; glibtop_get_proc_mem(&procmem, info->pid); info->mem = procmem.resident - procmem.share; glibtop_get_proc_state(&procstate, info->pid); info->status = procstate.state; info->set_user(procstate.uid); guint64 difference = proctime.rtime - info->cpu_time; if (difference > 0) info->status = GLIBTOP_PROCESS_RUNNING; info->pcpu = difference * 100 / this->cpu_total_time; info->pcpu = MIN(info->pcpu, 100); //CPU 百分比使用 Solaris 模式,工作在“Solaris 模式”,其中任务的 CPU 使用量将被除以总的 CPU 数目。否则它将工作在“Irix 模式”。 info->pcpu *= this->num_cpus; info->frequency = this->frequency; ProcessWorker::cpu_times[info->pid] = info->cpu_time = proctime.rtime; info->nice = procuid.nice; } // Remove dead processes from the process list and from the // tree. children are queued to be readded at the right place // in the tree. const std::set pids(pid_list, pid_list + proclist.number); ProcessWorker::Iterator it(ProcessWorker::begin()); while (it != ProcessWorker::end()) { ProcessWorker * const info = it->second; ProcessWorker::Iterator next(it); ++next; if (pids.find(info->pid) == pids.end()) { addition.remove(info); ProcessWorker::all.erase(it); delete info; } it = next; } QList items; for (ProcessWorker::Iterator it(ProcessWorker::begin()); it != ProcessWorker::end(); ++it) { QString username = QString::fromStdString(it->second->user); long nice = it->second->nice; QString name = QString::fromStdString(it->second->name); QString session; if (it->second->session) { session = QString(it->second->session); } QString status = formatProcessState(it->second->status); uint cpu = it->second->pcpu; long memory = it->second->mem; pid_t pid = it->second->pid; /*---------------------kobe test string--------------------- //QString to std:string QString test_QString = "lixiang"; std::string test_string = test_QString.toStdString(); //std::string to QString QString result = QString::fromStdString(test_string) QString::fromStdString(test_QString.toStdString()); ----------------------------------------------------------*/ std::string desktopFile; desktopFile = getDesktopFileFromName(pid, name, ""); // qDebug() << "****************"<< QString::fromStdString(desktopFile); QPixmap icon_pixmap; int iconSize = 24 * qApp->devicePixelRatio(); QPixmap defaultPixmap = QIcon::fromTheme("application-x-executable").pixmap(iconSize, iconSize); if (desktopFile.size() == 0) { icon_pixmap = defaultPixmap; icon_pixmap.setDevicePixelRatio(qApp->devicePixelRatio()); } else { icon_pixmap = getDesktopFileIcon(desktopFile, 24); if (icon_pixmap.isNull()) { icon_pixmap = defaultPixmap; icon_pixmap.setDevicePixelRatio(qApp->devicePixelRatio()); } //QPixmap pixmap = QPixmap::fromImage(img).scaled(iconSize, iconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); } QString title = getDisplayNameFromName(name, desktopFile); QString displayName; if (whose_processes == "all") { displayName = QString("[%1] %2").arg(username).arg(title); } else { displayName = title; } ProcData info; // info.pidt = it->second->pid; info.user = username; info.iconPixmap = icon_pixmap; info.displayName = displayName; info.cpu = cpu; info.m_memory = memory; info.pid = pid; info.m_status = status; info.m_nice = nice; info.m_session = session; info.cpu_duration_time = formatDurationForDisplay(100 * it->second->cpu_time / this->frequency); info.processName = QString::fromStdString(it->second->name); info.commandLine = QString::fromStdString(it->second->arguments); ProcessListItem *item = new ProcessListItem(info); items << item; } this->updateStatus(items); g_free (pid_list); //---------------end---------------------- } ProcessListWidget* ProcessDialog::getProcessView() { return m_processListWidget; } void ProcessDialog::endDialogButtonClicked(int index, QString) { if (index == 1) {//cancel:0 ok:1 endProcesses(); } } void ProcessDialog::killDialogButtonClicked(int index, QString) { if (index == 1) {//cancel:0 ok:1 killProcesses(); } } void ProcessDialog::focusProcessView() { QTimer::singleShot(100, m_processListWidget, SLOT(setFocus())); } void ProcessDialog::onSearch(QString text) { m_processListWidget->doSearch(text); } //杀死 void ProcessDialog::killProcesses() { int error; // int saved_errno; for (pid_t pid : *actionPids) { // Resume process first, otherwise kill process too slow. kill(pid, SIGCONT); // if (kill(pid, SIGKILL) != 0) { // qDebug() << QString("Kill process %1 failed, permission denied.").arg(pid); // } error = kill(pid, SIGKILL); if(error != -1) { qDebug() << "success....."; } else { //need to be root if(errno == EPERM) { qDebug() << QString("Kill process %1 failed, permission denied.").arg(pid); /*gboolean success; success = procdialog_create_root_password_dialog ( PROCMAN_ACTION_KILL, args->app, info->pid, args->arg_value); if(success) { actionPids->clear(); return; } if(errno) { saved_errno = errno; }*/ } } } actionPids->clear(); } //结束 void ProcessDialog::endProcesses() { int error; // int saved_errno; for (pid_t pid : *actionPids) { // if (kill(pid, SIGTERM) != 0) { // qDebug() << QString("Kill process %1 failed, permission denied.").arg(pid); // } error = kill(pid, SIGTERM); if(error != -1) { qDebug() << "success....."; } else { //need to be root if(errno == EPERM) { qDebug() << QString("End process %1 failed, permission denied.").arg(pid); /*gboolean success; success = procdialog_create_root_password_dialog ( PROCMAN_ACTION_KILL, args->app, info->pid, args->arg_value); if(success) { actionPids->clear(); return; } if(errno) { saved_errno = errno; }*/ } } } actionPids->clear(); } void ProcessDialog::popupMenu(QPoint pos, QList items) { actionPids->clear(); int count = 0; pid_t cur_pid = -1; for (ProcessListItem *item : items) { count ++; ProcessListItem *procItem = static_cast(item); cur_pid = procItem->getPid(); // qDebug() << "HAHAHAH===========" << cur_pid; actionPids->append(cur_pid); } /*if (count == 1) { ProcessWorker *info = ProcessWorker::find(cur_pid); if (!info) { priorityGroup->setActionsEnabled(false); } else { priorityGroup->setActionsEnabled(true); gint nice = info->nice; int priority; if (nice < -7) priority = -20; else if (nice < -2) priority = -5; else if (nice < 3) priority = 0; else if (nice < 7) priority = 5; else priority = 19; priorityGroup->setChecked(priority); } } else { priorityGroup->setActionsEnabled(false); }*/ m_menu->exec(pos); } void ProcessDialog::continueProcesses() { for (pid_t pid : *actionPids) { if (kill(pid, SIGCONT) != 0) { qDebug() << QString("Resume process %1 failed, permission denied.").arg(pid); } } actionPids->clear(); } void ProcessDialog::showPropertiesDialog() { for (pid_t pid : *actionPids) { foreach (QWidget *widget, QApplication::topLevelWidgets()) { // Show attribute dialog if it has create, avoid create attribute dialog duplicate. if (qobject_cast(widget) != 0) { PropertiesDialog *dialog = qobject_cast(widget); if (dialog->getPid() == pid) { dialog->show(); actionPids->clear(); return; } } } PropertiesDialog *dialog = new PropertiesDialog(this, pid); dialog->show(); } actionPids->clear(); } void ProcessDialog::showKillProcessDialog() { killProcessDialog->exec(); } void ProcessDialog::showEndProcessDialog() { endProcessDialog->exec(); } //停止 void ProcessDialog::stopProcesses() { pid_t currentPid = getpid(); for (pid_t pid : *actionPids) { if (pid != currentPid) { if (kill(pid, SIGSTOP) != 0) { qDebug() << QString("Stop process %1 failed, permission denied.").arg(pid); } } } actionPids->clear(); } void ProcessDialog::updateStatus(QList items) { m_processListWidget->refreshItems(items); } /*void ProcessDialog::paintEvent(QPaintEvent *event) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); //绘制背景色 // QPainterPath path; // path.addRect(QRectF(rect())); // painter.setOpacity(1); // painter.fillPath(path, QColor("#FFFFFF")); //绘制圆角矩形 // painter.setPen(QPen(QColor("#0d87ca"), 0));//边框颜色 QColor(255, 255, 255, 153) // painter.setBrush(QColor("#e9eef0"));//背景色 #0d87ca painter.setPen(QPen(QColor("#0000FF"), 0));//边框颜色 QColor(255, 255, 255, 153) painter.setBrush(QColor("#B22222"));//背景色 #0d87ca painter.setOpacity(1); // QRectF r(0 / 2.0, 0 / 2.0, width() - 0, height() - 0);//左边 上边 右边 下边 QRectF r(2, 2, width() - 0, height() - 0);//左边 上边 右边 下边 painter.drawRoundedRect(r, 10, 10); QWidget::paintEvent(event); }*/