Well, that’s a perfect example. I’ve adapted it to my Python script and it works like a charmed.
Here is the full code, hope it would be useful to everyone. Shares are in ~/SMBLinks/
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import tkinter as tk
from tkinter import ttk, messagebox
import subprocess
import os
import getpass
from pathlib import Path
class SMBMounter:
def __init__(self, root):
self.root = root
self.root.title("SMB Share Mounter")
# Configuration
self.default_server = "lagrange"
self.server = self.default_server
self.username = getpass.getuser()
self.smb_links = Path.home() / "SMBLinks"
self.credentials_dir = Path.home() / ".credentials"
# Default shares if detection fails
self.default_shares = {
"photo": {"mounted": False},
"music": {"mounted": False},
"video": {"mounted": False},
"commun": {"mounted": False}
}
# Create main container
self.main_container = ttk.Frame(self.root, padding="10")
self.main_container.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Create server input form
self.create_server_form()
# Initialize shares as empty
self.shares = {}
def create_server_form(self):
"""Creates the server input form"""
form_frame = ttk.Frame(self.main_container)
form_frame.grid(row=0, column=0, pady=10, sticky=(tk.W, tk.E))
# Server label and entry
ttk.Label(
form_frame,
text="Server:",
font=('Helvetica', 10)
).pack(side=tk.LEFT, padx=5)
self.server_entry = ttk.Entry(form_frame, width=30)
self.server_entry.pack(side=tk.LEFT, padx=5)
self.server_entry.insert(0, self.default_server)
# Connect button
ttk.Button(
form_frame,
text="Connect",
command=self.connect_to_server
).pack(side=tk.LEFT, padx=5)
def connect_to_server(self):
"""Connects to the specified server and detects shares"""
new_server = self.server_entry.get().strip()
if not new_server:
messagebox.showerror("Error", "Please enter a server name")
return
self.server = new_server
# Clear previous widgets if they exist
if hasattr(self, 'main_frame'):
self.main_frame.destroy()
# Detect shares on the new server
self.shares = self.detect_shares()
# Create necessary directories
self.setup_directories()
# Create interface
self.create_widgets()
# Check mount status
self.check_mounted_shares()
def setup_directories(self):
"""Creates necessary directories"""
self.smb_links.mkdir(parents=True, exist_ok=True)
self.credentials_dir.mkdir(parents=True, exist_ok=True)
self.credentials_dir.chmod(0o700)
def detect_shares(self):
"""Detects available shares on the server"""
shares = {}
try:
cmd = ["smbclient", "-L", self.server, "-N"]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
lines = result.stdout.split('\n')
for line in lines:
if "Disk" in line and not "$" in line:
share_name = line.split()[0].strip()
shares[share_name] = {"mounted": False}
if not shares:
messagebox.showwarning(
"Warning",
f"No shares found on {self.server}\nUsing default shares list"
)
shares = self.default_shares.copy()
else:
messagebox.showerror(
"Error",
f"Unable to list shares on {self.server}:\n{result.stderr}\nUsing default shares list"
)
shares = self.default_shares.copy()
except Exception as e:
messagebox.showerror(
"Error",
f"Error while detecting shares: {str(e)}\nUsing default shares list"
)
shares = self.default_shares.copy()
return shares
def create_widgets(self):
"""Creates the main interface widgets"""
self.main_frame = ttk.Frame(self.main_container)
self.main_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Header with refresh button
header_frame = ttk.Frame(self.main_frame)
header_frame.grid(row=0, column=0, columnspan=2, pady=10)
ttk.Label(
header_frame,
text=f"Available shares on {self.server}",
font=('Helvetica', 10, 'bold')
).pack(side=tk.LEFT, padx=5)
ttk.Button(
header_frame,
text="↻",
width=3,
command=self.refresh_shares
).pack(side=tk.LEFT, padx=5)
# Checkboxes for shares
self.check_vars = {}
for idx, share in enumerate(self.shares, start=1):
self.check_vars[share] = tk.BooleanVar()
cb = ttk.Checkbutton(
self.main_frame,
text=f"Share \\{share}",
variable=self.check_vars[share]
)
cb.grid(row=idx, column=0, sticky=tk.W, pady=2)
# Action buttons
btn_frame = ttk.Frame(self.main_frame)
btn_frame.grid(row=len(self.shares) + 2, column=0, columnspan=2, pady=10)
ttk.Button(
btn_frame,
text="Mount Selected",
command=self.mount_selected
).grid(row=0, column=0, padx=5)
# Add new Unmount Selected button
ttk.Button(
btn_frame,
text="Unmount Selected",
command=self.unmount_selected
).grid(row=0, column=1, padx=5)
ttk.Button(
btn_frame,
text="Unmount All",
command=self.unmount_all
).grid(row=0, column=2, padx=5)
def refresh_shares(self):
"""Refreshes the list of available shares"""
# Save current mount states
mounted_states = {share: self.shares[share]["mounted"]
for share in self.shares if share in self.check_vars}
# Detect shares again
self.shares = self.detect_shares()
# Recreate widgets
if hasattr(self, 'main_frame'):
self.main_frame.destroy()
self.create_widgets()
# Restore mount states
for share, mounted in mounted_states.items():
if share in self.check_vars:
self.check_vars[share].set(mounted)
def mount_selected(self):
"""Mounts selected shares"""
for share, var in self.check_vars.items():
if var.get():
self.mount_share(share)
self.check_mounted_shares()
def mount_share(self, share):
"""Mounts a specific share"""
try:
cmd = ["gio", "mount", f"smb://{self.server}/{share}"]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
endpoint = Path(f"/run/user/{os.getuid()}/gvfs/smb-share:server={self.server},share={share}")
link_path = self.smb_links / share
if endpoint.exists():
if link_path.exists():
link_path.unlink()
link_path.symlink_to(endpoint)
messagebox.showinfo("Success", f"Share {share} mounted successfully")
return True
else:
messagebox.showerror("Error", f"Error mounting {share}\n{result.stderr}")
return False
except Exception as e:
messagebox.showerror("Error", str(e))
return False
def unmount_selected(self):
"""Unmounts only the selected shares"""
for share, var in self.check_vars.items():
if var.get():
self.unmount_share(share)
self.check_mounted_shares()
def unmount_share(self, share):
"""Unmounts a specific share"""
try:
# Remove symbolic link
link_path = self.smb_links / share
if link_path.exists():
link_path.unlink()
# Unmount share
cmd = ["gio", "mount", "-u", f"smb://{self.server}/{share}"]
subprocess.run(cmd, capture_output=True, check=True)
return True
except Exception as e:
messagebox.showerror("Error", f"Error unmounting {share}: {str(e)}")
return False
def check_mounted_shares(self):
"""Checks which shares are currently mounted"""
for share in self.shares:
link_path = self.smb_links / share
self.shares[share]["mounted"] = link_path.exists()
if __name__ == "__main__":
root = tk.Tk()
app = SMBMounter(root)
root.mainloop()