Predstavljamo Vam Gemini Browser, kompletan veb-pregledač nastao iz ambicioznog projekta, a usavršen u stabilnu i funkcionalnu aplikaciju. Izgrađen na temeljima Pythona i Qt WebEngine-a, Gemini Browser donosi sve što očekujete od modernog pregledača, plus niz alata fokusiranih na napredne korisnike i programere.
Zahvaljujući ključnim prepravkama, ovaj brauzer rešava uobičajene probleme s medijima i nudi neviđen nivo kontrole nad podacima i preuzimanjima.
✨ Ključne Funkcionalnosti: Preglednost i Prilagodljivost
Gemini Browser je dizajniran za efikasno i udobno pregledanje sa sledećim osnovnim karakteristikama:
- Napredno Upravljanje Karticama: Jednostavno otvarajte, zatvarajte i premestite kartice. Kritična stabilnost je postignuta: svi linkovi koji se otvaraju u novom prozoru (
target="_blank") sada se ispravno otvaraju u novoj kartici, obezbeđujući neprekidan tok rada. - Tamni i Svetli Režim (Light/Dark Mode): Prebacite se između klasičnog i tamnog stila za udobno pregledanje u svim svetlosnim uslovima.
- Istorija i Obeleživači: Istorija pregledanja čuva se u lokalnoj SQLite bazi, omogućavajući vam da se lako vratite na prethodno posećene stranice. Dodavanje obeleživača je dostupno jednim klikom.
- URL Navigacija: URL traka služi i kao brza pretraga, automatski preusmeravajući na Bing za unete termine.
- Command Palette (Ctrl+K): Za napredne korisnike, brza komandna paleta za pokretanje funkcionalnosti kucanjem komande (npr. “new tab”, “toggle dark mode”).
🛡️ Bezbednost i Kontrola Podataka
Potpuna kontrola nad podacima i privatnošću je prioritet:
- Privatna Kartica: Otvorite karticu u posebnom profilu, osiguravajući da se ne čuvaju istorija, kolačići ili keš iz te sesije, idealno za privatno pregledanje.
- Potpuno Čišćenje Podataka: Jednim klikom možete obrisati celokupni keš, kolačiće (cookies) i podatke sa sajtova. Ovo je ključno za rešavanje problema sa prijavom ili za brzo osvežavanje privatnosti.
- Fleksibilan User-Agent: Promenite identitet Vašeg pretraživača! Možete birati između unapred definisanih
User-Agentstringova (npr. Chrome Windows, Firefox Linux) kako biste simulirali pregledanje s druge platforme.
🛠️ Alati za Programere i Napredne Korisnike
Ove funkcije izdvajaju Gemini Browser kao moćan alat za programere i one koji zahtevaju više od standardnog pretraživača:
1. Developer Tools (F12)
Kao i kod drugih modernih brauzera, pritisnite F12 da biste otvorili namenski prozor Developer Tools. Ovaj alat je neophodan za:
- Inspekciju i uređivanje HTML, CSS i JavaScript koda.
- Debagovanje koda i praćenje performansi.
- Analizu mrežnih zahteva (Network Traffic).
2. Upravljanje Preuzimanjem (Download Manager)
Za razliku od standardnih pretraživača, Gemini Browser ima integrisan, robustan Download Manager:
- Kontrola Putanje: Pri svakom preuzimanju, brauzer traži da ručno izaberete putanju i ime fajla.
- Detaljno Praćenje: U prozoru Download Managera pratite status (u toku, završeno, otkazano), progres u procentima i imate opciju da otkažete aktivne downloade.
3. Printanje u PDF (Ctrl+P)
Sačuvajte bilo koju veb-stranicu kao PDF fajl jednim klikom (Ctrl+P), omogućavajući lako arhiviranje ili štampanje.
🎬 Rešavanje Problema sa Medijima i DRM-om
Gemini Browser je optimizovan za rad sa savremenim veb-sajtovima, uključujući servise za striming:
- Widevine DRM Podrška: Brauzer koristi napredni
User-Agenti profil podešavanja za podršku Widevine DRM-a, što je ključno za gledanje sadržaja na platformama kao što su Netflix, HBO Max i YouTube TV. - Popravka DRM Problema: Ako imate problema sa puštanjem zaštićenog sadržaja, tu su specijalne akcije unutar Developer Tools trake:
- “Popravi DRM Probleme” čisti keš i kolačiće za autentifikaciju.
- “Testiraj DRM Podršku” otvara liste poznatih test stranica kako biste brzo proverili da li Vaša instalacija podržava potrebne standarde.

Gemini Browser predstavlja savršenu kombinaciju funkcionalnosti, kontrole i naprednih alata, čineći ga idealnim izborom za svakodnevno pregledanje i razvoj.
Programski kod za GeminiBrowser.py
#GeminiBrowser.py
# instalacija neophodnih zavisnosti: pip install PyQt6 PyQt6-WebEngine
import sys
import sqlite3
import os
import subprocess
from PyQt6.QtCore import QUrl, Qt, QTimer, QCoreApplication
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QTabWidget,
QLineEdit, QPushButton, QToolBar, QStatusBar, QMenu, QInputDialog,
QDockWidget, QTextEdit, QLabel, QListWidget, QListWidgetItem, QDialog,
QGridLayout, QSizePolicy, QStyle, QProgressBar, QMessageBox, QFileDialog,
QToolButton
)
from PyQt6.QtGui import (
QIcon,
QAction,
QGuiApplication,
QKeySequence
)
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWebEngineCore import QWebEngineProfile, QWebEnginePage, QWebEngineDownloadRequest
from PyQt6.QtWebEngineCore import QWebEngineSettings
from PyQt6.QtNetwork import QSslConfiguration
# --- QSS Stilovi za teme ---
LIGHT_STYLE = """
QMainWindow, QWidget {
background-color: #f0f0f0;
color: #000000;
}
QTabWidget::pane {
border: 1px solid #c0c0c0;
background-color: #ffffff;
}
QTabBar::tab {
background-color: #e0e0e0;
color: #000000;
padding: 8px 12px;
margin-right: 2px;
border: 1px solid #c0c0c0;
border-bottom: none;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
QTabBar::tab:selected {
background-color: #ffffff;
border-color: #c0c0c0;
}
QLineEdit {
padding: 6px;
border: 1px solid #c0c0c0;
border-radius: 4px;
background-color: #ffffff;
color: #000000;
}
QToolBar {
background-color: #e8e8e8;
border: none;
spacing: 3px;
padding: 2px;
}
QStatusBar {
background-color: #e8e8e8;
color: #000000;
}
QMenu {
background-color: #ffffff;
color: #000000;
border: 1px solid #c0c0c0;
}
QMenu::item:selected {
background-color: #0078d4;
color: #ffffff;
}
"""
DARK_STYLE = """
/* Glavni Prozor */
QMainWindow, QWidget {
background-color: #2e2e2e;
color: #ffffff;
}
QTabWidget::pane {
border: 1px solid #555555;
background-color: #3c3c3c;
}
QTabBar::tab {
background-color: #404040;
color: #ffffff;
padding: 8px 12px;
margin-right: 2px;
border: 1px solid #555555;
border-bottom: none;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
QTabBar::tab:selected {
background-color: #505050;
border-color: #666666;
}
QLineEdit {
padding: 6px;
border: 1px solid #555555;
border-radius: 4px;
background-color: #404040;
color: #ffffff;
}
QToolBar {
background-color: #363636;
border: none;
spacing: 3px;
padding: 2px;
}
QStatusBar {
background-color: #363636;
color: #ffffff;
}
QMenu {
background-color: #404040;
color: #ffffff;
border: 1px solid #555555;
}
QMenu::item:selected {
background-color: #0078d4;
color: #ffffff;
}
QDockWidget {
background-color: #3c3c3c;
color: #ffffff;
titlebar-close-icon: url(close_white.png);
titlebar-normal-icon: url(float_white.png);
}
QDockWidget::title {
background-color: #505050;
padding: 6px;
text-align: center;
}
QTextEdit {
background-color: #404040;
color: #ffffff;
border: 1px solid #555555;
}
QLabel {
color: #ffffff;
}
QProgressBar {
border: 1px solid #555555;
border-radius: 4px;
text-align: center;
color: #ffffff;
}
QProgressBar::chunk {
background-color: #0078d4;
}
"""
# --- Klasa za Prikaz Web Sadržaja (WebView) ---
class WebView(QWebEngineView):
def __init__(self, main_window, parent=None, profile=None):
super().__init__(parent)
self.main_window = main_window
# Modern User-Agent koji podržava GSI i DRM
chrome_version = "120.0.0.0"
user_agent = f"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{chrome_version} Safari/537.36"
# Kreiraj WebPage pre nego što pristupiš page()
if profile:
self.web_page = WebPage(profile, self)
self.setPage(self.web_page)
else:
self.web_page = self.page()
self.web_page.profile().setHttpUserAgent(user_agent)
# Napredna podešavanja za DRM i medije
settings = self.web_page.settings()
settings.setAttribute(QWebEngineSettings.WebAttribute.JavascriptEnabled, True)
settings.setAttribute(QWebEngineSettings.WebAttribute.LocalStorageEnabled, True)
settings.setAttribute(QWebEngineSettings.WebAttribute.PlaybackRequiresUserGesture, False)
settings.setAttribute(QWebEngineSettings.WebAttribute.AllowWindowActivationFromJavaScript, True)
settings.setAttribute(QWebEngineSettings.WebAttribute.WebRTCPublicInterfacesOnly, False)
settings.setAttribute(QWebEngineSettings.WebAttribute.AllowRunningInsecureContent, True)
settings.setAttribute(QWebEngineSettings.WebAttribute.DnsPrefetchEnabled, True)
settings.setAttribute(QWebEngineSettings.WebAttribute.PdfViewerEnabled, True)
# Omogući Widevine DRM
settings.setAttribute(QWebEngineSettings.WebAttribute.PlaybackRequiresUserGesture, False)
# Poveži signale
self.urlChanged.connect(self.handle_url_changed)
self.titleChanged.connect(self.handle_title_changed)
self.web_page.linkHovered.connect(self.handle_link_hovered)
def createWindow(self, type):
new_browser = self.main_window.add_new_tab(QUrl('about:blank'), initial_title="Učitavanje...")
return new_browser
def handle_url_changed(self, url):
self.main_window.update_url_bar_for_current_tab(url)
def handle_title_changed(self, title):
self.main_window.update_tab_title(self, title)
def handle_link_hovered(self, url):
self.main_window.update_status_bar_link(url)
# --- Ispravljena Klasa za rukovanje novim prozorima ---
class WebPage(QWebEnginePage):
def __init__(self, profile, parent_web_view):
super().__init__(profile, parent_web_view)
self.parent_web_view = parent_web_view
self.main_window = parent_web_view.main_window
def createWindow(self, type):
# Kreiraj novu karticu i vrati njen WebPage
new_browser = self.main_window.add_new_tab(QUrl('about:blank'), "Nova Kartica")
return new_browser.page()
# --- Klasa za Prikaz Istorije ---
class HistoryDialog(QDialog):
def __init__(self, main_window):
super().__init__(main_window)
self.setWindowTitle("Istorija Pregledanja")
self.main_window = main_window
self.setGeometry(200, 200, 600, 400)
self.list_widget = QListWidget()
self.list_widget.itemDoubleClicked.connect(self.navigate_from_history)
layout = QGridLayout(self)
layout.addWidget(self.list_widget, 0, 0)
self._load_history_items()
def _load_history_items(self):
self.list_widget.clear()
history_items = self.main_window.get_history()
if not history_items:
QListWidgetItem("Istorija je prazna.", self.list_widget)
return
for title, url, date in history_items:
item_text = f"[{date[:16]}] {title} - ({url})"
item = QListWidgetItem(item_text, self.list_widget)
item.setData(Qt.ItemDataRole.UserRole, url)
def navigate_from_history(self, item):
url = item.data(Qt.ItemDataRole.UserRole)
if url:
self.main_window.add_new_tab(QUrl(url))
self.accept()
# --- Klasa za Download Manager ---
class DownloadManager(QDialog):
def __init__(self, main_window):
super().__init__(main_window)
self.main_window = main_window
self.setWindowTitle("Download Manager")
self.setGeometry(300, 300, 700, 400)
self.downloads = []
layout = QVBoxLayout(self)
# Lista downloadova
self.download_list = QListWidget()
layout.addWidget(self.download_list)
# Progress bar
self.progress_bar = QProgressBar()
self.progress_bar.setVisible(False)
layout.addWidget(self.progress_bar)
# Dugmadi
button_layout = QGridLayout()
self.open_folder_btn = QPushButton("Otvori Folder")
self.open_folder_btn.clicked.connect(self.open_download_folder)
button_layout.addWidget(self.open_folder_btn, 0, 0)
self.clear_btn = QPushButton("Očisti Listu")
self.clear_btn.clicked.connect(self.clear_finished_downloads)
button_layout.addWidget(self.clear_btn, 0, 1)
self.cancel_btn = QPushButton("Otkaži Download")
self.cancel_btn.clicked.connect(self.cancel_download)
button_layout.addWidget(self.cancel_btn, 1, 0)
self.close_btn = QPushButton("Zatvori")
self.close_btn.clicked.connect(self.close)
button_layout.addWidget(self.close_btn, 1, 1)
layout.addLayout(button_layout)
def add_download(self, download_item):
self.downloads.append(download_item)
self.update_download_list()
def update_download_list(self):
self.download_list.clear()
for download in self.downloads:
status_text = self.get_status_text(download)
item_text = f"{download['filename']} - {status_text}"
item = QListWidgetItem(item_text)
# Boja prema statusu
if download['status'] == 'Završeno':
item.setBackground(Qt.GlobalColor.green)
elif download['status'] == 'Greška':
item.setBackground(Qt.GlobalColor.red)
elif download['status'] == 'Otkazano':
item.setBackground(Qt.GlobalColor.gray)
self.download_list.addItem(item)
def get_status_text(self, download):
if download['status'] == 'Preuzimanje':
if download['total'] > 0:
progress = (download['received'] / download['total']) * 100
return f"Preuzimanje {progress:.1f}%"
else:
return "Preuzimanje..."
return download['status']
def open_download_folder(self):
downloads_path = os.path.expanduser("~/Downloads")
if os.path.exists(downloads_path):
if sys.platform == 'win32':
os.startfile(downloads_path)
elif sys.platform == 'darwin': # macOS
os.system(f'open "{downloads_path}"')
elif sys.platform.startswith('linux'): # Linux
os.system(f'xdg-open "{downloads_path}"')
else:
QMessageBox.warning(self, "Greška", "Nepoznat OS. Otvorite folder ručno.")
else:
QMessageBox.warning(self, "Greška", "Downloads folder ne postoji!")
def clear_finished_downloads(self):
self.downloads = [d for d in self.downloads if d['status'] not in ['Završeno', 'Greška', 'Otkazano']]
self.update_download_list()
def cancel_download(self):
current_row = self.download_list.currentRow()
if 0 <= current_row < len(self.downloads):
download = self.downloads[current_row]
if 'download_obj' in download and download['status'] == 'Preuzimanje':
download['download_obj'].cancel()
download['status'] = 'Otkazano'
self.update_download_list()
# --- Glavni Prozor Pregledača ---
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Gemini Browser - Pobeda je Naša!")
self.setGeometry(100, 100, 1000, 700)
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
# Napredna konfiguracija profila za DRM
self.default_profile = QWebEngineProfile.defaultProfile()
self.private_profile = QWebEngineProfile('privateProfile', self)
# Omogući persistent cookies za autentifikaciju
self.default_profile.setPersistentCookiesPolicy(
QWebEngineProfile.PersistentCookiesPolicy.AllowPersistentCookies
)
self.private_profile.setPersistentCookiesPolicy(
QWebEngineProfile.PersistentCookiesPolicy.NoPersistentCookies
)
# Podesi cache i storage putanje
self.default_profile.setHttpCacheType(QWebEngineProfile.HttpCacheType.DiskHttpCache)
cache_path = os.path.expanduser("~/.cache/gemini-browser")
os.makedirs(cache_path, exist_ok=True)
self.default_profile.setCachePath(cache_path)
self.default_profile.setPersistentStoragePath(cache_path)
# Podesi download folder
self.download_path = os.path.expanduser("~/Downloads")
self.default_profile.setDownloadPath(self.download_path)
self.private_profile.setDownloadPath(self.download_path)
# Referenca za Developer Tools
self.dev_tools_view = None
self.current_dev_tools_browser = None
self.is_dark_mode = False
self.apply_style(LIGHT_STYLE)
self.db_name = 'gemini_browser.db'
self._setup_database()
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.layout = QVBoxLayout(self.central_widget)
self.tabs = QTabWidget()
self.tabs.setTabsClosable(True)
self.tabs.setMovable(True)
self.tabs.tabCloseRequested.connect(self.close_tab)
self.tabs.currentChanged.connect(self.current_tab_changed)
self.layout.addWidget(self.tabs)
self.url_input = QLineEdit()
self.url_input.returnPressed.connect(self.navigate_to_url)
self.navigation_toolbar = QToolBar("Navigacija")
self.addToolBar(self.navigation_toolbar)
self._setup_navigation_actions()
self.navigation_toolbar.addWidget(self.url_input)
self.extra_toolbar = QToolBar("Alati")
self.addToolBar(Qt.ToolBarArea.RightToolBarArea, self.extra_toolbar)
self._setup_extra_actions()
self._setup_ai_summary_panel()
self.download_manager = DownloadManager(self)
self.command_map = self._setup_command_map()
self.status_bar.showMessage("Pritisnite Ctrl+K za Command Palette")
self._setup_download_handlers()
# Dodaj akcije za rešavanje problema sa videom
self._setup_video_fix_actions()
self.add_new_tab(QUrl("https://abel.rs/zx"), "Google")
# Metoda za pokretanje htmleditor.py sa trenutnim URL-om
def start_htmleditor(self):
try:
current_browser = self.tabs.currentWidget().findChild(WebView)
if not current_browser:
QMessageBox.warning(self, "Greška", "Nema aktivne kartice.")
return
current_url = current_browser.url().toString()
script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "htmleditor.py")
if os.path.exists(script_path):
if sys.platform == "win32":
subprocess.Popen([sys.executable, script_path, current_url], creationflags=subprocess.CREATE_NEW_CONSOLE)
else:
subprocess.Popen([sys.executable, script_path, current_url])
self.status_bar.showMessage(f"Pokrenut HTML Editor sa URL: {current_url}", 3000)
else:
QMessageBox.warning(self, "Greška", f"htmleditor.py nije pronađen na putanji: {script_path}")
except Exception as e:
QMessageBox.critical(self, "Greška", f"Ne mogu pokrenuti htmleditor.py: {str(e)}")
# Metoda za pokretanje xuvd.py
def start_xuvd(self):
try:
script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "xuvd.py")
if os.path.exists(script_path):
if sys.platform == "win32":
subprocess.Popen([sys.executable, script_path], creationflags=subprocess.CREATE_NEW_CONSOLE)
else:
subprocess.Popen([sys.executable, script_path])
self.status_bar.showMessage("Pokrenut XUVD", 3000)
else:
QMessageBox.warning(self, "Greška", f"xuvd.py nije pronađen na putanji: {script_path}")
except Exception as e:
QMessageBox.critical(self, "Greška", f"Ne mogu pokrenuti xuvd.py: {str(e)}")
# Metoda za popravku video problema
def _setup_video_fix_actions(self):
"""Dodaje specijalne akcije za rešavanje problema sa videom"""
style = QApplication.instance().style()
# Akcija za popravku DRM problema
drm_fix_btn = QAction(QIcon.fromTheme("emblem-readonly"), "Popravi DRM Probleme", self)
drm_fix_btn.triggered.connect(self.fix_drm_issues)
self.extra_toolbar.addAction(drm_fix_btn)
# Akcija za testiranje Widevine DRM
test_drm_btn = QAction(QIcon.fromTheme("system-search"), "Testiraj DRM Podršku", self)
test_drm_btn.triggered.connect(self.test_drm_support)
self.extra_toolbar.addAction(test_drm_btn)
# DODATAK: Dugme za pokretanje HTML Editora
htmleditor_btn = QAction(QIcon.fromTheme("text-html"), "Pokreni HTML Editor", self)
htmleditor_btn.triggered.connect(self.start_htmleditor)
self.extra_toolbar.addAction(htmleditor_btn)
# DODATAK: Dugme za pokretanje XUVD
xuvd_btn = QAction(QIcon.fromTheme("video-display"), "Pokreni XUVD", self)
xuvd_btn.triggered.connect(self.start_xuvd)
self.extra_toolbar.addAction(xuvd_btn)
def fix_drm_issues(self):
"""Popravlja DRM i autentifikacione probleme"""
try:
# Očisti sve podatke koji mogu da ometaju DRM
self.default_profile.clearHttpCache()
cookie_store = self.default_profile.cookieStore()
cookie_store.deleteAllCookies()
# Osveži trenutnu karticu
current_browser = self.tabs.currentWidget().findChild(WebView)
if current_browser:
current_browser.reload()
QMessageBox.information(
self,
"DRM Popravka",
"DRM problemi su popravljeni.\n\n"
"Sada pokušajte ponovo da pustite video.\n"
"Ako problem i dalje postoji, koristite 'Testiraj DRM Podršku'."
)
self.status_bar.showMessage("DRM problemi popravljeni", 5000)
except Exception as e:
QMessageBox.warning(self, "Greška", f"Došlo je do greške pri popravci: {str(e)}")
def test_drm_support(self):
"""Otvara testnu stranicu za proveru DRM podrške"""
test_urls = {
"Widevine DRM Test": "https://bitmovin.com/demos/drm",
"YouTube TV": "https://www.youtube.com/tv",
"HTML5 Video Test": "https://www.html5test.com/",
"DRM Info Test": "https://www.widevine.com/drm-info"
}
test_name, ok = QInputDialog.getItem(
self, "Test DRM Podrške", "Izaberi test stranicu:", list(test_urls.keys()), 0, False
)
if ok and test_name:
test_url = test_urls[test_name]
self.add_new_tab(QUrl(test_url), f"DRM Test - {test_name}")
self.status_bar.showMessage(f"Otvaram {test_name} za testiranje DRM podrške", 3000)
def _setup_extra_actions(self):
style = QApplication.instance().style()
# AKCIJA 1: Developer Tools (Shortcut F12)
dev_tools_btn = QAction(QIcon.fromTheme("utilities-terminal"), "Developer Tools (F12)", self)
dev_tools_btn.setShortcut(Qt.Key.Key_F12)
dev_tools_btn.triggered.connect(self.show_dev_tools)
self.extra_toolbar.addAction(dev_tools_btn)
self.addAction(dev_tools_btn)
# AKCIJA 2: Print u PDF (Shortcut Ctrl+P)
print_pdf_btn = QAction(QIcon.fromTheme("application-pdf"), "Štampaj u PDF (Ctrl+P)", self)
print_pdf_btn.setShortcut(QKeySequence("Ctrl+P"))
print_pdf_btn.triggered.connect(self.print_to_pdf)
self.extra_toolbar.addAction(print_pdf_btn)
self.addAction(print_pdf_btn)
# NOVA AKCIJA: Promena User-Agent-a
user_agent_btn = QAction(QIcon.fromTheme("preferences-system-network"), "Promeni User-Agent", self)
user_agent_btn.triggered.connect(self.change_user_agent)
self.extra_toolbar.addAction(user_agent_btn)
# NOVA AKCIJA: Očisti podatke pregledača
clear_data_btn = QAction(QIcon.fromTheme("edit-clear"), "Očisti podatke", self)
clear_data_btn.triggered.connect(self.clear_browser_data)
self.extra_toolbar.addAction(clear_data_btn)
def show_dev_tools(self):
current_browser = self.tabs.currentWidget().findChild(WebView)
if not current_browser:
self.status_bar.showMessage("Nema aktivne kartice.", 3000)
return
# Proveri da li Developer Tools već postoji i da li je za istu karticu
if (self.dev_tools_view is not None and
self.current_dev_tools_browser == current_browser and
self.dev_tools_view.isVisible()):
# Ako je Developer Tools već otvoren za ovu karticu, samo ga fokusiraj
self.dev_tools_view.raise_()
self.dev_tools_view.activateWindow()
self.status_bar.showMessage("Developer Tools je već otvoren za ovu karticu.", 3000)
return
# Ako Developer Tools postoji ali je za drugu karticu, zatvori ga
if self.dev_tools_view is not None:
try:
# Očisti prethodne konekcije
if self.current_dev_tools_browser:
try:
self.current_dev_tools_browser.titleChanged.disconnect(self._update_dev_tools_title)
self.current_dev_tools_browser.urlChanged.disconnect(self._update_dev_tools_url)
except:
pass
self.dev_tools_view.close()
self.dev_tools_view.deleteLater()
except:
pass
self.dev_tools_view = None
self.current_dev_tools_browser = None
# Kreiraj novi Developer Tools za trenutnu karticu
try:
self.dev_tools_view = QWebEngineView()
self.current_dev_tools_browser = current_browser
current_title = current_browser.title() or current_browser.url().toString()
self.dev_tools_view.setWindowTitle(f"Gemini Browser - Developer Tools - {current_title}")
self.dev_tools_view.resize(800, 600)
# Poveži Developer Tools sa TRENUTNOM karticom
current_browser.page().setDevToolsPage(self.dev_tools_view.page())
# Poveži signale za ažuriranje naslova
current_browser.titleChanged.connect(self._update_dev_tools_title)
current_browser.urlChanged.connect(self._update_dev_tools_url)
# Poveži signal za zatvaranje Developer Tools
self.dev_tools_view.destroyed.connect(self._clear_dev_tools_reference)
self.dev_tools_view.show()
self.status_bar.showMessage("Otvorene Developer Tools za trenutnu karticu.", 3000)
except Exception as e:
QMessageBox.warning(self, "Greška", f"Ne mogu otvoriti Developer Tools: {str(e)}")
if self.dev_tools_view:
self.dev_tools_view = None
self.current_dev_tools_browser = None
def _update_dev_tools_title(self, title):
"""Ažurira naslov Developer Tools kada se promeni naslov stranice"""
if self.dev_tools_view and self.dev_tools_view.isVisible():
try:
current_browser = self.tabs.currentWidget().findChild(WebView)
if current_browser and current_browser == self.current_dev_tools_browser:
url = current_browser.url().toString()
self.dev_tools_view.setWindowTitle(f"Gemini Browser - Developer Tools - {title} ({url})")
except:
pass
def _update_dev_tools_url(self, url):
"""Ažurira naslov Developer Tools kada se promeni URL"""
if self.dev_tools_view and self.dev_tools_view.isVisible():
try:
current_browser = self.tabs.currentWidget().findChild(WebView)
if current_browser and current_browser == self.current_dev_tools_browser:
title = current_browser.title() or "Nema naslova"
self.dev_tools_view.setWindowTitle(f"Gemini Browser - Developer Tools - {title}")
except:
pass
def _clear_dev_tools_reference(self):
"""Očisti referencu na Developer Tools kada se zatvori"""
try:
if self.current_dev_tools_browser:
try:
self.current_dev_tools_browser.titleChanged.disconnect(self._update_dev_tools_title)
self.current_dev_tools_browser.urlChanged.disconnect(self._update_dev_tools_url)
except:
pass
self.current_dev_tools_browser = None
except:
pass
self.dev_tools_view = None
def print_to_pdf(self):
current_browser = self.tabs.currentWidget().findChild(WebView)
if not current_browser:
self.status_bar.showMessage("Nema aktivne kartice za štampanje.", 3000)
return
# Koristi naslov stranice za ime fajla
page_title = current_browser.title() or "stranica"
# Očisti naslov od nevalidnih karaktera za ime fajla
safe_title = "".join(c for c in page_title if c.isalnum() or c in (' ', '-', '_')).rstrip()
if not safe_title:
safe_title = "stranica"
suggested_filename = f"{safe_title}.pdf"
# Otvori dijalog za čuvanje fajla
file_path, _ = QFileDialog.getSaveFileName(
self,
"Sačuvaj kao PDF...",
os.path.join(os.path.expanduser("~/Documents"), suggested_filename),
"PDF Files (*.pdf)"
)
if file_path:
# Asinhrono štampanje u PDF
def handle_pdf_finished(file_path, success):
if success:
self.status_bar.showMessage(f"✅ Stranica '{page_title}' sačuvana kao PDF: {file_path}", 5000)
else:
self.status_bar.showMessage("❌ Greška pri štampanju u PDF.", 5000)
current_browser.page().printToPdf(file_path)
current_browser.page().pdfPrintingFinished.connect(handle_pdf_finished)
self.status_bar.showMessage(f"Štampam '{page_title}' u PDF, molimo pričekajte...", 3000)
# NOVA METODA: Promena User-Agent-a
def change_user_agent(self):
agents = {
"Chrome Windows": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Chrome Linux": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Firefox Windows": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
"Firefox Linux": "Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0",
"Safari macOS": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15"
}
agent_name, ok = QInputDialog.getItem(
self, "Promeni User-Agent", "Izaberi User-Agent:", list(agents.keys()), 0, False
)
if ok and agent_name:
new_agent = agents[agent_name]
self.default_profile.setHttpUserAgent(new_agent)
self.private_profile.setHttpUserAgent(new_agent)
# Ažuriraj sve postojeće kartice
for index in range(self.tabs.count()):
tab_widget = self.tabs.widget(index)
if tab_widget:
browser = tab_widget.findChild(WebView)
if browser:
browser.page().profile().setHttpUserAgent(new_agent)
self.status_bar.showMessage(f"User-Agent promenjen na: {agent_name}", 5000)
QMessageBox.information(self, "User-Agent promenjen",
f"User-Agent je promenjen na: {agent_name}\n\n"
f"Promena će biti primenjena na nove stranice.")
# NOVA METODA: Očisti podatke pregledača
def clear_browser_data(self):
reply = QMessageBox.question(
self,
"Očisti podatke pregledača",
"Da li želite da obrišete cache, cookies i ostale podatke pregledača?\n\n"
"Ovo će vas odjaviti sa sajtova i obrisati privremene podatke.",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
try:
# Očisti cache
self.default_profile.clearHttpCache()
self.private_profile.clearHttpCache()
# Očisti sve cookies
cookie_store = self.default_profile.cookieStore()
cookie_store.deleteAllCookies()
private_cookie_store = self.private_profile.cookieStore()
private_cookie_store.deleteAllCookies()
# Očisti visited links
self.default_profile.clearAllVisitedLinks()
# Očisti storage
self.default_profile.clearAllVisitedLinks()
QMessageBox.information(self, "Uspešno", "Podaci pregledača su očišćeni.\n\nStranice će se ponovo učitati.")
self.status_bar.showMessage("Podaci pregledača očišćeni", 5000)
# Osveži trenutnu karticu
current_browser = self.tabs.currentWidget().findChild(WebView)
if current_browser:
current_browser.reload()
except Exception as e:
QMessageBox.warning(self, "Greška", f"Došlo je do greške pri čišćenju: {str(e)}")
def _setup_navigation_actions(self):
style = QApplication.instance().style()
back_btn = QAction(style.standardIcon(QStyle.StandardPixmap.SP_ArrowBack), "Nazad", self)
back_btn.triggered.connect(lambda: self.tabs.currentWidget().findChild(WebView).back())
self.navigation_toolbar.addAction(back_btn)
forward_btn = QAction(style.standardIcon(QStyle.StandardPixmap.SP_ArrowForward), "Napred", self)
forward_btn.triggered.connect(lambda: self.tabs.currentWidget().findChild(WebView).forward())
self.navigation_toolbar.addAction(forward_btn)
reload_btn = QAction(style.standardIcon(QStyle.StandardPixmap.SP_BrowserReload), "Osveži", self)
reload_btn.triggered.connect(lambda: self.tabs.currentWidget().findChild(WebView).reload())
self.navigation_toolbar.addAction(reload_btn)
home_btn = QAction(QIcon.fromTheme("go-home"), "Početna", self)
home_btn.triggered.connect(self.navigate_home)
self.navigation_toolbar.addAction(home_btn)
self.navigation_toolbar.addSeparator()
# Dugme za novu karticu sa ikonkom "+"
new_tab_btn = QAction(QIcon.fromTheme("list-add"), "Nova Kartica", self)
new_tab_btn.triggered.connect(lambda: self.add_new_tab(QUrl("about:blank"), "Nova Kartica"))
self.navigation_toolbar.addAction(new_tab_btn)
private_tab_btn = QAction(QIcon.fromTheme("system-lock-screen"), "Nova Privatna Kartica", self)
private_tab_btn.triggered.connect(lambda: self.add_new_tab(QUrl("about:blank"), "Privatna", is_private=True))
self.navigation_toolbar.addAction(private_tab_btn)
self.navigation_toolbar.addSeparator()
bookmark_btn = QAction(style.standardIcon(QStyle.StandardPixmap.SP_DialogYesButton), "Dodaj obeleživač", self)
bookmark_btn.triggered.connect(self.add_bookmark)
self.navigation_toolbar.addAction(bookmark_btn)
history_btn = QAction(style.standardIcon(QStyle.StandardPixmap.SP_FileDialogDetailedView), "Istorija", self)
history_btn.triggered.connect(self.show_history_dialog)
self.navigation_toolbar.addAction(history_btn)
download_btn = QAction(style.standardIcon(QStyle.StandardPixmap.SP_DriveHDIcon), "Download Manager", self)
download_btn.triggered.connect(self.show_download_manager)
self.navigation_toolbar.addAction(download_btn)
self.navigation_toolbar.addSeparator()
ai_summary_btn = QAction(style.standardIcon(QStyle.StandardPixmap.SP_FileDialogInfoView), "Gemini Sažetak", self)
ai_summary_btn.triggered.connect(self.generate_gemini_summary)
self.navigation_toolbar.addAction(ai_summary_btn)
palette_shortcut = QAction("Command Palette", self)
palette_shortcut.setShortcut(QKeySequence("Ctrl+K"))
palette_shortcut.triggered.connect(self.show_command_palette)
self.addAction(palette_shortcut)
# --- Metode za Download Manager ---
def show_download_manager(self):
self.download_manager.exec()
def _setup_download_handlers(self):
self.default_profile.downloadRequested.connect(self._handle_download)
self.private_profile.downloadRequested.connect(self._handle_download)
def _handle_download(self, download):
try:
suggested_filename = download.downloadFileName()
file_path, _ = QFileDialog.getSaveFileName(
self,
f"Sačuvaj fajl kao...",
os.path.join(self.download_path, suggested_filename),
"All Files (*.*)"
)
if file_path:
download.setDownloadDirectory(os.path.dirname(file_path))
download.setDownloadFileName(os.path.basename(file_path))
download_item = {
'filename': suggested_filename,
'path': file_path,
'status': 'Preuzimanje',
'received': 0,
'total': 0,
'download_obj': download
}
self.download_manager.add_download(download_item)
download.receivedBytesChanged.connect(
lambda: self._update_download_progress(download_item)
)
download.totalBytesChanged.connect(
lambda: self._update_download_progress(download_item)
)
download.isFinishedChanged.connect(
lambda: self._download_finished(download_item)
)
download.stateChanged.connect(
lambda state: self._download_state_changed(download_item, state)
)
download.accept()
self.status_bar.showMessage(f"Download započet: {suggested_filename}")
else:
download.cancel()
except Exception as e:
print(f"Greška pri downloadu: {e}")
download.cancel()
def _update_download_progress(self, download_item):
try:
download = download_item['download_obj']
download_item['received'] = download.receivedBytes()
download_item['total'] = download.totalBytes()
self.download_manager.update_download_list()
except Exception as e:
print(f"Greška pri update progresa: {e}")
def _download_state_changed(self, download_item, state):
try:
if state == QWebEngineDownloadRequest.DownloadState.DownloadCompleted:
download_item['status'] = 'Završeno'
self.status_bar.showMessage(f"Download završen: {download_item['filename']}", 5000)
elif state == QWebEngineDownloadRequest.DownloadState.DownloadCancelled:
download_item['status'] = 'Otkazano'
elif state == QWebEngineDownloadRequest.DownloadState.DownloadInterrupted:
download_item['status'] = 'Greška'
self.download_manager.update_download_list()
except Exception as e:
print(f"Greška pri promeni stanja downloada: {e}")
def _download_finished(self, download_item):
try:
if download_item['download_obj'].isFinished():
download_item['status'] = 'Završeno'
self.download_manager.update_download_list()
except Exception as e:
print(f"Greška pri završetku downloada: {e}")
# --- Metoda za update tab title ---
def update_tab_title(self, browser, title):
for index in range(self.tabs.count()):
tab_widget = self.tabs.widget(index)
if tab_widget and tab_widget.findChild(WebView) == browser:
short_title = (title[:20] + '...') if len(title) > 23 else title
self.tabs.setTabText(index, short_title)
self.tabs.setTabToolTip(index, title)
break
# --- Tematske Metode ---
def apply_style(self, style):
QApplication.instance().setStyleSheet(style)
def toggle_dark_mode(self):
if self.is_dark_mode:
self.apply_style(LIGHT_STYLE)
self.is_dark_mode = False
self.status_bar.showMessage("Prebačeno na Svetli Režim", 3000)
else:
self.apply_style(DARK_STYLE)
self.is_dark_mode = True
self.status_bar.showMessage("Prebačeno na Tamni Režim", 3000)
# --- Command Palette Metode ---
def _setup_command_map(self):
return {
"new tab": lambda: self.add_new_tab(QUrl("about:blank"), "Nova Kartica"),
"new private tab": lambda: self.add_new_tab(QUrl("about:blank"), "Privatna", is_private=True),
"go home": self.navigate_home,
"close tab": lambda: self.close_tab(self.tabs.currentIndex()),
"reload page": lambda: self.tabs.currentWidget().findChild(WebView).reload(),
"toggle dark mode": self.toggle_dark_mode,
"show history": self.show_history_dialog,
"add bookmark": self.add_bookmark,
"download manager": self.show_download_manager,
"show developer tools": self.show_dev_tools,
"print to pdf": self.print_to_pdf,
"change user agent": self.change_user_agent,
"clear browser data": self.clear_browser_data,
"fix drm issues": self.fix_drm_issues,
"test drm support": self.test_drm_support,
"start html editor": self.start_htmleditor,
"start xuvd": self.start_xuvd,
}
def show_command_palette(self):
command_list = list(self.command_map.keys())
command, ok = QInputDialog.getItem(
self, "Gemini Command Palette (Ctrl+K)", "Izaberi/Unesi komandu:", command_list, 0, True
)
if ok and command:
self.execute_command(command.lower().strip())
def execute_command(self, command):
if command in self.command_map:
try:
self.command_map[command]()
self.status_bar.showMessage(f"Komanda izvršena: '{command}'")
except Exception as e:
self.status_bar.showMessage(f"Greška pri izvršavanju komande '{command}': {e}")
else:
self.status_bar.showMessage(f"Nepoznata komanda: '{command}'", 5000)
# --- Metode za Navigaciju i Kartice ---
def navigate_home(self):
current_browser = self.tabs.currentWidget().findChild(WebView)
if current_browser:
current_browser.setUrl(QUrl("https://abel.rs/zx"))
def navigate_to_url(self):
q = QUrl(self.url_input.text())
if q.scheme() == "": q.setScheme("https")
if q.host() == "" and "." not in self.url_input.text():
q = QUrl(f"https://www.bing.com/search?q={self.url_input.text()}")
current_browser = self.tabs.currentWidget().findChild(WebView)
if current_browser:
current_browser.setUrl(q)
def add_new_tab(self, qurl=None, initial_title="Učitavanje...", is_private=False):
if qurl is None: qurl = QUrl("https://abel.rs/zx")
browser_container = QWidget()
container_layout = QVBoxLayout(browser_container)
container_layout.setContentsMargins(0, 0, 0, 0)
profile = self.private_profile if is_private else self.default_profile
browser = WebView(self, browser_container, profile=profile)
browser.setUrl(qurl)
browser.page().loadFinished.connect(self._save_history_on_load)
container_layout.addWidget(browser)
i = self.tabs.addTab(browser_container, initial_title)
self.tabs.setCurrentIndex(i)
self.update_url_bar(qurl)
return browser
def close_tab(self, index):
if self.tabs.count() < 2:
self.close()
else:
self.tabs.removeTab(index)
def current_tab_changed(self, index):
if index > -1:
current_browser = self.tabs.currentWidget().findChild(WebView)
if current_browser:
self.update_url_bar(current_browser.url())
def update_url_bar(self, q):
self.url_input.setText(q.toString())
self.url_input.setCursorPosition(0)
def update_url_bar_for_current_tab(self, q):
if not self.tabs.currentWidget():
return
current_browser = self.tabs.currentWidget().findChild(WebView)
if current_browser and current_browser.url() == q:
self.update_url_bar(q)
def update_status_bar_link(self, url):
if url:
self.status_bar.showMessage(url)
else:
self.status_bar.clearMessage()
# --- Metode za Bazu Podataka i Istoriju ---
def _setup_database(self):
conn = sqlite3.connect(self.db_name)
cursor = conn.cursor()
cursor.execute("CREATE TABLE IF NOT EXISTS bookmarks (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, url TEXT NOT NULL UNIQUE, added_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP)")
cursor.execute("CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, url TEXT NOT NULL, visit_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP)")
conn.commit()
conn.close()
self.status_bar.showMessage("Baza podataka za obeleživače i istoriju spremna.")
def add_bookmark(self):
current_browser = self.tabs.currentWidget().findChild(WebView)
if not current_browser: return
url = current_browser.url().toString()
title = current_browser.title()
if not url or url == "about:blank":
self.status_bar.showMessage("Ne možete obeležiti ovu stranicu.", 3000)
return
conn = sqlite3.connect(self.db_name)
cursor = conn.cursor()
try:
cursor.execute("INSERT INTO bookmarks (title, url) VALUES (?, ?)", (title, url))
conn.commit()
self.status_bar.showMessage(f"Obeleživač dodat: {title}", 5000)
except sqlite3.IntegrityError:
self.status_bar.showMessage("Ova stranica je već u obeleživačima.", 5000)
finally:
conn.close()
def _save_history_on_load(self, success):
if success:
current_browser = self.tabs.currentWidget().findChild(WebView)
if not current_browser: return
url = current_browser.url().toString()
title = current_browser.title()
if not url or url == "about:blank" or url.startswith("data:"): return
conn = sqlite3.connect(self.db_name)
cursor = conn.cursor()
cursor.execute("INSERT INTO history (title, url) VALUES (?, ?)", (title, url))
conn.commit()
conn.close()
def get_history(self):
conn = sqlite3.connect(self.db_name)
cursor = conn.cursor()
cursor.execute("SELECT title, url, visit_date FROM history ORDER BY visit_date DESC")
history = cursor.fetchall()
conn.close()
return history
def show_history_dialog(self):
history_dialog = HistoryDialog(self)
history_dialog.exec()
# --- Metode za AI Summary Panel ---
def _setup_ai_summary_panel(self):
self.summary_dock = QDockWidget("✨ Gemini AI Sažetak", self)
self.summary_dock.setAllowedAreas(Qt.DockWidgetArea.RightDockWidgetArea)
dock_content = QWidget()
dock_layout = QVBoxLayout(dock_content)
self.summary_title = QLabel("Kliknite na 'Gemini Sažetak' za analizu stranice.")
self.summary_title.setStyleSheet("font-weight: bold; padding: 5px;")
self.summary_text_area = QTextEdit()
self.summary_text_area.setReadOnly(True)
self.summary_text_area.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
dock_layout.addWidget(self.summary_title)
dock_layout.addWidget(self.summary_text_area)
self.summary_dock.setWidget(dock_content)
self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.summary_dock)
self.summary_dock.hide()
def generate_gemini_summary(self):
current_browser = self.tabs.currentWidget().findChild(WebView)
if not current_browser:
self.status_bar.showMessage("Nema aktivne kartice za sumarizaciju.", 3000)
return
self.summary_dock.show()
self.summary_text_area.setText("Analiziram sadržaj stranice, molimo pričekajte...")
self.summary_title.setText("✨ Analiza u toku...")
current_browser.page().toPlainText(self._handle_page_text)
def _handle_page_text(self, text_content):
if not text_content or len(text_content) < 50:
self.summary_title.setText("⚠️ Nema dovoljno teksta za analizu.")
self.summary_text_area.setText("Stranica je prazna, sadrži samo slike ili skripte. Ne mogu generisati sažetak.")
return
# SIMULACIJA AI OBRADE
sentences = [s.strip() for s in text_content.split('.') if s.strip()]
key_points = []
if len(sentences) > 0: key_points.append(sentences[0])
if len(sentences) > 2: key_points.append(sentences[2])
if len(sentences) > 5: key_points.append(sentences[-1])
simulated_summary = (
f"**SAŽETAK GENERISAN OD GEMINI AI:**\n\n"
f"**Ključne Teme:**\n"
f" - Prvi Pasus: *{key_points[0] if len(key_points)>0 else 'Nema dovoljno teksta.'}*\n"
f" - Glavni Fokus: *{key_points[1] if len(key_points)>1 else 'Tekst prekratak za srednju analizu.'}*\n"
f" - Zaključak: *{key_points[2] if len(key_points)>2 else 'Zaključak nije pronađen.'}*\n\n"
f"**Analizirana Dužina:** {len(text_content.split())} reči."
)
current_title = self.tabs.currentWidget().findChild(WebView).title()
self.summary_title.setText(f"✅ Sažetak za: {current_title}")
self.summary_text_area.setText(simulated_summary)
self.status_bar.showMessage("Gemini AI analiza uspešno završena!", 5000)
if __name__ == '__main__':
QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_ShareOpenGLContexts)
app = QApplication(sys.argv)
app.setApplicationName("Gemini Browser")
window = MainWindow()
window.show()
sys.exit(app.exec())
#MIT_License.txt MIT License Copyright (c) [2025] [Aleksandar Maričić] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
